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.mdFor 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 depsRUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates curl gnupg && \ rm -rf /var/lib/apt/lists/*
# Install ghost-cli globallyRUN npm install -g ghost-cli@latest --no-progress --no-audit --no-fund
# Copy nothing initially — ghost-cli will install GhostCOPY start.sh /usr/local/bin/start-ghost.shRUN 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 bashset -euo pipefail
# Create content dir and ensure permissionsmkdir -p /var/lib/ghost/contentchown -R node:node /var/lib/ghost
# Run as node userexec 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 localis convenient for SQLite-based local testing. For production, use MySQL andghost install --db mysqlor a manual config.gosuentry requires addinggosuto the image or switching tosu-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=productionENV 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 clientRUN 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 entrypointCOPY start.sh /usr/local/bin/start-ghost.shRUN 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 bashset -euo pipefail
# Ensure content directory existsmkdir -p /var/lib/ghost/contentchown -R node:node /var/lib/ghost
# If config.production.json isn't present, create it from environment variablescat > /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 presentif [ ! -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 ghostexec gosu node ghost run --dir /var/lib/ghostEnvironment 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 serverMYSQL_USER— MySQL userMYSQL_PASSWORD— MySQL passwordMYSQL_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.shexample.
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).
5) Environment variables and recommended settings
Essential vars:
GHOST_URL(required) — public URLNODE_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_URLif you run admin on a different host
Example SMTP env vars (Mailgun):
MAILGUN_API_KEYMAILGUN_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:
- Create and manage volumes
- Provision a MySQL database
- Add a custom domain and TLS
- Klutch.sh quick start
Deployment steps:
-
Push your repository (including
Dockerfileandstart.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/contentso 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
- 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. - 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.dbregularly. - For MySQL: use
mysqldumpor configure managed backups from your MySQL provider. - Back up
/var/lib/ghost/content/imagesand/var/lib/ghost/content/themesfrequently.
Example mysqldump command (run from a job or external host):
mysqldump -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE > ghost-backup-$(date +%F).sqlStore 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:2368andGHOST_URLis set. - Database connection errors: Confirm network connectivity, host/port, and credentials. Test using
mysqlclient from a debug container. - File-permission errors: Ensure
/var/lib/ghost/contentis writable by the user running Ghost (node user). Usechowninstart.sh.
Useful debug commands (run via a temporary shell on the container):
# Inspect logscat /var/lib/ghost/content/logs/ghost.log
# Check DB connectivitymysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASSWORD -e 'SHOW DATABASES;'11) Example: Full quickstart (copy-paste)
- Create repo with
Dockerfile(minimal from section 1) andstart.sh. - Commit and push.
- 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_URLto 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.