Ghost is an amazing open-source blogging platform.  Managed hosting is available, but if you already have a Kubernetes cluster available, why not use it?

These steps are focused on Azure Kubernetes Services (AKS), but will mostly apply to AWS EKS or Google GKE.  Additionally, this assumes that you are using NGINX Ingress Controller.  Otherwise, you will have to customize the load balancer or ingress configuration.  These instructions will use a SQLite database as opposed to the MySQL option.

Step 1: Create Namespace

apiVersion: v1
kind: Namespace
metadata:
  name: ghost

Step 2: Register Azure Files Storage Class

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: azurefile
provisioner: kubernetes.io/azure-file
mountOptions:
  - dir_mode=0777
  - file_mode=0777
  - uid=1000
  - gid=1000
  - mfsymlinks
  - nobrl
  - cache=none
parameters:
  skuName: Standard_LRS

Step 3: Configure Persistent Volume Claim

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ghost-claim
  namespace: ghost
spec:
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: azurefile
  resources:
    requests:
      storage: 50Gi

Note: Azure Files with standard performance is billed per used GB.  So you can over-provision the storage without paying extra.

Step 4: Deployment

Replace your URL in the following deployment script:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: ghost-3-2-0
  namespace: ghost
  labels:
    app: ghost
    release: 3.2.0
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ghost
      release: 3.2.0
  template:
    metadata:
      labels:
        app: ghost
        release: 3.2.0
    spec:
      volumes:
      - name: ghost-content
        persistentVolumeClaim:
          claimName: ghost-claim
      containers:
      - name: ghost-3-2-0
        image: ghost:3.2.0-alpine
        env:
        - name: url
          value: {{YOUR WEBSITE URL}}
        volumeMounts:
        - name: ghost-content
          mountPath: /var/lib/ghost/content
        resources:
          limits:
            cpu: "1"
            memory: 256Mi
          requests:
            cpu: 100m
            memory: 64Mi
        ports:
        - name: http
          containerPort: 2368
          protocol: TCP
      restartPolicy: Always

Step 5: Service

apiVersion: v1
kind: Service
metadata:
  name: ghost
  namespace: ghost
spec:
  type: ClusterIP
  selector:
    app: ghost
  ports:
  - protocol: TCP
    port: 80
    targetPort: 2368

Step 6: Ingress

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: ingress-ghost
  namespace: ghost
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/backend-protocol: HTTP
    nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: 16m
spec:
  tls:
  - hosts:
    - {{HOST NAME ex. myghostblog.com}}
    secretName: {{Kubernetes Secret name with TLS/SSL Certificate}}
  rules:
  - host: {{HOST NAME ex. myghostblog.com}}
    http:
      paths:
      - path: /
        backend:
          serviceName: ghost
          servicePort: 80

Note: this assumes you want to use TLS / SSL.  You can modify the ingress for standard HTTP and skip the TLS Secret.

Step 7: Configure DNS

Update your DNS to point to the public IP address of your Kubernetes load balancer.

Step 8: Test it out

Connect to your site.

Step 9: Login

You can access the admin portal for Ghost at https://{{host name}}/ghost.

Thanks for reading!  I'd love to hear your thoughts or questions.