Skip to main content
The Quickstart deploys the upstream demo image. This guide walks the builder path: create your own copy of site-template, let it self-customise, push your own image, and deploy that. We’ll call your new repo mysite throughout — substitute whatever name you actually pick (my-blog, acme-marketing, etc.). Same for <owner> — your GitHub account or organisation.

Prerequisites

  • GitHub account with permission to create public repos and packages
  • Docker Desktop or OrbStack (macOS), or Docker Engine on Linux
  • git, make, composer
  • gh (GitHub CLI) — optional, but the docs use it for terseness
  • kind + kubectl + helm ≥ v3.8 — only needed for the cluster step
  • A new repo at github.com/<your-account>/<your-site> (public by default — flip to private if you prefer; cluster-side pulls of a private GHCR package need credentials, e.g. node-level containerd auth on Talos or a per-namespace imagePullSecret)
  • A GHCR package at ghcr.io/<your-account>/<your-site> — built and tagged automatically by CI
1

Create your site repo

GitHub gives you two ways to derive a new repo from site-template. Pick whichever matches how closely you want to track upstream.Whichever you pick, you’ll end up with a new repo at github.com/<owner>/mysite.Within ~30 seconds GitHub Actions runs the template-cleanup workflow on your new repo. It does three things:
  1. Substitutes the upstream-hardcoded fields:
    • composer.json: name, description, homepage, support.issues, support.source
    • Dockerfile: org.opencontainers.image.source label
  2. Removes the cleanup workflow itself (one-shot).
  3. Triggers build.yml to publish the first image.
You’ll see two commits on main: the original template snapshot and chore: initialize from site-template. Watch progress with:
gh run watch --repo <owner>/mysite
After ~2 minutes, ghcr.io/<owner>/mysite:latest is pullable and the workflows tab shows green template-cleanup + build runs.
2

Clone and run locally

git clone git@github.com:<owner>/mysite.git
cd mysite
fp init
fp init is a one-command onboarding helper — it scaffolds .env from .env.example, runs composer install via docker, brings the stack up (docker compose up -d --wait), installs WordPress with sensible local defaults, and applies the latest committed snapshot (if any). Re-run it after docker compose down -v to recover from a wiped local stack.Open http://localhost:8080/wp/wp-admin/ and log in (admin / admin by default; override via [init] in frankenpress.toml).
Lower-level alternative if you’d rather drive it manually:
make setup
make up
make wp ARGS="core install \
  --url=http://localhost:8080 \
  --title='My Site' \
  --admin_user=admin \
  --admin_email=admin@example.com \
  --admin_password=admin \
  --skip-email"
make wp runs wp-cli in the site container with --allow-root --path=/app/web/wp (WordPress core lives under web/wp/ per the Bedrock layout — wp-cli won’t find it otherwise). Pass your sub-command + flags through ARGS="…".
Local Docker Compose is the only place you need to install WordPress manually (or via fp init). When you deploy via the Helm chart (the next steps), the chart runs wp core install automatically as a post-install hook Job — see charts → First install.
The “Update WordPress” / “Update Plugins” / “Update Themes” buttons should be absent. The lockdown is hard-coded in config/application.php — see site-template → Lockdown.
Upload a media item under Media → Add New. It lands in MinIO at http://localhost:9001 (credentials minioadmin / minioadmin).
make logs tails the site container. make shell drops you into it. make down tears the stack down (volumes preserved); make clean wipes volumes too.
3

Cut a release

The template’s build.yml publishes images on every push to main (:latest + :<short-sha>) and on every v*.*.* tag (:vX.Y.Z additionally). Tag your first release:
git tag v0.1.0
git push origin v0.1.0
Watch the build:
gh run watch
Three image tags now exist on ghcr.io/<owner>/mysite:
  • :latest (moves on every main push)
  • :<short-sha> (immutable per commit)
  • :v0.1.0 (immutable per tag — use this in production)
Your site builds against PHP 8.3 by default. To pin to a different supported PHP, see Runtime matrix.
4

Deploy to a cluster

Same site chart as the Quickstart, pointed at your image:
kind create cluster --name myfp
kubectl config use-context kind-myfp

helm install mysite oci://ghcr.io/frankenpress/charts/site \
  --version 0.13.2 \
  --namespace mysite --create-namespace \
  --set image.repository=ghcr.io/<owner>/mysite \
  --set image.tag=v0.1.0 \
  --set site.url=http://localhost:8080 \
  --set site.env=development \
  --wait --timeout=10m
--set site.url=http://localhost:8080 matches the port-forward URL below; --set site.env=development keeps FORCE_SSL_ADMIN=false so /wp/wp-admin/ doesn’t 302 to a https:// URL the port-forward isn’t serving. See Quickstart for the full explanation. For a real deploy, drop both overrides and set site.url to your public HTTPS URL.
The chart runs wp core install automatically as a post-install hook Job — see charts → First install for the admin credentials, bring-your-own-Secret, and rotation options.Port-forward and log in:
# Retrieve the auto-generated admin password
kubectl --namespace mysite get secret mysite-install \
  -o jsonpath='{.data.admin_password}' | base64 -d

kubectl --namespace mysite port-forward svc/mysite 8080:80 &
Open http://localhost:8080/wp/wp-admin/ and log in as admin.
The default install brings up bundled MariaDB + Redis + MinIO subcharts — fine for kind, not for production. Swap them out per Production topology.

What’s next

You’ve got mysite building, deployed, and serving. The natural next step is making it yours — adding plugins, picking a theme, swapping defaults.

Customizing your site

Add and remove plugins and themes the FrankenPress way — composer at build time, immutable image tag, GitOps deploy.

Production topology

Replace bundled subcharts with operator-managed databases + AWS S3.

All env vars

Every FP_* and WP_* variable the platform reads, with defaults.

Upgrade flow

git tag → CI → image build → helm upgrade --set image.tag=... per environment.