Ghost CMS in Kubernetes
Let's get right down to business: Can Ghost CMS run in Kubernetes? Yes! This site is proof. It's running on my personal cluster in Digital Ocean (previously Azure Kubernetes Service, but Digital Ocean costs about half the price).
How to do it
It's relatively simple. Once you get things running in a container (which is extremely simple), you can move on to defining your Kubernetes manifests. Here's my Dockerfile:
FROM ghost:3.40.2-alpine
COPY content content
COPY config.production.json .
COPY scripts/addAppInsights.sh .
This simply copies all of my relevant content and configuration into the container. Now here's my Kubernetes manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: personal-ghost
namespace: ghost
labels:
app: personal-ghost
spec:
replicas: 1
selector:
matchLabels:
app: personal-ghost
template:
metadata:
labels:
app: personal-ghost
spec:
containers:
- name: personal-ghost
image: docker.pkg.github.com/justin-vanwinkle/ghost-website/ghost:latest
imagePullPolicy: Always
ports:
- containerPort: 2368
readinessProbe:
httpGet:
scheme: HTTP
path: /
port: 2368
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 60
periodSeconds: 5
resources:
requests:
memory: "300Mi"
cpu: "300m"
limits:
memory: "600Mi"
cpu: "600m"
env:
- name: NODE_ENV
value: production
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: personal-website
key: storage-connectionString
- name: database__connection__password
valueFrom:
secretKeyRef:
name: personal-website
key: db-password
volumeMounts:
- mountPath: /var/lib/ghost/content/images
name: images-volume
volumes:
- name: images-volume
persistentVolumeClaim:
claimName: pvc-ghost-personal
restartPolicy: Always
imagePullSecrets:
- name: github-registry-credentials
There's a lot there, but the relevant parts are the image that I use, the environment variables that are used to pass sensitive information into the container, and the volume/volume mount that is used to store data that I wish to persist (i.e. directories that receive uploaded content).
Beyond that, it's just a matter of defining your service and ingress. If you need some guidance on those, take a look at my full manifest. And don't be afraid to ask questions in the comments. I'd love to help!
Feel free to take a look at the repository for my Ghost site. While I don't recommend forking my site as the base of your own, it can be a great starting point and a nice place to copy/paste some things to speed up the process. If you see anything that serves you, feel free to copy it as your own!
Pitfalls
Ghost is architected to only have a single instance running per site. No clustering, sharding, nor any other multi-server setups. This drastically reduces possibilities for safe rollouts to ensure zero downtime.
As a side note, don't bother attempting to use Digital Ocean for your MySQL database. Ghost has tables without primary keys, but Digital Ocean does not allow you to disable the MySQL setting for this.