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

# Ghost

> Deploy Ghost, the open-source publishing platform for blogs, newsletters, and paid memberships, on Control Plane. Covers the bundled MySQL 8 database, persistent content storage, public HTTPS access, SMTP email, and optional scheduled database backups.

## Overview

Ghost is the open-source publishing platform for professional blogs, newsletters, and paid memberships, with a first-class editor and REST Content/Admin APIs. This template deploys a single stateful Ghost workload backed by a bundled MySQL 8 database, with durable content storage, an HTTPS public site, optional SMTP email, and optional scheduled database backups to object storage.

### Architecture

* **Ghost** — Stateful, single-replica workload serving the site, editor, and Content/Admin APIs on port `2368`. Boots through a startup script that sets the public `url` from the canonical endpoint.
* **MySQL 8** — Backing database provisioned from the [mysql](/template-catalog/templates/mysql) template as a subchart (image pinned to `mysql:8` — the only database Ghost supports) and connected to Ghost automatically.

### What Gets Created

* **Stateful Ghost Workload** — The Ghost server with configurable CPU and memory.
* **Stateful MySQL Workload** — Single-replica MySQL 8, automatically connected to Ghost.
* **Volume Sets** — A persistent volume set for Ghost content (`/var/lib/ghost/content`: uploaded images, themes, logs, adapters) and one for MySQL data.
* **Secrets** — An opaque secret holding the startup script and the MySQL credentials secret.
* **Identity & Policy** — An identity bound to the Ghost workload with `reveal` access scoped to exactly the secrets it mounts (plus your SMTP secret when mail is enabled).
* **Cron Backup Workload** *(optional)* — Scheduled MySQL dumps to object storage, created only when `mysql.backup.enabled` is `true`.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

None for a default install — it deploys with a working MySQL 8 backend and an auto-assigned HTTPS endpoint. Two optional features need setup before installing:

* **SMTP email** — for member sign-in links and newsletters, create a dictionary secret with keys `user` and `password` first (see [Mail](#mail-smtp)).
* **Database backups** — an AWS S3 or GCS bucket plus a Control Plane Cloud Account (see [Backing Up](#backing-up)).

Install using your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

Key configuration values (see the template's `values.yaml` for the complete set):

```yaml theme={null}
image: ghost:6.54.1-alpine # official Docker Hub image (library/ghost)

resources: # Ghost (Node) app — single instance
  cpu: 500m
  memory: 1024Mi
  minCpu: 250m
  minMemory: 512Mi

# Persistent content: uploaded images, themes, logs, adapters
volumeset:
  capacity: 10 # initial capacity in GiB (minimum is 10)

# Public URL Ghost uses to build links and emails. Leave empty to auto-derive
# the canonical *.cpln.app endpoint. Set to your custom domain once attached.
publicUrl: "" # e.g. https://blog.example.com

# Mail (SMTP) — optional. DISABLED while secretName is empty (email off).
mail:
  secretName: "" # e.g. my-ghost-smtp (dictionary secret with keys: user, password)
  host: ""       # e.g. smtp.mailgun.org
  port: 587      # 465 = SSL, 587 = STARTTLS
  secure: false  # true for port 465
  from: ""       # e.g. "Ghost <noreply@example.com>"

publicAccess:
  enabled: true # HTTPS site via the canonical *.cpln.app endpoint

internalAccess:
  type: same-gvc # options: none, same-gvc, same-org, workload-list
  workloads: []  # only used when type is same-gvc or workload-list

# Backing database: MySQL 8 (bundled via the mysql template)
mysql:
  image: mysql:8 # Ghost supports ONLY MySQL 8 — do not change to 9 or MariaDB
  enablePhpMyAdmin: false
  config:
    db: ghost
    user: ghost
    password: change-me-ghost-db       # change before installing
    rootPassword: change-me-mysql-root # change before installing
  resources:
    minCpu: 150m
    maxCpu: 500m
    minMemory: 256Mi
    maxMemory: 1024Mi
  volumeset:
    capacity: 10 # initial capacity in GiB (minimum is 10)
  internalAccess:
    type: same-gvc
  backup:
    enabled: false        # scheduled DB dumps to object storage — see Backing Up
    schedule: "0 2 * * *" # daily at 2am UTC
    provider: aws         # aws or gcp
    aws:
      bucket: my-backup-bucket
      region: us-east-1
      cloudAccountName: my-backup-cloudaccount
      policyName: my-backup-policy
      prefix: ghost/backups
    gcp:
      bucket: my-backup-bucket
      cloudAccountName: my-backup-cloudaccount
      prefix: ghost/backups
```

### Ghost Application

* `image` — The official Ghost image from Docker Hub (`library/ghost`, Alpine variant).
* `resources` — CPU and memory bounds for the Ghost workload.
* `volumeset.capacity` — Initial content volume size in GiB (minimum 10). Holds uploaded images, themes, logs, and adapters at `/var/lib/ghost/content`.

### Site URL

* `publicUrl` — The public URL Ghost uses to build page links and email links. Leave empty (default) to auto-derive the canonical `*.cpln.app` endpoint at boot. Set it to your custom domain (e.g. `https://blog.example.com`) once you attach one, so links and emails point at the right host.

### Mail (SMTP)

Email powers member sign-in links, staff invitations, and newsletters. It is **off by default** — the entire mail configuration is gated on `mail.secretName` being non-empty. To enable it, create a dictionary secret with your SMTP credentials **before** installing:

```bash theme={null}
cpln secret create --name my-ghost-smtp --type dictionary \
  --data 'user=smtp-username,password=smtp-password'
```

Then set:

* `mail.secretName` — The name of the dictionary secret (keys `user` and `password`). The template grants the Ghost identity `reveal` on exactly this secret.
* `mail.host` — Your SMTP server hostname (e.g. `smtp.mailgun.org`).
* `mail.port` / `mail.secure` — `587` with `secure: false` for STARTTLS (default), or `465` with `secure: true` for SSL.
* `mail.from` — The From address for outgoing mail (e.g. `"Ghost <noreply@example.com>"`).

### Access

* `publicAccess.enabled` — Serve the site and admin over HTTPS on the auto-assigned `*.cpln.app` canonical endpoint (default). Set to `false` for an internal-only instance — Ghost then derives its URL from the internal DNS name.
* `internalAccess.type` — Controls which workloads can reach Ghost over the internal network:

| Type            | Description                                                            |
| --------------- | ---------------------------------------------------------------------- |
| `none`          | No internal access.                                                    |
| `same-gvc`      | Allow access from all workloads in the same GVC (default).             |
| `same-org`      | Allow access from all workloads in the same organization.              |
| `workload-list` | Allow access only from workloads listed in `internalAccess.workloads`. |

### Backing Database

* `mysql.image` — Pinned to `mysql:8`. Ghost supports **only MySQL 8** — not MySQL 9 and not MariaDB. Do not change this.
* `mysql.config.db` / `user` / `password` / `rootPassword` — Credentials for the bundled MySQL, applied on first startup. **Change both passwords before installing.**
* `mysql.resources` / `mysql.volumeset.capacity` — CPU/memory bounds and initial volume size for the MySQL workload.
* `mysql.internalAccess.type` — Controls which workloads can reach MySQL directly.
* `mysql.enablePhpMyAdmin` — Off by default to keep the footprint to Ghost plus its database.

<Note>
  MySQL credentials are only applied on first startup when the data directory is empty. Changing them in values after the initial deployment has no effect on the running database. To fully reset, `helm uninstall` (which deletes the volume sets) and reinstall.
</Note>

## Connecting

| Access            | Endpoint                                       | Notes                                                                                                                                                            |
| ----------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Public site       | `https://<canonical>.cpln.app`                 | Auto-assigned when `publicAccess.enabled`. Find it under `status.canonicalEndpoint` (`cpln workload get <release>-ghost -o yaml`).                               |
| Admin panel       | `https://<canonical>.cpln.app/ghost`           | The Ghost editor and settings.                                                                                                                                   |
| Owner account     | First visit to `/ghost`                        | Created via the setup wizard on first visit — there are no bootstrap credentials.                                                                                |
| Internal (in-GVC) | `http://<release>-ghost.<gvc>.cpln.local:2368` | Reachable from other workloads per `internalAccess.type`. Send an `X-Forwarded-Proto: https` header — Ghost redirects plain-HTTP requests to its configured URL. |
| Database          | `<release>-mysql.<gvc>.cpln.local:3306`        | Credentials live in the `<release>-mysql-config` secret.                                                                                                         |

After deploy, open `https://<canonical>.cpln.app/ghost` in a browser to create the owner account, then start publishing. Until the owner exists, the site serves the default theme with no admin user.

## Backing Up

Database backups are optional and disabled by default. When enabled, a cron workload runs a scheduled `mysqldump` of the Ghost database and uploads it (gzipped) to your bucket under the configured prefix. Enable with `mysql.backup.enabled: true`, pick a `mysql.backup.provider`, and complete the storage setup below **before** installing.

<Tabs>
  <Tab title="AWS S3">
    <Steps>
      <Step title="Create a bucket">
        Create an S3 bucket. Set `mysql.backup.aws.bucket` and `mysql.backup.aws.region` to match.
      </Step>

      <Step title="Set up a Cloud Account">
        If you do not have one, [create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account) for your AWS account. Set `mysql.backup.aws.cloudAccountName` to its name.
      </Step>

      <Step title="Create a bucket-scoped IAM policy">
        Create an IAM policy granting the required S3 actions on the bucket, and set `mysql.backup.aws.policyName` to its name:

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::my-backup-bucket" },
            { "Effect": "Allow", "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"], "Resource": "arn:aws:s3:::my-backup-bucket/*" }
          ]
        }
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Google Cloud Storage">
    <Steps>
      <Step title="Create a bucket">
        Create a GCS bucket. Set `mysql.backup.gcp.bucket` to its name.
      </Step>

      <Step title="Set up a Cloud Account">
        If you do not have one, [create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account) for your GCP project and grant its service account **Storage Object Admin** (`roles/storage.objectAdmin`) on the bucket. Set `mysql.backup.gcp.cloudAccountName` to its name.
      </Step>
    </Steps>
  </Tab>
</Tabs>

### Restoring a Backup

Backups are standard gzipped `mysqldump` archives named `mysql-<timestamp>.sql.gz`. To restore, download the archive from your bucket and load it into a MySQL 8 instance:

```bash theme={null}
gunzip -c mysql-<timestamp>.sql.gz | mysql -u root -p
```

The dump recreates the full `ghost` database — posts, members, settings, and users. Uploaded images and themes live on the Ghost content volume set, not in the database, so a database restore covers content and configuration but not media files.

## Important Notes

* **Single replica by design.** Ghost has no upstream clustering support, so there is no `replicas` knob. Durability comes from the MySQL backend, the content volume set, and optional scheduled backups. Put a CDN in front of a busy public site to absorb the brief blip during a restart or upgrade.
* **MySQL 8 only.** Ghost does not support MySQL 9 or MariaDB — keep `mysql.image: mysql:8`.
* **Create the owner account first.** After deploy, visit `/ghost` to run the setup wizard. Until then the site is public with the default theme and no admin exists.
* **Change both database passwords before installing.** They seed the database on first boot and cannot be changed by editing values afterward — uninstalling (which deletes the volume sets) and reinstalling is the reset path.
* **SMTP is a prerequisite secret, not a value.** Create the dictionary secret (keys `user`, `password`) before install and reference it via `mail.secretName`. Leaving it empty keeps email fully off.
* **Set `publicUrl` when using a custom domain** so page links and emails point at the right host. Empty auto-derives the canonical `*.cpln.app` endpoint.
* **Content survives redeploys** under the same release name — posts and settings in MySQL, media on the content volume set. Uninstalling deletes both volume sets and all data.

## External References

<CardGroup cols={2}>
  <Card title="Ghost Documentation" href="https://docs.ghost.org" icon="book">
    Official Ghost product documentation
  </Card>

  <Card title="Configuration Reference" href="https://docs.ghost.org/config" icon="sliders">
    Full reference of Ghost configuration options
  </Card>

  <Card title="Supported Databases" href="https://docs.ghost.org/faq/supported-databases" icon="database">
    Why Ghost requires MySQL 8
  </Card>

  <Card title="Official Docker Image" href="https://hub.docker.com/_/ghost" icon="docker">
    The library/ghost image on Docker Hub
  </Card>

  <Card title="Ghost Template" href="https://github.com/controlplane-com/templates/tree/main/ghost" icon="github">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
