Moving to Docker: Ghost

Now that Docker is maturing, and nearly ready to manage data volumes in v1.8 as a first-class citizen, I figured it was time to start migrating all my services to start using container technology1.

The first service I thought I'd tackle was Ghost, as there is a new release of it that I wanted to upgrade to. In addition, I had already created a Docker image for running Ghost (in production mode) a few months ago.

The setup I had been running on timofejew.com was pretty much according to the installation instructions provided by the Ghost team (mainly, installing the correct version of node.js, grabbing the latest release of Ghost, and then setting up init scripts). I also set up NGINX as a reverse proxy and TLS terminator, but I'll be continuing to use this direct installation for now (that will most likely be the next service I put into a container).

Here is how I did it:

Shutting down installation

First things first, I stopped the current running instance of Ghost:

sudo service ghost stop  

Then, I removed the init scripts:

cd /etc/init.d  
sudo rm ghost  
sudo update-rc.d ghost remove  

Now, with a machine re-boot, it won't try to start Ghost up at boot-time.

Install Docker

My VPS is running a stock Ubuntu 14.04 kernel, rather than a special Docker version, so I needed to install Docker:

curl -sSL https://get.docker.com/ | sh  

And... that's about it. Note that the script will need to run parts of itself as root, so you'll need to have the ability to sudo (it will automagically invoke sudo for you). If that's not set up (shame on you), do this step as root.

Establish Ghost data directory

The Ghost team's installation instructions place the data directory in /var/www/ghost/, but I wanted to change that to /var/lib/ghost (to match the Docker image). This is largely for aesthetics, but it also saves cleaning up the source directory of detritus that's no longer required.

Before we go further, a note about file ownership... The official Ghost Docker image creates a user user with a uid:gid of 1000:1000. And due to how it runs, it's using that uid as the owner of the Ghost process. In my case, I'm perfectly happy with that (it's the uid of my account on the host machine), but that may not work for you. If that is the case, you'll have to make your own Docker image (take a look at the official repo, as well as mine, for inspiration).

That out of the way, copy the relevant directories to their new home, and change ownership:

sudo mkdir /var/lib/ghost  
sudo cp -rp /var/www/ghost/{apps,data,images,themes} /var/lib/ghost/  
sudo chown -R 1000:root /var/lib/ghost  

Configure Ghost

The Docker image I created makes this fairly painless. Create a new file /etc/default/ghost that has the following contents

# Ghost environment
# Place in /etc/default/ghost

GHOST_URL=http://www.example.com  
MAIL_FROM='"Webmaster" <webmaster@example.com>'  
MAIL_HOST=mail.example.com  

And, of course, replace this with your info.

Run Ghost

Running Ghost is a matter of telling docker to pull the image, and then run it as a persistent container (that will survive reboots) in the background. Here's the command I use:

docker run --name ghost --env-file /etc/default/ghost \  
    -p 127.0.0.1:2368:2368 -v /var/lib/ghost:/var/lib/ghost \
    --restart=always -m 150M --memory-swap 300M \
    -d ptimof/ghost npm start --production

A couple of notes:

  • I'm binding Ghost's port to the localhost address. The NGINX reverse proxy faces the scary Internet, and forwards all traffic to the Ghost port on localhost. It would be a terrible idea to have Ghost listen on all network interfaces.
  • I'm restricting memory to 150MB (with a 300MB swap limit). This value is from observing how much resident memory Ghost takes when it's running (126MB is the max I've seen so far). This is just an upper boundary - Docker doesn't reserve this much memory, just sets an upper limit.
  • The restart policy is set to always restart. npm is pretty darn stable, so I'm not expecting (nor have I seen) it keep restarting uncontrollably. This also sets things up so that when the VPS restarts, Ghost will restart.

After this is done, you should be able to hit your old blog URL, and all should be good!

Cleaning up

As your mother (should have) told you, wash up after you're done.

sudo rm -r /var/lib/www/ghost  
sudo apt-get remove -y nodejs  
sudo apt-get autoremove -y  

There! Clean as a whistle.

Next steps

There's only one more service on this machine I need to move to Docker, and that's the NGINX reverse proxy. That is covered in the second part of this series, Moving to Docker: NGINX reverse proxy with SSL termination. You'll also find a more complex Dockerfile that I created that will handle sending authenticated email to a mail relay (as opposed to the basic email configuration in this article).

For now, I'll stick to using the host's filesystem to persist data, but I'll switch to using Docker data containers once I've figured out a comfortable mechanism to back up and restore data containers.

And after that, I'll move on to containerizing my mail service. That will be a much bigger challenge, as there's a zillion services running on that box...


  1. For a detailed explanation of what Docker actually is, please see https://www.docker.com/whatisdocker.