Skip to content

Production Docker image (FrankenPHP + compose + ProductionSeeder)#14

Open
bambamboole wants to merge 2 commits into
mainfrom
feat-production-docker-setup
Open

Production Docker image (FrankenPHP + compose + ProductionSeeder)#14
bambamboole wants to merge 2 commits into
mainfrom
feat-production-docker-setup

Conversation

@bambamboole
Copy link
Copy Markdown
Owner

Summary

  • Adds a production-ready Docker setup on top of serversideup/php:8.4-frankenphp: multi-stage Dockerfile (node assets → composer vendor → runtime) and compose-production.yaml with three services — app (FrankenPHP on :8080), scheduler (same image, runs supercronic against crontab), db (MySQL 8).
  • Container init script (docker/entrypoint.d/10-init.sh) gates an optional first-boot wp core install behind WP_INSTALL=true, then always runs wp acorn migrate --force and wp acorn optimize (config + route + view cache).
  • New ProductionSeeder handles site-specific setup on first install: permalinks (/blog/%postname%/), home + blog pages with correct show_on_front / page_on_front / page_for_posts options, WooCommerce activation.

Logging

All logs stream to docker logs — no file fallbacks:

  • Caddy access logs as JSON (CADDY_LOG_FORMAT=json)
  • Acorn/Laravel to stderr (LOG_CHANNEL=stderr, using Acorn's stock stderr Monolog channel)
  • Supercronic with -json -passthrough-logs
  • WordPress WP_DEBUG_LOG already hard-coded false in Bedrock — no debug.log file ever appears
  • storage/logs/ stays empty at runtime

Operator interface

  • .env.production.example — 14 vars total: WP_HOME, four DB_*, eight WP salts, WP_INSTALL. Optional overrides for WP_TITLE / WP_ADMIN_USER / WP_ADMIN_PASSWORD / WP_ADMIN_EMAIL (defaults admin / admin / admin@admin.com / WordPress).
  • Image listens on plain HTTP :8080 — intended for an external TLS-terminating reverse proxy. README suggests Coolify as the deployment target.

Notes worth flagging

  • Multi-arch: supercronic install uses ARG TARGETARCH so the binary matches the build platform (caught on Apple Silicon).
  • mysqli isn't in the serversideup base image — added via install-php-extensions mysqli exif.
  • Scheduler runs supercronic -no-reap because go-reaper conflicts with the base image's s6 zombie reaper.
  • composer install --no-scripts skips the project's post-install-cmd (which runs npm ci + Playwright); the vendor stage then re-runs composer dump-autoload --optimize --no-dev so Acorn's post-autoload-dump (package manifest) still fires.
  • WordPressBaselineSeeder is unchanged besides one new bullet on the demo home page mentioning the Docker setup. ProductionSeeder is a separate file; the splitting of demo vs. baseline content is not part of this PR.

Test plan

  • docker compose --env-file .env.production -f compose-production.yaml up -d --build brings up dbapp (healthy) → scheduler
  • WP_INSTALL=true triggers wp core install + ProductionSeeder; idempotent on restart
  • WP_INSTALL=false (or unset) skips install with a clear log message
  • wp acorn migrate and wp acorn optimize run on every boot (after install)
  • curl http://localhost:8080/HTTP 200 after install
  • Caddy access logs visible in docker logs app as JSON
  • Supercronic JSON events visible in docker logs scheduler; wp acorn schedule:run and wp cron event run --due-now both report job succeeded each minute
  • WooCommerce active after ProductionSeeder (wp plugin list --status=active includes woocommerce)
  • Home page front-page (show_on_front=page, page_on_front=<home>), blog page wired (page_for_posts=<blog>), permalinks /blog/%postname%/
  • storage/logs/ stays empty (proves stderr channel is active)
  • Behind a real TLS-terminating proxy / on Coolify (not exercised in local smoke test)

🤖 Generated with Claude Code

bambamboole and others added 2 commits May 13, 2026 23:46
Multi-stage Dockerfile on serversideup/php:8.4-frankenphp (node assets ->
composer vendor -> runtime). Adds `compose-production.yaml` with three
services: `app` (FrankenPHP on :8080, runs the init script), `scheduler`
(same image, runs supercronic against `crontab`), and `db` (MySQL 8).

The container init script at `docker/entrypoint.d/10-init.sh` gates an
optional first-boot `wp core install` behind `WP_INSTALL=true`, then
always runs `wp acorn migrate --force` and `wp acorn optimize`
(config + route + view cache). A new `ProductionSeeder` handles the
site-specific setup (permalinks, home + blog pages, WooCommerce
activation) and is invoked from the init script only on the first
install.

All logs stream to docker logs: Caddy access (`CADDY_LOG_FORMAT=json`),
Acorn/Laravel (`LOG_CHANNEL=stderr`), supercronic (`-json
-passthrough-logs`), and the init script. WordPress `WP_DEBUG_LOG` is
already hard-coded false in Bedrock; nothing falls back to files.

The image listens on plain HTTP; an external reverse proxy (Coolify is
the suggested target) terminates TLS. `.env.production.example` lists
the operator-supplied env vars. README has a new "Production deployment"
section and the demo home page mentions the Docker setup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant