CMS

Set up Ghost as a Headless CMS

Step-by-step guide to set up Ghost on a dataforest Seed as a headless CMS. Two paths, Coolify or Docker Compose with MySQL.

AuthorMarvin Strauch
PublishedMay 16, 2026
min read~18 min
Words3.000
Difficulty Beginner
StackGhost · Headless CMS · Docker · Coolify · MySQL

Why Ghost as a headless CMS?

Ghost is an open-source publishing platform with over 52,000 GitHub stars. The focus is on writing: a clean editor, membership management, newsletter delivery and a native Content API. Ghost is well suited as a headless CMS for blogs, magazines, documentation and content marketing.

Unlike a pure API-first CMS like Strapi, Ghost comes with its own frontend. In headless mode, this is disabled and the built-in Content API is used instead to deliver content to a custom frontend (Nuxt, Next.js, Astro). Editors continue working in the familiar Ghost editor, developers have full control over the presentation.

This guide describes two installation paths:

  • Path A: Via Coolify (recommended for beginners): one-click installation, automatic SSL
  • Path B: Via Docker Compose (for more control): manual configuration, full flexibility

Ghost Docker Architecture
Ghost Docker Architecture

Prerequisites

  • A Seed on the dataforest Cloud (recommended: 2 CPU, 4 GB RAM; 1 CPU / 2 GB sufficient for getting started). Ghost is resource-efficient and runs smoothly even on small models.
  • SSH access to the Seed
  • A custom domain (e.g. blog.your-company.com) pointing to your Seed's IP address via DNS A record. Set the A record at your domain provider: enter the Seed's IP address as the target for your desired subdomain. It may take up to an hour for the entry to propagate globally.
  • For Path A: Coolify installed on the Seed (see Coolify Guide)

Path A: Installation via Coolify

Coolify offers Ghost as a one-click service. If you already use Coolify, this is the fastest path.

1. Create Ghost service

Open the Coolify dashboard and navigate to your project. Click New Resource and select Service. Search for Ghost in the service list and select it.

2. Configure domain

In the service settings, enter your domain (e.g. blog.your-company.com). Coolify automatically configures a Let's Encrypt certificate for HTTPS.

3. Check environment variables

Coolify sets up MySQL as the database automatically. Check the following values in the environment variables:

  • url: Must contain your full URL (e.g. https://blog.your-company.com). Without this variable, Ghost generates broken links.
  • database__connection__password: Database password (change the default value)

4. Start service

Click Deploy. Coolify starts Ghost and MySQL, configures SSL and sets up the database. After a few minutes, your CMS is reachable at your domain.

Path B: Installation via Docker Compose

This path offers more control over the configuration and is suitable if you don't use Coolify.

1. Install Docker

Connect to your Seed via SSH and install Docker. The official installation script detects your operating system automatically and sets up Docker including Docker Compose:

bash
curl -fsSL https://get.docker.com | sh

2. Create project directory

All configuration files for Ghost are placed in a shared directory:

bash
mkdir -p /opt/ghost && cd /opt/ghost

3. Create Caddyfile

Caddy is used as the reverse proxy. Caddy requires only a few lines of configuration and automatically obtains a Let's Encrypt certificate for HTTPS.

Create the file Caddyfile:

text
blog.your-company.com {
    reverse_proxy ghost:2368
}

Replace blog.your-company.com with your domain.

4. Create Docker Compose file

The docker-compose.yml describes all three containers and how they work together. Ghost provides an official Docker image, so no custom build is needed:

yaml
services:
  caddy:
    image: caddy:2-alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - ghost

  ghost:
    image: ghost:5-alpine
    restart: always
    volumes:
      - ghost_content:/var/lib/ghost/content
    environment:
      - url=https://blog.your-company.com
      - database__client=mysql
      - database__connection__host=db
      - database__connection__port=3306
      - database__connection__user=ghost
      - database__connection__password=SECURE_DB_PASSWORD
      - database__connection__database=ghost
      - NODE_ENV=production
    depends_on:
      - db

  db:
    image: mysql:8.0
    restart: always
    volumes:
      - db_data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=ROOT_PASSWORD_HERE
      - MYSQL_DATABASE=ghost
      - MYSQL_USER=ghost
      - MYSQL_PASSWORD=SECURE_DB_PASSWORD

volumes:
  ghost_content:
  db_data:
  caddy_data:
  caddy_config:

Replace SECURE_DB_PASSWORD (identical in both places), ROOT_PASSWORD_HERE and blog.your-company.com with your own values. Generate secure passwords with:

bash
openssl rand -base64 32

Key configuration notes:

  • restart: always automatically restarts each container if it crashes or the server reboots
  • depends_on sets the startup order: database before Ghost, Ghost before Caddy
  • volumes store data persistently outside the containers. Without volumes, all content is lost on restart

Environment variable overview:

  • url is the most important variable. Ghost uses it for internal links, the sitemap, RSS feeds and the API base URL. It must include the protocol (https://) and the exact domain.
  • database__client=mysql configures MySQL as the database (Ghost also supports SQLite, but MySQL is recommended for production)
  • NODE_ENV=production enables caching and performance optimizations

5. Start everything

bash
docker compose up -d

The -d option (detached) starts the containers in the background. The first start takes a few minutes. Docker pulls the images, MySQL initializes the database and Ghost runs migrations.

6. Check that everything is running

bash
docker compose ps

All three containers (caddy, ghost, db) should show status running. If a container fails to start:

bash
docker compose logs ghost

Ghost is ready when Ghost booted appears in the log.

Then open https://blog.your-company.com in your browser.

Create admin account

Navigate to https://blog.your-company.com/ghost (the admin panel). On first visit, Ghost guides you through a setup wizard:

  1. Enter site title and description
  2. Create admin account (name, email, password)
  3. Optional: invite team members

After setup, you land in the Ghost dashboard.

Activate Content API

By default, Ghost delivers content through its own frontend (theme). To use Ghost as a headless CMS, the Content API is enabled via a Custom Integration:

  1. Navigate to Settings > Integrations
  2. Click Add custom integration
  3. Enter a name (e.g. "My Frontend")
  4. Ghost generates a Content API Key and an Admin API Key

The Content API Key is used for public read access to content. The Admin API Key allows write access and should only be used server-side.

Test the Content API

Create a test article in the Ghost editor (admin panel under Posts > New post). Write a title and some text, then click Publish.

Test the API with the Content API Key from the previous step:

bash
curl "https://blog.your-company.com/ghost/api/content/posts/?key=YOUR_CONTENT_API_KEY"

The response contains your posts as JSON:

json
{
  "posts": [
    {
      "id": "...",
      "title": "My first post",
      "slug": "my-first-post",
      "html": "<p>...</p>",
      "excerpt": "...",
      "published_at": "2026-05-16T10:00:00.000Z"
    }
  ]
}

Available Content API endpoints:

  • /ghost/api/content/posts/ (articles)
  • /ghost/api/content/pages/ (pages)
  • /ghost/api/content/tags/ (tags)
  • /ghost/api/content/authors/ (authors)
  • /ghost/api/content/settings/ (site settings)

Headless mode: connecting your own frontend

To use Ghost purely as a data source with a custom frontend, there are two approaches:

Static Site Generation (SSG): A framework like Nuxt or Astro fetches all content via Content API during each build and generates static HTML pages. Advantage: maximum performance, CDN-ready. Disadvantage: new content requires a rebuild.

Server-Side Rendering (SSR): The frontend fetches content via API on each request. Advantage: content is visible immediately after publication. Disadvantage: the frontend must run as a server.

In both cases, the built-in Ghost theme is not used. It can be replaced with an empty theme or disabled via route redirect configuration.

Set up backups

Database backup

With Path A (Coolify), you can configure automatic database backups directly in the Coolify dashboard.

With Path B, create a regular backup of the MySQL database. First create the backup directory:

bash
mkdir -p /opt/backups

A single backup can be created manually at any time:

bash
docker exec ghost-db-1 mysqldump -u ghost -pSECURE_DB_PASSWORD ghost > /opt/backups/ghost-db-$(date +%Y%m%d).sql

To run the backup automatically, set up a cronjob. A cronjob is a time-scheduled command that the operating system executes regularly:

bash
crontab -e

Add the following line:

text
0 3 * * * docker exec ghost-db-1 mysqldump -u ghost -pSECURE_DB_PASSWORD ghost > /opt/backups/ghost-db-$(date +\%Y\%m\%d).sql

The five values 0 3 * * * mean: minute 0, hour 3, every day, every month, every weekday. The backup runs daily at 03:00 AM.

Back up content directory

Ghost stores uploaded images and themes in the ghost_content volume. Find the storage location with:

bash
docker volume inspect ghost_ghost_content --format '{{ .Mountpoint }}'

Additionally, Ghost offers a complete JSON export of all content in the admin panel under Settings > Labs.

Server backup via dataforest Cloud

The dataforest Cloud offers automatic daily offsite backups as an add-on option. This allows you to back up all data on your Seed and restore it at any time. Backups are not active by default and must be enabled in the cloud console.

Summary

After completing this guide, Ghost runs as a headless CMS on your own server, accessible via HTTPS and secured with automatic backups. Articles, pages and media can be delivered to any frontend via the Content API.

If you need structured content with custom content types (products, events, custom data structures), the Strapi guide provides a suitable alternative. More possibilities for your Seed can be found in our solutions and guides.

Ready to get started?

Create your first Seed and start deploying in minutes.

Back to overview