Back to Blog
Tutorial10 min read2026-06-28

How to Deploy a Ghost Blog with MySQL

Ghost is a professional publishing platform that needs MySQL 8, persistent storage, and correct mail config in production. Here's how to deploy the official Docker image the right way.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Ghost is a fast, focused publishing platform used by everyone from solo bloggers to large media teams. The official Docker image makes deployment approachable, but production Ghost has specific requirements: MySQL 8 (not SQLite), persistent storage for uploaded images, correct URL config, and working email. Let's cover all of it.

Ghost's production requirements

Ghost officially supports two databases: SQLite (for local/dev) and MySQL 8 for production. As of recent versions, MySQL 8 is the recommended and supported production database. You'll also need:

  • Persistent storage for /var/lib/ghost/content (images, themes, settings) — or an external storage adapter.
  • A correct url — Ghost bakes its public URL into generated links, so getting this right matters.
  • A mail provider for member signups, password resets, and newsletters.

Running the official image

The official ghost image is well maintained. Configure it entirely through environment variables (Ghost maps database__connection__* style vars to its config):

NODE_ENV=production
url=https://blog.example.com
database__client=mysql
database__connection__host=<db-host>
database__connection__port=3306
database__connection__user=<db-user>
database__connection__password=<db-password>
database__connection__database=<db-name>

Ghost automatically runs its own database migrations on startup, so you don't manage migrations manually — just point it at an empty MySQL 8 database and it initializes the schema.

The persistent content directory

This is the most common Ghost deployment mistake. Ghost stores uploaded images, installed themes, and some settings in /var/lib/ghost/content. In a container, that directory is ephemeral by default — redeploy and your images are gone.

Two solutions:

  1. 1Mount a persistent volume at /var/lib/ghost/content.
  2. 2Use a storage adapter that writes images to S3-compatible object storage instead of local disk.

For a containerized cloud deployment, the storage-adapter route is cleaner because it works seamlessly across redeploys and replicas. A community S3 storage adapter handles this; configure it via env vars pointing at your bucket.

Email configuration

Ghost uses email for transactional messages and (with a bulk provider like Mailgun) newsletters. Set the mail transport:

mail__transport=SMTP
mail__options__host=smtp.example.com
mail__options__port=587
mail__options__auth__user=<smtp-user>
mail__options__auth__pass=<smtp-pass>

Without working mail, member signups and staff invites silently fail — test this early.

A note on the URL setting

Ghost generates absolute URLs (canonical tags, RSS, sitemap, email links) from the url value. Set it to your real public HTTPS URL from the start. Changing it later requires care because existing content may reference the old domain.

Deploying on PandaStack

  1. 1Create a MySQL 8 database — connection details are injected as env vars; map them to Ghost's database__connection__* variables.
  2. 2Connect the official ghost image (or a thin Dockerfile based on it) as a container app.
  3. 3Set url, the database vars, and mail vars in the dashboard.
  4. 4Configure an S3-compatible storage adapter for images so media survives redeploys.
  5. 5Push — Ghost migrates its schema on boot, and you get automatic SSL on your custom domain.
ConcernProduction value
DatabaseMySQL 8 (managed)
Content/mediaObject storage adapter
URLReal public HTTPS URL
MailSMTP / bulk provider
Port2368

Common pitfalls

  • SQLite in production — unsupported at scale; data lives in the ephemeral filesystem.
  • No persistent storage for content/ — uploaded images disappear on redeploy.
  • Wrong url — broken links, RSS, and emails.
  • No mail provider — silent failures on signups and invites.
  • Forgetting Ghost runs on port 2368 — map your service port correctly.

References

  • Ghost official documentation: https://ghost.org/docs/
  • Ghost Docker image: https://hub.docker.com/_/ghost
  • Ghost configuration reference: https://ghost.org/docs/config/
  • Ghost MySQL requirement: https://ghost.org/docs/install/ubuntu/#requirements
  • Ghost storage adapters: https://ghost.org/integrations/custom-integrations/

---

PandaStack's free tier includes a container app and a managed MySQL database — deploy the official Ghost image, add an object-storage adapter for media, and get automatic SSL on your domain. Start at https://dashboard.pandastack.io

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also