Skip to main content
This guide spins up a working WordPress site on a local kind cluster. It uses the published runtime image and the site-template template repo unchanged — no fork required for the smoke test.

Prerequisites

  • Docker Desktop or OrbStack (macOS), or Docker Engine on Linux
  • kind ≥ v0.20
  • kubectl ≥ v1.27
  • helmv3.8 (OCI registry support is required)
  • composer (only for the optional fork-and-build step)
The default Helm install brings up 5 pods (site + mariadb + redis + minio + minio-console). Budget at least 2 CPU and 3 GB RAM for the cluster.

3-step quickstart

1

Create a kind cluster

kind create cluster --name frankenpress
kubectl config use-context kind-frankenpress
Verify the node is ready:
kubectl get nodes
# NAME                         STATUS   ROLES           AGE   VERSION
# frankenpress-control-plane   Ready    control-plane   30s   v1.35.0
2

Install the chart

The chart is published as an OCI artifact on GHCR. Install it with all subchart defaults (in-cluster MariaDB + Redis + MinIO — fine for kind, not for production):
helm install mysite oci://ghcr.io/frankenpress/charts/site \
  --version 0.13.2 \
  --namespace mysite --create-namespace \
  --set site.url=http://localhost:8080 \
  --set site.env=development \
  --set externalS3.bucketUrl=http://localhost:9000/site-media \
  --wait --timeout=10m
The --wait flag blocks until all pods are Ready. Expect 1–2 minutes on a cold image cache.
--set site.url=http://localhost:8080 matches the URL you’ll reach through kubectl port-forward in the next step. Without it, WP_HOME defaults to http://site.localhost and WordPress 301-redirects every localhost:8080 request away — you’ll see “unknown app” or a connection error in the browser. For a real deploy, set this to your public URL (and enable Ingress / HTTPRoute instead of port-forward).--set site.env=development keeps FORCE_SSL_ADMIN=false so /wp/wp-admin/ doesn’t 302 to https://localhost:8080 (which the port-forward isn’t TLS-serving). The default site.env=production is correct for any real deploy behind a TLS-terminating Ingress — only relax it for the local port-forward case.--set externalS3.bucketUrl=http://localhost:9000/site-media tells humanmade/s3-uploads to write that URL into attachment GUIDs and post content. Without it, WordPress bakes canonical https://site-media.s3.amazonaws.com/... URLs and the Media Library 403s on every thumbnail. For a real deploy this is your CDN URL — see Production topology.
The chart runs wp core install automatically as a post-install Helm hook Job — no manual kubectl exec step. Watch it complete:
kubectl --namespace mysite get jobs --watch
Once the install Job is Complete, expose the MinIO bucket for anonymous browser GETs so the Media Library can render thumbnails:
kubectl --namespace mysite exec deploy/mysite-minio -- \
  mc anonymous set download local/site-media
The bundled MinIO subchart creates buckets private by default. mc anonymous set download makes objects publicly readable — acceptable for a dev kind cluster, never for production. Real deploys put their bucket behind a CDN with IAM-controlled Object Ownership.
The default image.repository points at the demo site image ghcr.io/frankenpress/site-template at the chart’s appVersion tag. To deploy your own site, fork site-template, push a v*.*.* tag (CI builds + pushes to your GHCR), then add --set image.repository=ghcr.io/<your-org>/<your-site>,image.tag=v1.0.0.
3

Log in to your site

Retrieve the auto-generated admin password:
kubectl --namespace mysite get secret mysite-install \
  -o jsonpath='{.data.admin_password}' | base64 -d
Port-forward both the site and the MinIO bucket (in two separate shells, or background the first with &):
kubectl --namespace mysite port-forward svc/mysite 8080:80 &
kubectl --namespace mysite port-forward svc/mysite-minio 9000:9000 &
Bitnami’s common.names.fullname helper collapses <release>-<chart> to just <release> when the release name already contains the chart name (site) as a substring. Because mysite contains site, resources render as mysite, mysite-install, mysite-keys, mysite-minio, etc. For a release named myblog, the same resources would be myblog-site, myblog-site-install, myblog-site-minio.
Open http://localhost:8080/wp/wp-admin/ in your browser. Log in as admin with the password from the Secret above.
You should see the WordPress admin dashboard. Confirm the “Update WordPress” / “Update Plugins” / “Update Themes” buttons are absent — the in-cluster lockdown is on. Upload an image from Media → Add New — the thumbnail should render immediately in the Media Library, served from localhost:9000/site-media/....
Upgrading from 0.12.2 or earlier? Pre-0.12.3 didn’t emit FP_S3_BUCKET_URL when the MinIO subchart was enabled, so any attachments uploaded before this fix have AWS URLs baked into wp_posts.guid and post content. Rewrite them in place:
kubectl --namespace mysite exec deploy/mysite -- \
  wp --allow-root --path=/app/web/wp search-replace \
  'https://site-media.s3.amazonaws.com' 'http://localhost:9000/site-media' \
  --all-tables
Want to set the admin password at install time instead of retrieving the generated one? Pass --set siteInstall.adminPassword=your-password on the helm install line. See charts → First install for the full options including bring-your-own Secret.

Tear it down

helm uninstall mysite --namespace mysite
kubectl delete namespace mysite
kind delete cluster --name frankenpress

Common things to do next

Your first site

The full builder path: fork the template, customise locally, publish to your GHCR, deploy your own image.

Add a plugin

composer require wpackagist-plugin/<slug> — composer.json + lock, rebuild the image, redeploy.

Production topology

Replace MariaDB / Redis / MinIO subcharts with operator-managed services and AWS S3.

All env vars

Every FP_* and WP_* env var the platform reads, with defaults.
Smoke-tested. This quickstart is run end-to-end against a fresh kind cluster as part of FrankenPress release validation. If a step fails, open an issue with the output — we treat docs drift as a real bug.