The docker image for Ghost 2 used on FirePress and play-with-ghost
10M+
FirePress.org |
play-with-ghost |
GitHub |
Twitter
What is Ghost? — Ghost is an open-source software that lets you create your website with a blog. See the FAQ section for more details. This project makes it run in a Docker image.
This notice is for users of the stable and edge tags. edge moves to 5.130.6 when it is released. stable moves later, when the maintainer promotes it. From 5.130.6 on, the image runs Ghost on a distroless Node 22 runtime. It has no shell, no curl, and no gosu.
curl inside the container fails, so the container turns unhealthy. An orchestrator that restarts unhealthy containers then restarts Ghost in a loop. Remove your curl healthcheck when you move to 5.130.6. The image has its own, described in Health checkcommand or entrypoint override, such as node current/index.js: it fails with Cannot find moduledocker exec <container> sh fails with executable file not found. Use docker cp to read fileschown -R 1000:1000 on the volume once/etc/localtime read-only to set onelinux/amd64 onlynpm -v through a shell. Without a shell, Ghost logs spawn /bin/sh ENOENT when the check is due, about once a day, and keeps servingGhost migrates the SQLite database the first time 5.130.6 starts on it. Back up the content directory before you update.
node:22-trixie-slim, then copied into gcr.io/distroless/nodejs22-debian13:nonroot. Both base images are pinned by digestcurl, and no gosu--read-only and a writable /tmp/var/lib/ghost/content/data/ghost.db. MySQL works through the Ghost configsqlite3 and sharp loadlinux/amd64 onlyThe image is about 620 MB.
.github/workflows/release.yml runs only when a tag such as 5.130.6 is pushed. It runs one job on ubuntu-24.04, for linux/amd64, with Podman:
mise.toml, then runs just checkjust push --immutable, which pushes the image that just check tested under its immutable tag, such as 5.130.6_1a2b3c4_2026-10-28_12H00. It stops when that tag already existsactions/attestjust push --mutable, which points the version tag and edge at the same digest. It moves them only when git lists no release tag newer than this one, so a rerun of an older release publishes its immutable tag aloneA merge to master publishes nothing. The workflow never moves stable.
A fix to a base image ships as a rebuild. Dependabot opens one grouped PR a month for the base-image digests, and one for the GitHub Actions. After the base-image PR merges, push the next -N tag of the same Ghost version, such as 5.130.6-1, then 5.130.6-2.
Want to try Ghost quickly? This is for you!
play-with-ghost.com is a playground to learn about Ghost. What's remarkable here, is that you have the option to log into the admin panel of each live demo available, by using dummy credentials.
In short, you can try Ghost on the spot without having to sign-up!
At this point, this docker image has been pulled more than 11 millions of time!

GHOSTFIRE_IMG="devmtl/ghostfire:stable"
docker run -d \
--name ghostblog \
-p 2368:2368 \
-e url=http://localhost:2368 \
${GHOSTFIRE_IMG}
GHOSTFIRE_IMG="devmtl/ghostfire:stable"
docker run -d \
--name ghostblog \
--read-only \
--tmpfs /tmp \
-p 2368:2368 \
-e url=https://example.com \
-v /myuser/localpath/ghost/content:/var/lib/ghost/content \
-v /myuser/localpath/ghost/config.production.json:/var/lib/ghost/config.production.json:ro \
-v /etc/localtime:/etc/localtime:ro \
${GHOSTFIRE_IMG}
The content directory must be writable by UID 1000. Ghost writes uploads and temporary files to /tmp, so a read-only container needs the /tmp tmpfs.
To configure the config.production.json refer the ghost docs.
The image has its own HEALTHCHECK. Every 30 seconds, it requests the admin site API, <path>ghost/api/admin/site/, where <path> is the path of the site url. It reads the site url, the admin url, and the port from the environment (url, admin__url, server__port), or else from the config, as Ghost does. It reports healthy only when Ghost answers 200. Read the status with:
docker inspect --format '{{.State.Health.Status}}' ghostblog
Do not add a health check that runs curl or a shell: the image has neither. To change the timing, set only --health-interval, --health-timeout, --health-retries, or --health-start-period, and keep the image's test. In a Compose file, leave out the healthcheck block.
Each release pushes three tags:
devmtl/ghostfire:5.130.6_<sha7>_<date> # never moves; pin this one
devmtl/ghostfire:5.130.6 # the latest build of 5.130.6
devmtl/ghostfire:edge # the latest release
stable moves only when the maintainer runs just promote <tag> with the immutable tag that has run well on a real site. A -N rebuild pushes a new 5.130.6_<sha7>_<date> tag and moves 5.130.6 and edge.
Find the latest tags on DockerHub here: https://hub.docker.com/r/devmtl/ghostfire/tags/
The Dockerfile has two stages:
builder installs Ghost-CLI, then Ghost, on node:22-trixie-slim, with the build tools that sqlite3 needs. The build fails unless sqlite3 and sharp load/var/lib/ghost into gcr.io/distroless/nodejs22-debian13:nonroot, owned by root, with an empty content directory owned by UID 1000. It adds start.mjs and healthcheck.mjs from rootfs/At start, start.mjs copies each missing directory and default theme from content.orig into content, then starts Ghost. It never overwrites what the volume holds. The default themes, casper and source, stay links into the image, so they update with it.
just scan runs Grype and fails on any Critical or High finding with a fix in the OS layerCritical paths for data persistence:
/var/lib/ghost/content # All Ghost content, themes, uploads
/var/lib/ghost/config.production.json # Runtime configuration, mounted read-only
/var/lib/ghost/content/logs # Application logs
/var/lib/ghost/content/data/ghost.db # SQLite database
/tmp # Uploads and imports in progress; a tmpfs with --read-only
Let's understand our processes. In this post « How we update hundreds of Ghost's websites on Docker Swarm? », we explain how we deploy Ghost in production and which best practices we do follow.
Install the pinned tools with mise install, then run just check. It lints the repo, builds the image with Podman, boots it on SQLite, and scans it with Grype. AGENTS.md lists every recipe.
I open-sourced my setup here. It’s a workflow to run Ghost locally within a Docker container. Once your local paths are defined, it’s enjoyable and easy to work between many themes.
/var/lib/ghost/content/data/ghost.dbconfig.production.jsonThe image's config.production.json sets the url to http://localhost:2368, listens on port 2368, uses SQLite, and keeps the content in /var/lib/ghost/content. To change it, mount your own config.production.json read-only over it.
Ghost reads environment variables before config.production.json. A variable such as url or database__client wins over the mounted config.
Current Stack:
gcr.io/distroless/nodejs22-debian13Check versions in a running container:
docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.version" }}' <container-id>
docker exec <container-id> /nodejs/bin/node --version
Path consistency across versions:
- Ghost 5.x.x: /var/lib/ghost/content ✅ (current)
- Ghost 4.x.x: /var/lib/ghost/content
- Ghost 3.x.x: /var/lib/ghost/content
- Ghost 2.x.x: /var/lib/ghost/content
- Ghost 1.x.x: /var/lib/ghost/content
- Ghost 0.11.x: /var/lib/ghost (deprecated)
From an earlier image to 5.130.6, read Breaking change in 5.130.6.
At FirePress we empower entrepreneurs and small organizations to create their websites on top of Ghost.
At the moment, our pricing for hosting one Ghost website is $15 (Canadian dollars). This price will be only available for our first 100 new clients, starting May 1st, 2019 🙌. See our pricing section for details.
More details about this announcement on Ghost's forum.
The power of communities pull request and forks means that 1 + 1 = 3. You can help to make this repo a better one! Here is how:
git checkout -b my-new-featuregit commit -am 'Add some feature'git push origin my-new-featureCheck this post for more details: Contributing to our Github project. Also, by contributing you agree to the Contributor Code of Conduct on GitHub. It's plain common sense really.
Our mission is to empower freelancers and small organizations to build an outstanding mobile-first website.
Because we believe your website should speak up in your name, we consider our mission completed once your site has become your impresario.
For more info about the man behind the startup, check out my now page. You can also follow me on Twitter @askpascalandy.
— The FirePress Team 🔥📰
Content type
Image
Digest
sha256:fe6acd0e4…
Size
166.8 MB
Last updated
1 day ago
docker pull devmtl/ghostfire:edge