Skip to main content

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 a durable content volume, an HTTPS public site on the auto-assigned canonical endpoint, optional SMTP email, and optional scheduled database backups to object storage.
This template deploys into an existing GVC that you already have — it does not create one.

What Gets Created

Prerequisites

Two database secrets must exist before you install; the chart never creates them. They hold credentials, so they are prerequisite secrets rather than values — a value would sit in plaintext in the Helm release for the life of the install. Secrets are org-level, so no GVC flag is involved.
1

Database credentials

A dictionary secret with keys username, password and database. MySQL is initialized with it on first boot, and Ghost connects with the same keys:
Set mysql.credentialsSecretName to this name.
2

MySQL root password

An opaque secret holding the MySQL root password, kept separate so Ghost never reads it:
Set mysql.rootPasswordSecretName to this name. The two names must differ.
If either database secret is missing, the install reports success and then waits silently: cpln logs shows nothing, and the missing secret is named only in status.versions[].message of cpln workload get-deployments RELEASE_NAME-mysql --gvc GVC_NAME -o yaml. The workload recovers on its own once the secret exists, or immediately after cpln workload force-redeployment RELEASE_NAME-mysql --gvc GVC_NAME.
Two optional features need setup before you install:
1

SMTP credentials (optional)

Member sign-in links, staff invitations and newsletters are sent over SMTP, and SMTP is off until mail.secretName names a dictionary secret holding exactly the keys user and password. Secrets are org-level, so no GVC flag is involved:
Set mail.secretName to this name, together with mail.host and mail.from — see Mail (SMTP).
2

Backup bucket and Cloud Account (optional)

Scheduled database dumps need an S3 or GCS bucket plus a Control Plane Cloud Account. See Backing Up.
Create the SMTP secret before installing with mail.secretName set. A name that points at a secret which does not exist installs “successfully” and then wedges silently: cpln logs shows nothing, and the only place the missing secret is named is status.versions[].message in cpln workload get-deployments RELEASE_NAME-ghost --gvc GVC_NAME -o yaml. The workload recovers on its own once the secret exists, or immediately after cpln workload force-redeployment RELEASE_NAME-ghost --gvc GVC_NAME.

Installation

A default install needs only the names of the two database secrets:
Or install using your preferred method:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Configuration

Ghost Application

The resource block exposes both a floor and a ceiling, so the ceiling is named maxCpu / maxMemory — the bare cpu / memory names from template version 1.0.0 are refused at render (see Upgrading from 1.0.0). Media, themes and logs live on the content volume set; posts, members and settings live in MySQL.

Site URL

Ghost bakes its url into every page link, feed and email, and rejects admin requests whose origin does not match it, so it must be the address visitors actually use. The template sets it at container start through the RELEASE_NAME-ghost-start script, in this order: Once you attach a custom domain to the RELEASE_NAME-ghost workload, set publicUrl to it (for example https://blog.example.com) and upgrade the release. The new value lands in the startup-script secret, not in the workload spec, so the running container keeps the old URL until it restarts — force a redeployment after the upgrade:

Mail (SMTP)

  • mail.secretName — Name of the prerequisite dictionary secret (keys user, password) created in Prerequisites. Empty keeps email fully off; set, it turns on Ghost’s SMTP transport and grants the Ghost identity reveal on exactly this secret. mail.host and mail.from are then required — the chart refuses to render without them.
  • mail.port / mail.secure — 587 with secure: false for STARTTLS (default), or 465 with secure: true for implicit TLS.
  • mail.from — The From address for outgoing mail, for example "Ghost <noreply@example.com>".
  • staffDeviceVerification — Ghost 6 emails staff a 6-digit code on every admin sign-in from a new device. It defaults to false here so /ghost stays reachable on a mail-less install; the chart refuses true while mail.secretName is empty. Set it to true once SMTP works.
The SMTP credentials are read when the container starts. After changing the secret’s value, force a redeployment of RELEASE_NAME-ghost so Ghost picks up the new credentials.

Access

  • publicAccess.enabled — Serve the site and the /ghost admin over HTTPS on the auto-assigned *.cpln.app canonical endpoint (default). Set to false for an internal-only instance: Ghost then runs with the internal cpln.local URL from the Site URL table and is reachable only per internalAccess.
  • internalAccess.type — Which workloads can reach Ghost on port 2368 over the internal network:

Backing Database

  • mysql.image — Pinned to mysql:8. Ghost supports only MySQL 8 — not MySQL 9 and not MariaDB. Do not change it.
  • mysql.credentialsSecretName / mysql.rootPasswordSecretName — The names of your two prerequisite database secrets (see Prerequisites): a dictionary with username, password and database, and an opaque secret with the root password. MySQL applies them on first boot; Ghost reads only the first. Both must exist before installing and must be two different secrets.
  • mysql.resources / mysql.volumeset.capacity — CPU and memory bounds and the initial data volume size in GiB (minimum 10). The reservation-to-limit ratio is tuned to the platform’s cap for stateful workloads; keep it when you raise the limits.
  • mysql.internalAccess.type — Which workloads can reach MySQL directly on port 3306, using the same four types as Ghost’s internalAccess.
  • mysql.backup.* — Scheduled dumps to S3 or GCS, off by default. See Backing Up for the bucket, Cloud Account and IAM setup.
MySQL applies the database secrets only on first startup, when the data directory is empty. Editing a secret after the initial deployment does not change the running database — change the password inside MySQL with ALTER USER first, then update the secret’s password key (cpln secret edit SECRET_NAME) and force a redeployment of RELEASE_NAME-mysql and RELEASE_NAME-ghost (a secret change is not picked up until the workloads redeploy). To fully reset, cpln helm uninstall RELEASE_NAME --gvc GVC_NAME (which deletes both volume sets and all data) and reinstall, or install under a new release name.

Connecting

Verify the site is serving once the workload reports ready — the default theme answers 200 before any owner exists:
With publicAccess.enabled: false there is no canonical endpoint; reach port 2368 from another workload in the GVC, or from your machine with cpln port-forward RELEASE_NAME-ghost 2368:2368 --gvc GVC_NAME. Driving the /ghost admin through that tunnel has not been verified for this template — Ghost’s url is then the internal cpln.local address, and its admin checks requests against it.

First Run

Ghost ships no bootstrap credentials. The first visitor to /ghost creates the owner account.
1

Wait for both workloads to report ready

MySQL initializes first; Ghost runs its database migrations and starts serving. Ghost retries its database connection until MySQL is up, so early connection errors in its log during a first install are expected.
2

Open the admin and create the owner

Browse to https://CANONICAL_ENDPOINT/ghost and complete the setup form — site title, your name, email and password. Do this right after install: until the owner exists the site is public with the default theme and anyone who reaches /ghost can claim it.
3

Configure mail before inviting anyone

Staff invitations, member sign-in links and newsletters are delivered only by email. Configure Mail (SMTP) first, then set staffDeviceVerification: true if you want the emailed sign-in code on new devices.

Operations

Backing Up

Two things hold your data: the MySQL database (posts, pages, members, settings, staff accounts) and the content volume set (uploaded images, themes). A database dump does not include media. Scheduled database dumps are off by default. When mysql.backup.enabled: true, the RELEASE_NAME-mysql-backup cron workload runs mysqldump --databases of the database named in the database key of your mysql.credentialsSecretName secret on mysql.backup.schedule and uploads a gzipped archive named mysql-TIMESTAMP.sql.gz under mysql.backup.*.prefix in your bucket. Pick a mysql.backup.provider and complete the storage setup before enabling it:
1

Create a bucket

Create an S3 bucket. Set mysql.backup.aws.bucket and mysql.backup.aws.region to match.
2

Set up a Cloud Account

If you do not have one, create a Cloud Account for your AWS account. Set mysql.backup.aws.cloudAccountName to its name.
3

Create a bucket-scoped IAM policy

Create an IAM policy granting the required S3 actions on the bucket (replace YOUR_BUCKET_NAME), and set mysql.backup.aws.policyName to its name. The template attaches it to the MySQL identity:
Content volume. The RELEASE_NAME-ghost-vs volume set keeps a final snapshot for 7 days when it is deleted. Take a snapshot before a theme change or an upgrade:
Ad-hoc dump without a bucket. mysqldump is in the mysql:8 image. Binary output through cpln workload exec is not byte-safe, so wrap the stream in base64 on both sides; the root password is already in the container’s environment:
This ad-hoc command has not been run against a live install of this template — try it on a throwaway release before relying on it.

Restoring a Backup

This restore procedure was written against the chart’s configuration and the backup image’s dump format. It has not been exercised against a live install of this template — rehearse it on a throwaway release before you need it, and treat any step that errors as a stop.
Scheduled dumps are gzipped mysqldump --databases output of the database named in the database key of your mysql.credentialsSecretName secret, created with --set-gtid-purged=OFF, so the archive recreates the database itself (CREATE DATABASE and USE are embedded) and carries no GTID statement to strip. Download the archive from your bucket (aws s3 cp or gsutil cp), then stream it into the running MySQL through cpln workload exec. Stdin is off by default on exec, so pass --stdin, and base64-wrap the stream so nothing is altered in transit:
Then restart Ghost so it drops its in-memory settings and reads the restored database:
The dump restores posts, members, settings and staff accounts. Uploaded images and themes are on the content volume set, not in the database — restore those from a volume set snapshot (cpln volumeset snapshot restore has not been exercised for this template; check cpln volumeset snapshot restore --help) or re-upload them.

Upgrading from 1.0.0

Template version 1.1.0 renamed the Ghost resource ceiling from cpu / memory to maxCpu / maxMemory, because the block also exposes minCpu / minMemory and the bare names left it ambiguous which number was the limit. Rename the two keys in your values before upgrading:
An upgrade that still carries the old names is refused at render and your running release is left untouched:
Blocks that expose only a limit (mysql.backup.resources) keep the bare cpu / memory names.

Upgrading from 1.1.0

Template version 1.2.0 adds staffDeviceVerification (default false), which turns off the emailed sign-in code Ghost 6 requires on every new-device admin login. On a 1.1.0 install without SMTP that code could never be delivered and /ghost was unreachable from a new browser; after the upgrade admin login works without mail. If you have SMTP configured and want the check back, set staffDeviceVerification: true. The first cpln helm upgrade after an install can restart the bundled MySQL even when no database value changed, so run it during a quiet period.

Upgrading from 1.2.0

Template version 1.3.0 removed mysql.config (database name, user, password and root password): the database credentials are now the two prerequisite secrets under Prerequisites, the same ones the mysql template requires on its own. An upgrade that still carries mysql.config is refused at render, before anything changes; mysql.enablePhpMyAdmin is refused the same way, so remove it too. 1.2.0 created the secret RELEASE_NAME-mysql-config itself, and this upgrade deletes it. Create the new secrets under different names, holding the values your database was initialized with (not new ones, or Ghost and MySQL lose their login). Read the current values first:
Then create the two secrets from them (user becomes username; rootPassword gets its own secret). Use your current user and database names too, not ghost, if you changed them:
Upgrade with mysql.credentialsSecretName and mysql.rootPasswordSecretName set to those names, and mysql.config and mysql.enablePhpMyAdmin removed. The upgrade restarts MySQL and Ghost, so the site is briefly unavailable. The new version of a workload can wait for several minutes, with get-deployments reporting that it is not allowed to reveal the secret, while access to the new secrets propagates; the previous version keeps serving meanwhile, and it clears by itself.

Scaling and Availability

Ghost is single-replica by design and this template has no replicas knob: Ghost holds scheduler, job-queue and cache state in-process and has no upstream support for running more than one instance against the same database. Both workloads are pinned to one replica. Durability comes from the two volume sets and, optionally, scheduled database dumps. A restart or upgrade is a brief interruption of the one replica; put a CDN in front of a busy public site to absorb it. Raise resources.maxCpu / resources.maxMemory (and the mysql.resources counterparts, keeping the reservation ratio) to scale vertically.

Troubleshooting

Symptom: cpln helm upgrade from template version 1.0.0 stops at render with ghost: resources.cpu was RENAMED to resources.maxCpu (or the resources.memory equivalent). Nothing is changed on the platform.Cause: Your values still use the 1.0.0 key names.Fix: Rename resources.cpu to resources.maxCpu and resources.memory to resources.maxMemory, then upgrade again. See Upgrading from 1.0.0.
Symptom: Install or upgrade fails with ghost: staffDeviceVerification requires SMTP — set mail.secretName (Ghost emails the 2FA code) or leave staffDeviceVerification: false.Cause: staffDeviceVerification: true with mail.secretName empty. Ghost would email a code it has no way to send, and admin login would never complete.Fix: Configure Mail (SMTP) first, or set staffDeviceVerification: false.
Symptom: Signing in at /ghost from a new browser prompts for a 6-digit code and no email arrives; the browser shows a server error on submit.Cause: You are on template version 1.1.0 or earlier without SMTP, or on 1.2.0 with staffDeviceVerification: true and a mail configuration that does not deliver.Fix: Upgrade to 1.2.0 (the default turns the check off) or fix the SMTP settings and credentials in mail.*; after changing the secret’s value, run cpln workload force-redeployment RELEASE_NAME-ghost --gvc GVC_NAME.
Symptom: RELEASE_NAME-ghost stays not ready after install with mail.secretName set, and cpln logs returns no lines for it.Cause: The SMTP secret named in mail.secretName does not exist, so the container never starts.Fix: Read status.versions[].message in cpln workload get-deployments RELEASE_NAME-ghost --gvc GVC_NAME -o yaml — it names the missing secret. Create it as shown in Prerequisites; the workload recovers on its own, or immediately after cpln workload force-redeployment RELEASE_NAME-ghost --gvc GVC_NAME.
Symptom: After editing the password key of your database credentials secret and redeploying, Ghost fails to authenticate with Access denied for user.Cause: MySQL applies the database secrets only when the data directory is first initialized. Ghost now sends a password the database never learned.Fix: Either change the password inside MySQL to match the new value (ALTER USER 'ghost'@'%' IDENTIFIED BY '...' via cpln workload exec RELEASE_NAME-mysql --gvc GVC_NAME --container mysql -- mysql -u root -p ...), or put the old value back in the secret and force a redeployment of RELEASE_NAME-ghost. A clean reset is uninstall (deletes both volume sets) and reinstall.
Symptom: RELEASE_NAME-mysql stays not ready after install, and cpln logs returns no lines for it.Cause: A secret named by mysql.credentialsSecretName or mysql.rootPasswordSecretName does not exist, so the container never starts.Fix: Read status.versions[].message in cpln workload get-deployments RELEASE_NAME-mysql --gvc GVC_NAME -o yaml — it names the missing secret. Create it as shown in Prerequisites; the workload recovers on its own, or immediately after cpln workload force-redeployment RELEASE_NAME-mysql --gvc GVC_NAME.
Symptom: cpln helm upgrade stops at render with ghost: mysql.config was removed in 1.3.0 (or mysql.enablePhpMyAdmin was removed in 1.3.0). Nothing is changed on the platform.Cause: Your values still carry the 1.2.0 plaintext database passwords.Fix: Create the two database secrets holding your current values and remove the old keys — see Upgrading from 1.2.0.

Important Notes

  • Create both database secrets before installing — a missing one wedges RELEASE_NAME-mysql silently; see Prerequisites.
  • The database secrets seed MySQL on first boot only — editing them later does not change the stored passwords: change the password inside MySQL first, then update the secret and force a redeployment of RELEASE_NAME-mysql and RELEASE_NAME-ghost.
  • Create the owner account right after install — until someone completes the form at /ghost, the public site has no admin and anyone who reaches it can claim ownership.
  • MySQL 8 only — keep mysql.image: mysql:8; Ghost does not support MySQL 9 or MariaDB.
  • SMTP is a prerequisite secret, not a value — create the dictionary secret (keys user, password) before installing with mail.secretName set; a missing secret wedges the deployment silently.
  • Set publicUrl when you attach a custom domain, then force a redeployment of RELEASE_NAME-ghost — links and emails use whatever url the container started with.
  • staffDeviceVerification: true requires working SMTP — the chart refuses it otherwise, and a mis-delivering mail setup locks staff out of /ghost from new devices.
  • Single replica by design — there is no replicas knob; expect a brief interruption on restart or upgrade, and the first upgrade after install can restart the bundled MySQL.
  • Data survives redeploys under the same release name — content in RELEASE_NAME-ghost-vs, the database in RELEASE_NAME-mysql-vs. cpln helm uninstall deletes both; a database dump does not include uploaded media.

External References

Ghost Documentation

Official Ghost product documentation

Configuration Reference

The url, mail and security options this template sets

Supported Databases

Why Ghost requires MySQL 8

Device Verification and Email 2FA

The staff sign-in code that staffDeviceVerification controls

Official Docker Image

The library/ghost image on Docker Hub

Ghost Template

View the source files, default values, and chart definition