Deploying a Blog in 10 Minutes: Ghost + Docker + NGINX

I've been spending time with docker recently, and as an exercise I wanted to see just how easy it would be to deploy a blog on my server. It turns out it's extremely easy.

This guide will walk through the process assuming a few prerequisites are already taken care of:

  • Docker is installed
  • NGINX is installed and running
  • Port 80 is exposed

So here we go...

Step 1: Deploying a Ghost Container

First let's create a directory for our project, and a docker-compose.yml to configure the container which will host our blog:

$ mkdir blog
$ cd blog
$ vi docker-compose.yml

The contents of our docker-compose.yml should look like this:

  #use the official ghost image:
  image: ghost
  #expose the ghost port:
    - 2368:2368
  #attach to a volume to persist data
    - /path/to/blog/data:/var/lib/ghost

The volumes declaration is important here because it will allow us to persist data even if our container is restarted, and this is where we will edit the configuration later on.

Now that the we have our docker-compose.yml in place, starting our blog is as simple as running:

$ sudo docker-compose up

Running this command will pull the ghost image, start the container, initialize our data volume with an SQLite database and configuration files, and start the ghost server. Once this line appears in the console output:

blog_1 | Listening on

the blog is already available at http://<my-server-domain>:2368, assuming that port is available to the outside world.

Step 2: Adding NGINX

Let's clean up our blog's URL. We can use nginx to make the site available at the blog.<my-server-url> subdomain and to get rid of the port number in the URL.

We'll start by creating a new nginx config file:

$ export NGINX_CONF_DIR=/etc/nginx
$ sudo vi $NGINX_CONF_DIR/sites-available/blog.conf

note: I'm using ubuntu and my nginx configs are in /etc/nginx, but this location depends on your distro

And our blog.conf should look like this:

server {
    #specify the incoming port
    listen 80;
    #specity the subdomain

    location / {
        #refer traffic to the port our ghost container is listening on
        proxy_pass http://localhost:2368;

Next we can simlink blog.conf into sites-enabled so nginx will pick it up on the next restart:

$ sudo ln -s $NGINX_CONF_DIR/sites-available/blog.conf $NGINX_CONF_DIR/sites-enabled/blog.conf

Now all we have to do is restart nginx:

$ sudo service nginx restart

note: this assumes nginx is set up as an upstart service

And that's it - once nginx restarts our blog is available at http://blog.<my-server-domain>

Step 3: Fix the Ghost URL

We're almost there, but if you log into the ghost admin of the blog we just set up, you'll notice that the URL's referenced from within ghost all have the root http://localhost:2368. This is fine if you're running ghost on your local machine, but if you want to publish a blog to the outside world those URL's won't actually refer anything.

We have to fix this by editing ghost's config.js. This file was generated in the volume we declaired the first time we ran the ghost container, so we can access it like so:

$sudo vi /path/to/blog/data/config.js

note: sudo is required because docker runs as root, so by default a non-root user won't have write permission for this file.

Next we have to find the url parameter under development, and change it to point to the URL we just specified in nginx:

config = {
    development: {
       url: 'http://<my-server-domain>.com',

note: we're changing the development URL here because ghost runs in development mode by default. If you want to run in production mode the production url option should be changed instead.

Now all we have to do is restart the container and our blog is ready to go:

$ sudo docker-compose up -d

And that's all it takes.

This blog might not be 100% ready for prime-time; for instance at this point your blog will not be restarted with your system, it's not fault tollerant, and you might want to decouple the data volume into a data-only container or use an external database, but this should serve as an example of how quickly and painlessly an application can be deployed using docker.

Hire me! I'm a freelancer and I'm always looking for projects. I can make you a really great mobile app or back-end. find out more at or email me at [email protected]