Skip to content

Deploying a Ghost blog

Introduction

This guide shows you how to deploy Ghost (the popular open-source blogging platform) on Klutch.sh using a simple Dockerfile. Whether you’re learning how to deploy Ghost for the first time or want a reliable production setup for how to host a Ghost blog, this step-by-step walkthrough covers development quickstarts, production-ready MySQL configurations, persistent storage, custom domains, SSL, backups, and troubleshooting.


Prerequisites

  • A Klutch.sh account and access to the dashboard
  • A Git repository with your Ghost project or a basic Dockerfile
  • (Optional, recommended for production) Managed MySQL instance or Klutch.sh MySQL service
  • Basic knowledge of Docker, environment variables, and domains

Project layout

A minimal repo layout for deploying Ghost:

ghost-docker/
├─ Dockerfile
├─ start.sh
├─ config.production.json (optional)
└─ README.md

For quick tests you don’t need config.production.json — Ghost will read env vars.


1) Minimal Dockerfile (quickstart — SQLite)

Use this Dockerfile for development and quick testing. It uses Ghost’s official distribution and runs with SQLite (no external DB required):

FROM node:20-bullseye-slim
ENV GHOST_VERSION=6.0.0
WORKDIR /var/lib/ghost
# Install basic deps
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl gnupg && \
rm -rf /var/lib/apt/lists/*
# Install ghost-cli globally
RUN npm install -g ghost-cli@latest --no-progress --no-audit --no-fund
# Copy nothing initially — ghost-cli will install Ghost
COPY start.sh /usr/local/bin/start-ghost.sh
RUN chmod +x /usr/local/bin/start-ghost.sh
EXPOSE 2368
CMD ["/usr/local/bin/start-ghost.sh"]

And a start.sh that bootstraps Ghost using ghost-cli (idempotent):

#!/usr/bin/env bash
set -euo pipefail
# Create content dir and ensure permissions
mkdir -p /var/lib/ghost/content
chown -R node:node /var/lib/ghost
# Run as node user
exec gosu node bash -lc "if [ ! -d /var/lib/ghost/content/apps ]; then ghost install local --no-prompt --no-setup; fi && ghost run"

Notes:

  • ghost install local is convenient for SQLite-based local testing. For production, use MySQL and ghost install --db mysql or a manual config.
  • gosu entry requires adding gosu to the image or switching to su-exec; for the quickstart you can run as root but adjust permissions accordingly.

2) Production Dockerfile with MySQL

This Dockerfile creates a reproducible Ghost installation using ghost-cli and prepares the container for a production MySQL connection:

FROM node:20-bullseye-slim
ENV NODE_ENV=production
ENV GHOST_VERSION=6.0.0
WORKDIR /var/lib/ghost
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl gnupg procps && \
rm -rf /var/lib/apt/lists/*
# Install ghost-cli and mysql client
RUN npm install -g ghost-cli@latest --no-progress --no-audit --no-fund && \
apt-get update && apt-get install -y default-mysql-client --no-install-recommends && \
rm -rf /var/lib/apt/lists/*
# Copy entrypoint
COPY start.sh /usr/local/bin/start-ghost.sh
RUN chmod +x /usr/local/bin/start-ghost.sh
EXPOSE 2368
CMD ["/usr/local/bin/start-ghost.sh"]

And a production-friendly start.sh:

#!/usr/bin/env bash
set -euo pipefail
# Ensure content directory exists
mkdir -p /var/lib/ghost/content
chown -R node:node /var/lib/ghost
# If config.production.json isn't present, create it from environment variables
cat > /var/lib/ghost/config.production.json <<'JSON'
{
"url": "${GHOST_URL}",
"server": {
"port": 2368,
"host": "0.0.0.0"
},
"database": {
"client": "mysql",
"connection": {
"host": "${MYSQL_HOST}",
"user": "${MYSQL_USER}",
"password": "${MYSQL_PASSWORD}",
"database": "${MYSQL_DATABASE}"
}
},
"logging": {
"transports": ["file", "stdout"]
}
}
JSON
# Run ghost install if not present
if [ ! -f /var/lib/ghost/current/index.js ]; then
gosu node bash -lc "ghost install --db mysql --no-prompt --no-setup --dir /var/lib/ghost"
fi
# Start ghost
exec gosu node ghost run --dir /var/lib/ghost

Environment variables required for production:

  • GHOST_URL — the public URL for your blog (e.g., https://blog.example.com)
  • MYSQL_HOST — hostname or IP of MySQL server
  • MYSQL_USER — MySQL user
  • MYSQL_PASSWORD — MySQL password
  • MYSQL_DATABASE — MySQL database name

If you prefer not to use ghost install, copy a prebuilt Ghost distribution into /var/lib/ghost during build.


3) Persistent storage: volumes and content directory

Ghost stores content (images, themes, db) under /var/lib/ghost/content. Always attach a persistent Klutch.sh Volume to this path:

  • Path in container: /var/lib/ghost/content
  • Volume name (example): ghost-content

This ensures uploads, themes, and the database (if using SQLite) persist across deployments.


4) Database options: SQLite vs MySQL

  • SQLite (quickstart): Easiest for testing. Database file lives inside /var/lib/ghost/content/data/ghost.db. Use only for low-traffic or demos.
  • MySQL (production): Recommended for performance and reliability. Use a managed MySQL instance or Klutch.sh’s database offering. Configure as shown in the start.sh example.

Notes on MySQL:

  • Create dedicated user with proper permissions for the Ghost database.

  • Ensure network access between the Ghost container and MySQL host (private network or VPC recommended).


Essential vars:

  • GHOST_URL (required) — public URL
  • NODE_ENV (production)
  • DB variables as listed above for MySQL
  • PORT — Ghost listens on 2368 by default; Klutch.sh will map this to the public port

Optional:

  • SMTP settings for transactional emails (signup, password reset)
  • GHOST_ADMIN_URL if you run admin on a different host

Example SMTP env vars (Mailgun):

  • MAILGUN_API_KEY
  • MAILGUN_DOMAIN

Set these in the Klutch.sh dashboard under Environment Variables.


6) Deploying to Klutch.sh — step-by-step

Follow these concise steps to deploy Ghost on Klutch.sh. If you need a quick reference for creating volumes, databases, or adding custom domains, see the linked tutorials below.

Helpful tutorials:

Deployment steps:

  • Push your repository (including Dockerfile and start.sh) to your Git provider.

  • In the Klutch.sh dashboard, create a new App and connect your repository.

  • Set the build context and Dockerfile path if your project is not at the repo root.

  • Create and attach a Klutch.sh Volume to /var/lib/ghost/content so uploads, themes, and (for SQLite) the DB persist. See the volumes guide: Create and manage volumes

  • Configure environment variables in the Klutch.sh app settings:

    • GHOST_URL — the public URL for your blog (e.g., https://blog.example.com)
    • For MySQL: MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE (see the MySQL tutorial: Provision a MySQL database)
    • Any SMTP credentials or other app-specific variables
  • Set the app port to 2368 (Ghost default).

  • If using MySQL, provision or connect to your database now and confirm network access from the app to the DB host. See: Provision a MySQL database

  • (Optional) Choose the instance size and autoscaling options appropriate for your expected traffic.

  • Optionally add a custom domain and enable TLS for secure traffic: see Add a custom domain and TLS

  • Click Create / Deploy — Klutch.sh will build the image and start the container.

After the deployment completes, visit the public URL you set in GHOST_URL (for example, https://blog.example.com) to confirm your Ghost site is live. The initial admin setup may require creating the first user via the Ghost admin UI.


7) Custom domain

  1. In Klutch.sh, add your custom domain to the app (for example, blog.example.com). See the custom domains docs: Add a custom domain and TLS.
  2. Point your DNS A/AAAA record to the Klutch.sh provided address or add a CNAME as instructed by the dashboard.

Klutch.sh automatically manages TLS for supported domains. Ensure GHOST_URL matches the scheme and host (for example, https://blog.example.com) so Ghost generates correct links.


8) Backups and migrations

  • For SQLite: back up /var/lib/ghost/content/data/ghost.db regularly.
  • For MySQL: use mysqldump or configure managed backups from your MySQL provider.
  • Back up /var/lib/ghost/content/images and /var/lib/ghost/content/themes frequently.

Example mysqldump command (run from a job or external host):

Terminal window
mysqldump -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE > ghost-backup-$(date +%F).sql

Store backups in object storage or on another volume.


9) Monitoring and scaling

  • Monitor CPU, memory, and disk usage for the Ghost app.
  • Scale instances vertically (bigger machine) or horizontally (multiple replicas behind a load balancer) depending on traffic and session persistence. Use external storage for session and uploaded content.

10) Troubleshooting

  • 502 / 503 errors: Check container logs and ensure the app is listening on 0.0.0.0:2368 and GHOST_URL is set.
  • Database connection errors: Confirm network connectivity, host/port, and credentials. Test using mysql client from a debug container.
  • File-permission errors: Ensure /var/lib/ghost/content is writable by the user running Ghost (node user). Use chown in start.sh.

Useful debug commands (run via a temporary shell on the container):

Terminal window
# Inspect logs
cat /var/lib/ghost/content/logs/ghost.log
# Check DB connectivity
mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD -e 'SHOW DATABASES;'

11) Example: Full quickstart (copy-paste)

  1. Create repo with Dockerfile (minimal from section 1) and start.sh.
  2. Commit and push.
  3. In Klutch.sh, create app, attach volume, set GHOST_URL, and deploy.

This will produce a running Ghost site using SQLite. For production, repeat with the MySQL configuration and required env vars.


12) Security best practices

  • Always use HTTPS and set GHOST_URL to the HTTPS URL.
  • Keep the Node and Ghost versions up to date.
  • Rotate DB passwords and use principle of least privilege for the DB user.
  • Limit admin access and use 2FA on the Ghost admin account.

Resources