How We Got This Ghost Blog Running
A start-to-finish recap of the Docker, Traefik, Ghost, MySQL, DNS, and Gmail SMTP work that brought this blog online.
Start-to-finish setup recap
This post summarizes the work we did to get this Ghost blog running and reachable at https://blog.johnnyv5ai.com.
1. Created a Docker Compose project
We followed the Docker Manager-style convention and created a stable project directory at /docker/ghost-blog. Inside it, we created a docker-compose.yml and a locked-down .env file for configuration.
The stack uses two containers: ghost-blog-ghost-1 running ghost:5-alpine, and ghost-blog-mysql-1 running mysql:8.0. Ghost exposes port 2368, and MySQL stores the Ghost database.
2. Added persistent storage
We created named Docker volumes so the blog content and database survive container recreation: ghost-blog_ghost-content for Ghost content and ghost-blog_mysql-data for MySQL data.
3. Connected Ghost to MySQL
The compose file configures Ghost to use MySQL with the database name ghost and the ghost database user. The MySQL container includes a health check, and Ghost waits for MySQL to become healthy before starting.
4. Put the blog behind Traefik and HTTPS
Traefik was already running on the server and listening on ports 80 and 443. We added Traefik labels to the Ghost service so the blog could be routed publicly over HTTPS.
The first hostname used during setup was ghost.johnnyv5ai.com. Later, we remapped the blog to its final hostname: blog.johnnyv5ai.com.
5. Updated the canonical Ghost URL
When the hostname changed, we updated both the Traefik Host rule and Ghost’s canonical URL setting. That matters because Traefik controls how traffic reaches the container, while Ghost uses its own URL value for redirects, admin links, assets, and email links.
6. Verified the service
We checked Docker status, container logs, MySQL health, and Ghost startup messages. The important verification signals were that MySQL was healthy, Ghost reported “Database is in a ready state,†and Ghost reported that the site was available at https://blog.johnnyv5ai.com/.
7. Fixed the login email problem
When member login failed with “Failed to log in, please try again,†we checked the Ghost logs instead of guessing. The root cause was Gmail SMTP authentication. Gmail rejected the login with “Application-specific password required.â€
To fix that, we configured Ghost mail settings for Gmail SMTP, created a helper script at /docker/ghost-blog/configure-gmail-smtp.sh, and used a Google App Password instead of a normal Gmail password.
After the Gmail App Password was added and Ghost restarted, the magic-link login flow succeeded. Ghost logs showed POST /members/api/send-magic-link/ returning 201, and the blog login worked.
Current result
The blog is now running in Docker, backed by MySQL, routed through Traefik over HTTPS, configured with Gmail SMTP, and able to send login emails successfully.