Skip to content
v1.15.1Open source, GPL-2.0, production-tested

WordPress, built like the rest of your stack.

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.

To a running site
4 commands
Deployment pathways
4 built in
PHPStan
Level 8
The WhalePress whale: a graffiti-style whale carrying the WordPress logo, splashed with cyan, orange and pink paint.
~/projects — from clone to running site
$ 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

  • WordPress 7.1
  • PHP 8.4
  • MariaDB 11.8
  • Redis 8
  • Timber 2
  • Twig
  • Tailwind CSS 4
  • daisyUI 5
  • Alpine.js
  • Vite 8
  • Composer
  • PHPStan
  • Playwright
  • Docker
  • GitHub Actions
  • semantic-release

Why WhalePress

Four rules, enforced by the stack.

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.

01

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.

02

Production is immutable

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.

03

Dependencies are declared, never committed

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.

04

CI tests what ships

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

A production-shaped stack on every laptop.

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.

:8088

WordPress

Apache and PHP 8.4 from the same Dockerfile as production. Core lives in the container; only your code is mounted.

:8080

MariaDB + Adminer

MariaDB 11.8 LTS with health checks, and a browser UI for when you need to poke at a table.

internal

Redis

A persistent object cache, enabled by setup, so caching bugs show up on your machine and not in production.

:8025

Mailpit

Catches every email WordPress sends. The same SMTP plugin sends real mail in production.

:5173

Vite

Instant CSS updates and hot module replacement, switched on automatically while the dev server runs.

:9003

Xdebug 3

Installed and off by default, so there is zero overhead until you set XDEBUG_MODE. VS Code launch config included.

  • No PHP or Composer on your machine. Every PHP command uses local PHP 8.3+ if it’s suitable, or a container if not.
  • Files stay yours on Linux. Apache runs as your host UID/GID, so uploads and generated files stay editable.
  • Devcontainer included. Attach VS Code to the stack with Node 24 and the whole repository mounted.
  • Debugging plugins, locally only. Query Monitor, User Switching and WP Crontrol are dev dependencies, never in the image.
  • Seeded databases. Drop a .sql.gz in local/initial-db/ and new environments start from it.
  • One command per job. npm run wp, npm run composer, npm run logs: no container names to remember.

Real data, without the faff

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 snapshot

There is deliberately no db:push. Overwriting production should never be one command away.Database docs →

The theme

A theme architecture your developers will actually enjoy.

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.

  • A Twig component library

    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.

  • PSR-4 services, not functions.php

    Every feature is a small class implementing Hookable, with side-effect-free constructors. Easy to find, test and remove.

  • One set of design tokens

    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.

  • A content-only block editor

    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.

  • Alpine.js for interactivity

    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.

  • Accessible and translatable

    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.

src/Content/Events.php+1 line in Theme::services()
<?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( /* … */ ) );
	}
}
templates/single-event.twig
{% 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 %}
Read the theme guide

Quality

Quality gates that catch problems before your client does.

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.

PHP

PHPCS + WPCS
WordPress Coding Standards, text domain and prefixes checked
PHPStan level 8
Over the theme and must-use plugins, WordPress-aware
Rector
PHP 8.3 sets, dead code, type declarations, early returns
PHPUnit + Brain Monkey
Fast unit tests with no WordPress or database needed

Front end

ESLint 10
Flat config for theme JavaScript
Stylelint
Standard config, aware of Tailwind 4 at-rules
Prettier + Twig
JS, CSS, Twig, JSON, YAML and Markdown, repo-wide

End to end

Playwright
Desktop Chrome and a Pixel 7, front end and wp-admin
axe-core
WCAG 2.2 A/AA checks fail the build on serious violations
Editor round trip
The block editor, theme styles in its canvas, REST publishing
Starter patterns
Every pattern must contain only valid, allowed blocks

Every commit

Husky + lint-staged
Lints and fixes only the files you staged
commitlint
Conventional Commits, which drive version numbers

CI on every pull request

Three parallel jobs. All must pass before anything merges or releases.

Testing and standards
  1. PHP quality

    • PHPCS, inline annotations
    • PHPStan
    • Rector
    • PHPUnit
  2. Assets and formatting

    • commitlint
    • ESLint · Stylelint · Prettier
    • Production theme build
    • Docs build, dead-link check
  3. Image and end-to-end

    • Build the production image
    • Trivy CVE scan
    • Boot it
    • Full Playwright suite against it

Release and deploy

Merge to main. The release ships itself.

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.

  1. 01 · trigger

    Merge to main

    feat: · fix: · feat!:

  2. 02 · verify

    CI passes

    quality · assets · image + E2E

  3. 03 · release

    vX.Y.Z tagged

    notes on GitHub

  4. deploy-ghcr default

    deploy-ecr DEPLOY_ECR

    deploy-docr DEPLOY_DOCR

    deploy-hostinger DEPLOY_HOSTINGER

On by default

GitHub Container Registry

Zero secrets: the first push to main publishes a versioned image. An optional webhook redeploys Portainer, Coolify, Dokku or your own endpoint.

  • Docker Swarm stack behind Traefik
  • Zero-downtime start-first updates, auto rollback
  • Docker secrets, Redis and scheduled WP-Cron
ECR + ECS Fargate

AWS

Push to ECR with GitHub OIDC (no long-lived keys), then roll the ECS service and wait for it to stabilise.

  • Fargate task definition included
  • Secrets Manager, EFS uploads, CloudWatch logs
  • Pairs with RDS, ElastiCache and EventBridge cron
DOCR + App Platform

DigitalOcean

Push to DigitalOcean Container Registry and deploy an App Platform app from the spec in the repository.

  • App spec with managed MySQL
  • Encrypted secrets
  • Scheduled job for WP-Cron
SSH sync

Hostinger

For demos and smaller sites on managed hosting: syncs only what the project owns, then activates, flushes and purges caches.

  • Theme, vendor, Composer plugins, mu-plugins
  • Never touches uploads or other plugins
  • Purges LiteSpeed Cache when active
  • 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.

Read the deployment guide

Security

Hardened before you write a line.

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.

WordPress configuration

WordPress

  • XML-RPC off and the generator tag removed
  • User enumeration blocked over REST and ?author=N
  • Generic login errors
  • Security headers, with a filter for your CSP
  • File editing and plugin installs disabled outside local
  • Staging can’t be indexed, whatever the database says

Secrets

  • No committed keys or salts, and no shared fallback
  • Any setting can be read from a file: Docker and Swarm secrets
  • Private Composer credentials passed as BuildKit secrets, never in a layer
  • GitHub OIDC for AWS, environment-scoped deploy credentials
  • Composer’s vendor/ denied at the web server

Supply chain

  • Trivy fails the build on fixable critical CVEs
  • SBOM and provenance attestations on every image
  • Every third-party action pinned to a commit SHA
  • Weekly grouped Dependabot PRs for npm, Composer, Docker and Actions, with majors kept separate
  • OWASP ZAP baseline workflow for deployed sites

For agencies

A house stack for every client project.

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.

npm run init → Acme / acme
Theme directorywp-content/themes/acme
PHP namespaceAcme\Theme
Text domain & hooks'acme', acme/security_headers
Functions & optionsacme_env()
Must-use pluginsmu-plugins/acme-mail.php
Packages & imagesacme/acme, acme:ci

Upgrades flow downstream

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.

Documentation that ships with the code

A VitePress wiki in docs/, published to GitHub Pages. Add a Markdown file and it’s in the sidebar; dead links fail the build.

Ready for AI coding agents

AGENTS.md gives Claude Code, Codex and friends the conventions, commands and checks, so their changes look like your team’s.

One stack across every client

Developers move between projects without relearning the tooling. Onboarding is clone, setup, start.

Premium plugins, properly

Gravity Forms, ACF Pro and friends come from private Composer repositories, with credentials kept out of Git and out of images.

Yours, end to end

GPL-2.0-or-later, no SaaS, no lock-in, no licence keys. Your repository, your registry, your infrastructure.

Starting a new project from WhalePress

Get started

From empty folder to first release in an afternoon.

Docker Desktop, OrbStack, Colima or plain Docker Engine all work. PHP and Composer are optional.

  • Docker with Compose v2
  • Node 24
  • Git
  1. 1

    Clone and rename

    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
    
  2. 2

    Set up the stack

    Creates .env, builds assets and the dev image, installs Composer packages and WordPress.

    $ npm run setup
    
  3. 3

    Build

    Containers plus the Vite dev server. Edit Twig, PHP and CSS with instant reloads.

    $ npm start
    
  4. 4

    Ship

    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

Questions developers ask.

Is WhalePress a hosting platform or a framework?

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.

Do I need PHP or Composer installed?

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.

Can I use block themes or full-site editing?

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.

Are we tied to daisyUI and Alpine?

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.

What about premium and custom plugins?

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.

We don’t run containers in production. Can we still use it?

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.

How do content and uploads move between environments?

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.

Does it support multisite?

Yes: set WP_ALLOW_MULTISITE=true and follow WordPress’s network setup, reading the generated constants from the environment like everything else.

How do existing projects get WhalePress improvements?

Add the starter as a Git remote, then cherry-pick specific fixes or merge everything. Conventional Commits make it clear what each change does.

Stop rebuilding the boring parts.

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.