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
- Create directory:
mkdir kalvin-ghost && cd kalvin-ghost - Create file:
nano docker-compose.ymland paste the config above. - Launch:
bash docker compose up -d - Verify Status:
bash docker ps -a | grep kalvin-ghost - 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:
- Stop the stack:
docker compose down - Delete the database data:
sudo rm -rf mysql-data - Restart:
docker compose up -d
Post-Installation
- Reverse Proxy: Set up a proxy pass on the host machine to forward traffic from
https://kalvin.roun.myto127.0.0.1:2368. - Setup: Navigate to
domain.com/ghostto configure the admin account.
Future Note: Adding SMTP
You do not need to reinstall Ghost to add email functionality later.
- Uncomment the
mail__settings indocker-compose.yml. - Run
docker compose up -d. - Docker will recreate only the Ghost container with the new settings; the database remains untouched.