Ghost Docker: Migration Issues — Fix
The Problem
You updated your Ghost Docker image to a newer version and Ghost won’t start. The container exits with database migration errors, crashes during startup, or shows a blank page. Common error messages include:
DatabaseError: ER_DUP_ENTRY
KnexMigrateError: Migration failed
Error: Cannot read properties of null
Ghost has shut down
The Cause
Ghost runs database migrations automatically on startup when it detects a version change. These migrations modify the database schema (adding columns, changing data types, updating indexes). Failures happen when:
- Skipping major versions — Ghost expects sequential migrations (e.g., 4.x → 5.x → 5.y, not 4.x → 5.y)
- Volume permission issues — Ghost can’t write to the content directory
- Database connection timeouts — MySQL isn’t ready when Ghost tries to migrate
- Interrupted previous migration — Container was killed mid-migration, leaving the database in a partial state
- SQLite to MySQL migration — Switching database backends during an upgrade
The Fix
Method 1: Sequential Version Upgrades
Ghost must be upgraded through each major version. You can’t skip from 4.x directly to 5.x if migrations for intermediate versions exist.
Check your current version:
docker exec ghost-app ghost version
Upgrade through each major version:
# Step 1: Upgrade to latest 4.x
image: ghost:4-alpine
# Step 2: Start, let migrations run, verify, stop
# Step 3: Upgrade to latest 5.x
image: ghost:5-alpine
# Step 4: Start, let migrations run, verify
For each step:
# Update docker-compose.yml with the next version
docker compose up -d
docker compose logs -f ghost # Watch for migration completion
# Wait for "Ghost is running" message
docker compose down
# Proceed to next version
Method 2: Fix Partial Migration State
If Ghost died mid-migration, the database may be in an inconsistent state:
# 1. Back up the database first
docker exec ghost-db mysqldump -u ghost -p ghost > ghost-backup-$(date +%Y%m%d).sql
# 2. Check the migrations table
docker exec -it ghost-db mysql -u ghost -p ghost \
-e "SELECT * FROM migrations ORDER BY id DESC LIMIT 10;"
# 3. Identify the failed migration version
docker exec -it ghost-db mysql -u ghost -p ghost \
-e "SELECT * FROM migrations WHERE currentVersion != version;"
# 4. Delete the partial migration record
docker exec -it ghost-db mysql -u ghost -p ghost \
-e "DELETE FROM migrations WHERE id = [FAILED_ID];"
# 5. Restart Ghost to retry the migration
docker compose restart ghost
Method 3: Fix Volume Permissions
Ghost runs as user node (UID 1000) inside the container. If volumes are owned by root:
# Check current permissions
docker exec ghost-app ls -la /var/lib/ghost/content/
# Fix permissions
docker exec -u root ghost-app chown -R node:node /var/lib/ghost/content/
Or set the correct ownership from the host:
sudo chown -R 1000:1000 /path/to/ghost/content
Method 4: Database Connection Timing
If Ghost starts before MySQL is ready, migrations fail with connection errors. Add a health check:
services:
ghost:
image: ghost:5-alpine
depends_on:
ghost-db:
condition: service_healthy
restart: unless-stopped
ghost-db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
Method 5: Clean Reinstall (Last Resort)
If the database is too corrupted to migrate:
# 1. Export content from the Ghost admin panel (if accessible)
# Settings → Labs → Export
# 2. Back up the database
docker exec ghost-db mysqldump -u ghost -p ghost > ghost-full-backup.sql
# 3. Remove the existing database
docker compose down
docker volume rm ghost_db_data
# 4. Start fresh with the target version
docker compose up -d
# 5. Import content via Ghost admin
# Settings → Labs → Import
Prevention
- Always back up before upgrading. Run
mysqldumpor snapshot your volume before changing the Ghost image tag. - Never use
:latest. Pin Ghost to a specific version (e.g.,ghost:5.98.0-alpine). This prevents surprise upgrades. - Upgrade one major version at a time. Ghost 4 → 5 → current. Check the Ghost changelog for breaking changes.
- Use health checks on the database service so Ghost waits for MySQL.
- Don’t kill containers during startup. If you see migration logs, wait for them to complete before stopping the container.
- Test upgrades on a copy first. Clone your volume, run the upgrade on the copy, verify, then upgrade production.
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments