Deploying a Ghost Blog on Kubernetes: A Step-by-Step Guide

In this post, I'll show you how I set up my blog using Ghost on Kubernetes. Ghost is a sleek, powerful blogging platform built for simplicity and speed, making it a favorite among bloggers and digital publications.

Deploying a Ghost Blog on Kubernetes: A Step-by-Step Guide

In this post, I'm excited to show you how I rolled out my blog using the sleek Ghost platform on Kubernetes. It's the very setup powering this post!

What is Ghost?

Ghost is a powerful blogging platform built on modern technology stack. It is designed for simplicity and speed, with a focus on content-first blogging. Its minimalistic design and powerful features make it a favorite among bloggers, journalists, and digital publications.

Preparing Your Kubernetes Secrets

In our blog deployment, we utilize Kubernetes Secrets to securely store MySQL and SMTP passwords. Secrets are pivotal for protecting sensitive information like passwords, OAuth tokens, and SSH keys. They help keep this data secure and separate from your application's codebase, offering both security enhancements and easier data management. Below, we’ll show you how to set up and manage these Secrets using console commands:

Step 1: Create the Secret

You can create a secret using the kubectl create secret command directly. For instance, to store your database and SMTP credentials:

kubectl create secret generic ghost-app-secrets \
--from-literal=mysql-password=your_database_password \
--from-literal=smtp-password=your_smtp_password

This command creates a secret named ghost-app-secrets with two entries: password and smtp-password.

Preparing Your Kubernetes Manifest

The Kubernetes manifest file defines how your Ghost application will run and be managed within a Kubernetes cluster. Here’s a breakdown of the key components of the manifest file you will need:

Deployment Configuration

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ghost-app
  labels:
    app: ghost-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ghost-app
  template:
    metadata:
      labels:
        app: ghost-app
    spec:
      containers:
      - name: ghost-app
        image: ghost:5.72.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 2368

This configuration sets up a deployment named ghost-app. It specifies a single replica of the Ghost container using the image ghost:5.72.0 and exposes it on port 2368.

Persistent Storage

        volumeMounts:
        - name: nfs-ghost-app-content
          mountPath: /var/lib/ghost/content

Persistent volumes are essential for a stateful application like Ghost to ensure that your data is saved across pod restarts. In this example, an NFS server is used to mount the content directory of Ghost.

Environment Configuration

        env:
            - name: database__client
              value: mysql
            - name: database__connection__host
              value: 192.168.1.8
            - name: database__connection__user
              value: user
            - name: database__connection__password
              valueFrom:
                secretKeyRef:
                  name: ghost-app-secrets
                  key: mysql-password
            - name: database__connection__database
              value: ghost
            - name: useNullAsDefault
              value: "true"
            - name: debug
              value: "false"
            - name: url
              value: https://myblog.com/
            - name: mail__transport
              value: SMTP
            - name: mail__from
              value: [email protected]
            - name: mail__options__service
              value: SMTP
            - name: mail__options__host
              value: smtp.sendgrid.net
            - name: mail__options__port
              value: "587"
            - name: mail__options__auth__user
              value: apikey
            - name: mail__options__auth__pass
              valueFrom:
                secretKeyRef:
                  name: ghost-app-secrets
                  key: smtp-password

Environment variables are used to configure the Ghost instance, specifying database settings and the URL where the blog will be hosted. The mail__transport configuration allows Ghost to send emails through SMTP, crucial for blog notifications and user interactions.

Once you have your manifest prepared:

  1. Apply the manifest: Use kubectl apply -f your-manifest.yaml to create the resources on your Kubernetes cluster.
  2. Verify deployment: Ensure that the pod is up and running without errors using kubectl get pods.
  3. Access your blog: Once everything is set up, you can access your Ghost blog via the URL defined in the environment variables.

Handling External Traffic with MetalLB in a LAN Environment

Here’s how MetalLB is configured to facilitate access to your Ghost deployment:

Configuring the Load Balancer

apiVersion: v1
kind: Service
metadata:
  name: ghost-app-lb
spec:
  selector:
    app: ghost-app
  ports:
  - port: 80
    targetPort: 2368
    name: http
    protocol: TCP
  type: LoadBalancer
  loadBalancerIP: 192.168.1.9

Key Features of the Load Balancer:

  • Selector: This specifies which pods receive traffic by matching the labels. Here, it's set to target pods with the label app: ghost-app.
  • Ports: Defines the port mapping. External traffic on port 80 is routed to port 2368 on the pod running Ghost.
  • Load Balancer IP: This is the specific IP address assigned to the load balancer. In this case, 192.168.1.9 is used, which would typically be an internally routable IP in a private cloud or on-premise data center.

Deploying the Load Balancer

Just like deploying the Ghost app, you deploy the load balancer service using kubectl:

kubectl apply -f your-load-balancer-manifest.yaml

Final Manifest File

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ghost-app
  labels:
    app: ghost-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ghost-app
  template:
    metadata:
      labels:
        app: ghost-app
    spec:
      containers:
        - name: ghost-app
          image: ghost:5.72.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 2368
          volumeMounts:
            - name: nfs-ghost-app-content
              mountPath: /var/lib/ghost/content
          env:
              - name: database__client
                value: mysql
              - name: database__connection__host
                value: 192.168.1.8
              - name: database__connection__user
                value: tim
              - name: database__connection__password
                valueFrom:
                  secretKeyRef:
                    name: ghost-app-secrets
                    key: mysql-password
              - name: database__connection__database
                value: ghost
              - name: useNullAsDefault
                value: "true"
              - name: debug
                value: "false"
              - name: url
                value: https://myblog.com/
              - name: mail__transport
                value: SMTP
              - name: mail__from
                value: [email protected]
              - name: mail__options__service
                value: SMTP
              - name: mail__options__host
                value: smtp.sendgrid.net
              - name: mail__options__port
                value: "587"
              - name: mail__options__auth__user
                value: apikey
              - name: mail__options__auth__pass
                valueFrom:
                  secretKeyRef:
                    name: ghost-app-secrets
                    key: smtp-password
      volumes:
        - name: nfs-ghost-app-content
          nfs:
            server: 192.168.1.10
            path: /mnt/ext1/docker/ghost-app-content
            readOnly: no

Conclusion

Deploying Ghost on Kubernetes not only leverages the strengths of a containerized environment but also ensures that your blog can scale and recover from failures more effectively. With Kubernetes, you can focus more on creating content and less on managing servers.