• Dec. 2, 2025, 8:55 a.m.

    Context

    I run a hybrid server setup where I rely on YunoHost for ease of management but prefer to run specific applications, like Ghost CMS, inside their own isolated Docker containers. This gives me granular control over the versioning and database management without relying on pre-packaged YunoHost scripts that might be outdated.

    This guide documents the deployment of Ghost 5.x using docker compose and MySQL 8.

    Prerequisites

    • A VPS or Linux server with Docker and Docker Compose installed.
    • A domain name pointed to the server (e.g., blog.example.com).
    • A reverse proxy (Nginx, Caddy, or YunoHost's "Redirect" app) to map the internal port to the public web.

    The Configuration (docker-compose.yml)

    Below is the working configuration. I customized the service name to kalvin-ghost for easier CLI management.

    Crucial Note: Ghost 5.x requires MySQL 8.

    services:
      # Custom service name for easier log filtering
      kalvin-ghost:
        image: ghost:5-alpine
        restart: always
        ports:
          - "2368:2368"
        environment:
          # URL must match your public domain exactly (including https)
          url: https://kalvin.roun.my
    
          # Database Connection
          database__client: mysql
          database__connection__host: db
          database__connection__user: kalvin-ghost
          database__connection__password: <YOUR_SECURE_PASSWORD>
          database__connection__database: kalvin-ghost
    
          # Email (Can be added later without reinstalling)
          # mail__transport: SMTP
          # mail__options__service: Mailgun
          # ...
    
        volumes:
          - ./content:/var/lib/ghost/content
    
      db:
        image: mysql:8
        restart: always
        environment:
          MYSQL_ROOT_PASSWORD: <YOUR_ROOT_PASSWORD>
          MYSQL_DATABASE: kalvin-ghost
          MYSQL_USER: kalvin-ghost
          MYSQL_PASSWORD: <YOUR_SECURE_PASSWORD>
        volumes:
          - ./mysql-data:/var/lib/mysql
    

    Installation Steps

    1. Create directory: mkdir kalvin-ghost && cd kalvin-ghost
    2. Create file: nano docker-compose.yml and paste the config above.
    3. Launch:
      bash docker compose up -d
    4. Verify Status:
      bash docker ps -a | grep kalvin-ghost
    5. Check Logs:
      bash docker compose logs kalvin-ghost -n 48

    Troubleshooting: The "Access Denied" Loop

    During installation, I encountered an ER_ACCESS_DENIED_ERROR. This happens if the passwords in the ghost section and the db section of the YAML file do not match.

    The Trap:
    Simply fixing the password in the YAML file and restarting (docker compose up -d) does not fix the issue. This is because MySQL initializes the database user/password only on the very first run. It saves this data into the mysql-data volume. Subsequent restarts ignore the environment variables for existing users.

    The Fix:
    If you mess up the initial password setup, you must "nuke" the database volume to force a fresh creation:

    1. Stop the stack: docker compose down
    2. Delete the database data: sudo rm -rf mysql-data
    3. Restart: docker compose up -d

    Post-Installation

    1. Reverse Proxy: Set up a proxy pass on the host machine to forward traffic from https://kalvin.roun.my to 127.0.0.1:2368.
    2. Setup: Navigate to domain.com/ghost to configure the admin account.

    Future Note: Adding SMTP

    You do not need to reinstall Ghost to add email functionality later.

    1. Uncomment the mail__ settings in docker-compose.yml.
    2. Run docker compose up -d.
    3. Docker will recreate only the Ghost container with the new settings; the database remains untouched.
  • edit

    Thread title has been changed from Ghost Content Management System.

  • edit

    Thread title has been changed from Ghost CMS - Docker Compose & YunoHost Hybrid.

  • edit

    Thread title has been changed from Ghost CMS - Docker Compose.