Configuration comes from the environment
One committed wp-config.php reads every setting from environment variables, or from files via NAME_FILE for Docker and Swarm secrets. The same file runs locally, in staging and in production.
WhalePress is an open-source starter for teams and agencies that take WordPress seriously. Docker from laptop to production, a component-based Timber theme with Tailwind, daisyUI and Alpine, Composer-managed plugins, strict quality gates, and a release pipeline that ships immutable images to the cloud you already use.

$ git clone https://github.com/ufmedia/whalepress.git acme && cd acme
$ npm install && npm run init # make it yours
? Project name (PascalCase) Acme
? Slug acme
✔ Theme, namespace, text domain, prefixes and image names renamed in one reviewable diff
$ npm run setup # interactive, safe to re-run
✔ .env created: salts and admin password generated
✔ Theme assets built with Vite
✔ Stack healthy: wordpress, db, redis, mailpit, adminer
✔ Composer install, WordPress installed, theme and plugins activated, Redis cache on
→ Site ready at http://localhost:8088
$ npm start # containers + Vite HMR
The stack, current
Why WhalePress
Most WordPress projects drift: plugins updated on live, settings tweaked in wp-admin, a staging site that's nothing like production. WhalePress makes the right way the easy way, so every site you build behaves like a modern application.
One committed wp-config.php reads every setting from environment variables, or from files via NAME_FILE for Docker and Swarm secrets. The same file runs locally, in staging and in production.
Core, plugins and theme are baked into each release image. Nothing is installed or updated from wp-admin, so what you tested is exactly what runs, and rolling back is redeploying the previous tag.
Plugins come from Composer via WordPress Packagist (or your private repository); assets and tooling from npm. Lockfiles are the source of truth, and the repository stays small and reviewable.
Every pull request builds the production image, scans it for CVEs, boots it and runs the full Playwright suite against it, before anything is tagged or deployed.
Local development
npm run setup builds the dev image, starts every service, waits for health checks, installs WordPress and prints your credentials. New starters are productive on day one, and every developer runs the same thing.
Apache and PHP 8.4 from the same Dockerfile as production. Core lives in the container; only your code is mounted.
MariaDB 11.8 LTS with health checks, and a browser UI for when you need to poke at a table.
A persistent object cache, enabled by setup, so caching bugs show up on your machine and not in production.
Catches every email WordPress sends. The same SMTP plugin sends real mail in production.
Instant CSS updates and hot module replacement, switched on automatically while the dev server runs.
Installed and off by default, so there is zero overhead until you set XDEBUG_MODE. VS Code launch config included.
.sql.gz in local/initial-db/ and new environments start from it.npm run wp, npm run composer, npm run logs: no container names to remember.Pull a database from any environment and rewrite its URLs in one step. Missing media is proxied from the live site, so you never copy an uploads folder again.
# Configure once in .env
DB_PULL_FROM_URL=https://www.example.com
DB_PULL_COMMAND=ssh [email protected] "wp db export -"
UPLOADS_PROXY_URL=https://www.example.com
$ npm run db:pull # import, search-replace, flush caches
$ npm run db:export # timestamped .sql.gz snapshotThere is deliberately no db:push. Overwriting production should never be one command away.Database docs →
The theme
A classic theme with the ergonomics of a modern framework: Twig components, typed PHP classes, daisyUI on Tailwind CSS 4, Alpine.js and a Vite build. No page builder, no Sass, no 2,000-line functions.php.
Every piece of UI is a component, called with parameters: component('card', { … }). Components see only what they’re given, so they behave the same everywhere. Views just compose them.
Every feature is a small class implementing Hookable, with side-effect-free constructors. Easy to find, test and remove.
theme.json is the source of truth, mapped onto a single daisyUI theme and Tailwind CSS 4. The editor, block content and every component share colours, a fluid Utopia type scale and spacing, with dark mode built in.
Editors get blocks for content, presets for style, block styles like Card and Lead, and starter patterns tested in CI. No full-site editing, custom colours, remote patterns or font library. The design can’t be broken from wp-admin.
Menus, submenus and the colour scheme toggle are small Alpine components. State lives in ARIA attributes, the markup is ready for Alpine’s CSP build, and Vite bundles it all with hot reloads.
Keyboard-friendly disclosures, axe checks in both colour schemes, self-hosted fonts and a .pot generated from both PHP and Twig. A compatibility shim keeps WooCommerce-style plugins working.
<?php
declare(strict_types=1);
namespace Acme\Content;
use Acme\Contracts\Hookable;
final class Events implements Hookable {
public function register(): void {
add_action( 'init', array( $this, 'register_post_type' ) );
}
public function register_post_type(): void {
register_post_type( 'event', array( /* … */ ) );
}
}{% extends 'layouts/base.twig' %}
{% block content %}
{{ component('page-header', {
title: post.title,
meta: post.meta('starts_at')|date('j F Y')
}) }}
<div class="entry-content">{{ post.content }}</div>
{{ component('cta', {
title: __('Save your seat', 'acme'),
actions: [{
label: __('Book now', 'acme'),
url: post.meta('booking_url')
}]
}) }}
{% endblock %}Quality
The tooling you'd expect on any serious codebase, configured, wired into commit hooks and CI, and green out of the box. Run all of it locally with npm run lint, npm run analyse andnpm run test.
Three parallel jobs. All must pass before anything merges or releases.
PHP quality
Assets and formatting
Image and end-to-end
Release and deploy
semantic-release reads your Conventional Commits, tags the version and writes the release notes. Each enabled pathway then builds one immutable image, stamped with that version, and ships it. Switch pathways on with a single repository variable.
01 · trigger
Merge to main
feat: · fix: · feat!:
02 · verify
CI passes
quality · assets · image + E2E
03 · release
vX.Y.Z tagged
notes on GitHub
deploy-ghcr default
deploy-ecr DEPLOY_ECR
deploy-docr DEPLOY_DOCR
deploy-hostinger DEPLOY_HOSTINGER
Zero secrets: the first push to main publishes a versioned image. An optional webhook redeploys Portainer, Coolify, Dokku or your own endpoint.
Push to ECR with GitHub OIDC (no long-lived keys), then roll the ECS service and wait for it to stabilise.
Push to DigitalOcean Container Registry and deploy an App Platform app from the spec in the repository.
For demos and smaller sites on managed hosting: syncs only what the project owns, then activates, flushes and purges caches.
Images tagged X.Y.Z, X.Y and latest, with SBOM and provenance attestations.
Every deploy workflow can be re-run for any earlier version. That’s your rollback.
GitHub environments with required reviewers gate production when you want a human in the loop.
Run the end-to-end suite and an OWASP ZAP baseline scan against any deployed environment.
Security
Security reviews go faster when the answers are already in the repository. Hardening lives in must-use plugins that apply whatever the theme, and the pipeline treats the image like any other production artefact.
?author=Nvendor/ denied at the web serverFor agencies
WhalePress is a starter, not a framework you depend on. npm run init turns a fresh clone into your client’s project, renaming everything that carries the WhalePress name in one reviewable diff. The first push runs CI, cuts v1.0.0 and publishes an image, with no secrets needed.
| Theme directory | wp-content/themes/acme |
|---|---|
| PHP namespace | Acme\Theme |
| Text domain & hooks | 'acme', acme/security_headers |
| Functions & options | acme_env() |
| Must-use plugins | mu-plugins/acme-mail.php |
| Packages & images | acme/acme, acme:ci |
Every project keeps the starter as a Git remote. Cherry-pick a fix or merge the lot, through a normal pull request with green CI.
A VitePress wiki in docs/, published to GitHub Pages. Add a Markdown file and it’s in the sidebar; dead links fail the build.
AGENTS.md gives Claude Code, Codex and friends the conventions, commands and checks, so their changes look like your team’s.
Developers move between projects without relearning the tooling. Onboarding is clone, setup, start.
Gravity Forms, ACF Pro and friends come from private Composer repositories, with credentials kept out of Git and out of images.
GPL-2.0-or-later, no SaaS, no lock-in, no licence keys. Your repository, your registry, your infrastructure.
Get started
Docker Desktop, OrbStack, Colima or plain Docker Engine all work. PHP and Composer are optional.
Pick a name and a slug. Review the diff; undo it with git reset --hard.
$ git clone https://github.com/ufmedia/whalepress.git acme
$ cd acme && nvm use && npm install
$ npm run init
Creates .env, builds assets and the dev image, installs Composer packages and WordPress.
$ npm run setup
Containers plus the Vite dev server. Edit Twig, PHP and CSS with instant reloads.
$ npm start
CI runs, v1.0.0 is released and the image lands in GHCR. Enable more pathways when you need them.
$ git push -u origin main
Joining an existing WhalePress project instead? It’sclone, npm run setup, npm start.
FAQ
Neither. It’s a starter repository: you copy it, rename it and own every line. There’s nothing to subscribe to and no runtime dependency on WhalePress itself, just WordPress, Timber and well-known open-source tools.
No. You need Docker, Node 24 and Git. PHP tooling uses your local PHP if it’s 8.3+ with the right extensions, and quietly runs in a container if not.
WhalePress is deliberately a classic Timber theme with the block editor for content only, which keeps layout in code and under review. It’s your code, so you can change that, but the guard rails are the point.
No. daisyUI is a Tailwind plugin configured in one stylesheet, and Alpine registers a couple of small components. The UI is plain Twig components, so you can restyle them with Tailwind alone, or swap Alpine for another library, one component at a time.
Premium plugins install from private Composer repositories, with credentials supplied through COMPOSER_AUTH or BuildKit secrets. Your own plugins live in the repository and are allow-listed into the image. Plugin guide.
Yes. The Hostinger pathway syncs the theme, vendor directory and Composer plugins to managed WordPress hosting over SSH, and you still get the local stack, quality gates and releases.
Deployments carry code, not content. npm run db:pull brings a database down with its URLs rewritten, and the uploads proxy serves missing media from the live site. In production, uploads live on a volume, EFS or object storage.
Yes: set WP_ALLOW_MULTISITE=true and follow WordPress’s network setup, reading the generated constants from the environment like everything else.
Add the starter as a Git remote, then cherry-pick specific fixes or merge everything. Conventional Commits make it clear what each change does.
Start your next WordPress build with the local stack, theme, tests and pipeline already done, and spend the time on the work your client is paying for.