> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frankenpress.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Deploy FrankenPress on a local kind cluster in 3 steps

This guide spins up a working WordPress site on a local
[`kind`](https://kind.sigs.k8s.io/) cluster. It uses the published
`runtime` image and the [`site-template`](https://github.com/frankenpress/site-template)
template repo unchanged — no fork required for the smoke test.

## Prerequisites

<AccordionGroup>
  <Accordion title="Required tools" icon="screwdriver-wrench">
    * **Docker Desktop** or [OrbStack](https://orbstack.dev/) (macOS), or Docker Engine on Linux
    * [`kind`](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) ≥ v0.20
    * [`kubectl`](https://kubernetes.io/docs/tasks/tools/) ≥ v1.27
    * [`helm`](https://helm.sh/docs/intro/install/) ≥ **v3.8** (OCI registry support is required)
    * `composer` (only for the optional fork-and-build step)
  </Accordion>

  <Accordion title="Resources" icon="memory">
    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.
  </Accordion>
</AccordionGroup>

## 3-step quickstart

<Steps>
  <Step title="Create a kind cluster">
    ```bash theme={null}
    kind create cluster --name frankenpress
    kubectl config use-context kind-frankenpress
    ```

    Verify the node is ready:

    ```bash theme={null}
    kubectl get nodes
    # NAME                         STATUS   ROLES           AGE   VERSION
    # frankenpress-control-plane   Ready    control-plane   30s   v1.35.0
    ```
  </Step>

  <Step title="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**):

    ```bash theme={null}
    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.

    <Note>
      `--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](/operations/production).
    </Note>

    The chart runs `wp core install` automatically as a post-install
    Helm hook Job — no manual `kubectl exec` step. Watch it complete:

    ```bash theme={null}
    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:

    ```bash theme={null}
    kubectl --namespace mysite exec deploy/mysite-minio -- \
      mc anonymous set download local/site-media
    ```

    <Note>
      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.
    </Note>

    <Tip>
      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`](https://github.com/frankenpress/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`.
    </Tip>
  </Step>

  <Step title="Log in to your site">
    Retrieve the auto-generated admin password:

    ```bash theme={null}
    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 `&`):

    ```bash theme={null}
    kubectl --namespace mysite port-forward svc/mysite 8080:80 &
    kubectl --namespace mysite port-forward svc/mysite-minio 9000:9000 &
    ```

    <Note>
      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`.
    </Note>

    Open **[http://localhost:8080/wp/wp-admin/](http://localhost:8080/wp/wp-admin/)** in your browser. Log in
    as `admin` with the password from the Secret above.

    <Check>
      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/...`.
    </Check>

    <Tip>
      **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:

      ```bash theme={null}
      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
      ```
    </Tip>

    <Tip>
      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](/components/charts#first-install)
      for the full options including bring-your-own Secret.
    </Tip>
  </Step>
</Steps>

## Tear it down

```bash theme={null}
helm uninstall mysite --namespace mysite
kubectl delete namespace mysite
kind delete cluster --name frankenpress
```

## Common things to do next

<CardGroup cols={2}>
  <Card title="Your first site" icon="code-branch" href="/your-first-site">
    The full builder path: fork the template, customise locally,
    publish to your GHCR, deploy your own image.
  </Card>

  <Card title="Add a plugin" icon="puzzle-piece" href="/components/site-template#adding-plugins-and-themes">
    `composer require wpackagist-plugin/<slug>` — composer.json + lock,
    rebuild the image, redeploy.
  </Card>

  <Card title="Production topology" icon="cloud" href="/operations/production">
    Replace MariaDB / Redis / MinIO subcharts with operator-managed
    services and AWS S3.
  </Card>

  <Card title="All env vars" icon="list" href="/operations/configuration">
    Every `FP_*` and `WP_*` env var the platform reads, with defaults.
  </Card>
</CardGroup>

<Note>
  **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](https://github.com/frankenpress/charts/issues)
  with the output — we treat docs drift as a real bug.
</Note>
