DEV Community


Ghost in A shell - Part I : localhost

ebreton profile image Manu Originally published at on ・6 min read

Ghost in A shell - Part I : localhost

The last blog from the Foundation Ghost, "After 5 years and $3M, here's everything we've learned from building Ghost", was an eye-opener for me.

Not only the company model: a Foundation whose owners will not be able to sell, on purpose; but especially the actual love behind the product:

when we do anything you can be absolutely certain it's in the sole interest of building the best product we can for the right reasons.

Coming from WordPress, having played with Jekyll and Hugo, I could only take the bait to try Ghost. Therefore, I happily threw myself into the doc and the installation process to get a taste of the product.


The overall objective of this (short) series of blogs is to be able to spawn easily multiple (independent) ghost blogs on one's beloved server.

It's intended for developers/ops that wish to put their hands into the mechanics of Ghost, and need to easily spawn/kill blogs here and there.

I will not go through the code of the blog itself, neither on the topics of writing blogs, nor creating a theme. I will limit my sharing to the installation-side of Ghost, and how to facilitate/automate the process.

Would you not feel like a devops at the end of the day, you might find better value in the Ghost(Pro) service, which will do a much better job of hosting your blogs, and keeping them running and up to date.

In this first (of a few) blogs, I will focus on the installation of instances on localhost. The purpose is to set up locally a sandbox, as similar as possible as what we will ultimately have on production (hopefully).

Following the path

There I went on my way, clicking on the Developer > Documentation link of the Ghost website, scrolling a bit down, and finding the expected entry point for setting up things:

Ghost in A shell - Part I : localhost

After clicking on the Setup box, I had the choice between "I want to install Ghost on my own server" and "I want to try Ghost out quickly". I thought I was very lucky and I clicked on the second option: local install guide.

The TL;DR version of local installs, is you want to make use of Ghost CLI's super handy command: ghost install local

From there I stumbled on

  • the current configuration of my environment
  • its Node version
  • and my lack of knowledge of the JavaScript stack.

I quickly switched to one of my server running Ubuntu, where it was also quite painful to get all the versions aligned... At the end of my 10mins timebox, I could not make the ghost-cli execute itself properly.

I (quickly) switched again for a Docker image, and there you go...

Thank you Docker!

There is an official image (i.e. maintained by the DockerHub staff) on the docker hub, ghost, with a few examples of the commands available.

Back on my Mac I created a simple Makefile:


    # Simply start a ghost container making it directly available through $$PORT
    docker run --rm -d --name ${NAME} \
        -v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content \
        -p ${PORT}:2368 \
        -e url=http://${DOMAIN}:${PORT} \
Enter fullscreen mode Exit fullscreen mode

if/when copy-pasting, make sure to use tabs for the indentation (not spaces)

Running make was the only thing left to get my blog running on http://localhost:3001

Running NAME=another PORT=3002 make runs a second blog available on http://localhost:3002

Docker parameters

A quick explanation on the arguments passed above:

  • --rm will remove the container when it stops, thus preventing conflicts when spawning a new container with the same ${NAME}
  • -d will run the container in daemon mode
  • --name ${NAME} will name your container. This one is optional, but it is nice to have the same name for the container and the data volume defined on the following line...
  • -v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content will mount the Ghost files locally on your file system into subfolder ./instances. That will persist data (and allow you to sneak into the code later on).
  • -p ${PORT}:2368 exposes the blog on the local ${PORT}
  • -e url=http://${DOMAIN}:${PORT} tells ghost what URL will be used to access it.

A bit of cleaning up

Before next step, you can stop the containers with docker stop ghost-local another. However, the data will not be lost thanks to the volumes.

Moreover, they will be used again the next time you start a blog with the same name.

Thank you Traefik !

My initial objective achieved, I went for the bonus...

The bonus

The first one was to be able to serve on port 80, and to have the blogs sit on different paths. Something like

The "prod-stack" companion

I have my so humbly called "prod-stack" repo that provides me with this kind of routing functionalities (among others). It is based on Traefik (and NGinx), and allows me to quickly set up a proxy, a DB and a memcached.

It's running on my Mac, therefore making this solution free for me. I could only buy it :)

For you dear reader, I have taken advantage of this blog to open my prod-stack on github, and I also have done a bit of curating with the Readme (even though it is still more a draft than a real documentation).

Here is the fast-track setup to pull and run all docker containers:

$ git clone
Cloning into 'prod-stack'...
$ cd prod-stack
$ make
cp etc/traefik.toml.sample etc/traefik.toml
cp etc/db.sample.env etc/db.env
sed -i s/password/auMr9jsMrPnX0Oatb9j4yX2AYBN2jTQV/g etc/db.env
Generated etc/db.env
docker-compose pull
Pulling nginx-entrypoint ...
Enter fullscreen mode Exit fullscreen mode

Once the logs stabilize, you can Ctrl-C to get back on the command line, and docker ps to check that the stack is running.

The Traefik dashboard will be available on http://localhost:8081 with the user test/test

Ghost in A shell - Part I : localhost

Please, do not hesitate to feedback thanks to issues if you felt the curiosity to try it.

Adding make traefik

After making sure that Traefik is running, I added this command to the Makefile:

    # Start a ghost container behind traefik on path $$NAME
    # Beware of --network used, which is the same one traefik should be using
    docker run --rm -d --name ${NAME} \
        -v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content \
        -e url=http://${DOMAIN}/${NAME} \
        --network=proxy \
        --label "traefik.enable=true" \
        --label "traefik.backend=${NAME}" \
        --label "traefik.frontend.entryPoints=http" \
        --label "traefik.frontend.rule=Host:${DOMAIN};PathPrefix:/${NAME}" \
Enter fullscreen mode Exit fullscreen mode

The parameters are similar to the ones of the previous command with the following changes:

  • The ${PORT} is not exposed anymore since Traefik will handle the routing
  • --network=proxy launches the container in the same network as the one where Traefik is running (the values proxy is arbitrary and comes from the default configuration of the "prod-stack")
  • --label key=value are used by Traefik to create the appropriate route

Running make traefik will now run a blog on http://localhost/ghost-local

Running NAME=another-ghost make traefik runs a second blog available on http://localhost/another-ghost


First of all , I wanted to easily spawn Ghost blogs on my local machine,

The native documentation did not survive to my impatience, mainly because I knew it would be fast and smooth with Docker.

Secondly , I wanted to use the standard port (80 to start with), and route the blogs according to their path,

It was given for free with Traefik (and my "prod-stack") and I must admit I had a lot of fun playing with make. However, the result is still one for a developer, surely not ready for production (despite the name of the stack).

Nevertheless, the stack can provide HTTPs thanks to Let's Encrypt, a DB and some easy way to manage all this... I will dive into this in the next part to get something more robust... maybe

Discussion (0)

Editor guide