This is the story of how I got this blog up and running, and hopefully it's helpful to other people looking to self-host Ghost (hey that rhymed).

After some trouble getting the official Docker image for Ghost to work outside of my development box, I decided to make my own image.

Checking the Ghost Github repository, the documentation says:

Download the latest release of Ghost Unzip in the location you want to install Fire up a terminal npm install --production Start Ghost! Local environment: npm start

On a server: npm start --production http://localhost:2368/ghost

Converted into a Dockerfile, those instruction look something like the following.

Dockerfile

FROM download13/node WORKDIR /ghost COPY config.js /ghost/config.js RUN wget -O ghost.zip https://github.com/TryGhost/Ghost/releases/download/0.7.9/Ghost-0.7.9.zip \ && unzip ghost.zip \ && rm ghost.zip \ && npm install --production \ && npm cache clean \ && rm -rf /tmp/npm* CMD ["npm", "start", "--production"]

The npm cache clean and rm -rf /tmp/npm* bits are just cleanup to keep the image size down. The important part here is the COPY config.js /ghost/config.js line, which will bring in our custom config file.

Speaking of which...

config.js

module.exports = { production: { url: process.env.BLOG_URL, mail: {}, database: { client: 'sqlite3', connection: { filename: '/ghost/content/data/ghost.db' }, debug: false }, server: { host: '0.0.0.0', port: process.env.PORT || '80' } } };

Two fields are set via environment variables. The url field (set by BLOG_URL ) will be the base URL for your blog. It's required if you don't want a somewhat broken experience. server.port defaults to 80 which will be fine for most uses, but can be overridden by setting the PORT environment variable.

Let's test what we've got so far.

# docker build -t ghosttest . ... Successfully built 767d2c6e0177

Sweet! This might actually work. Now let's try running it.

Note that 192.168.99.100 is the IP of the Docker host VM on my computer. Replace it with localhost or the address of your Docker host if different.

# docker run -it -e "BLOG_URL=http://192.168.99.100" -p 80:80 ghosttest > ghost@0.7.9 start /ghost > node index WARNING: Ghost is attempting to use a direct method to send email. It is recommended that you explicitly configure an email service. Help and documentation can be found at http://support.ghost.org/mail. Migrations: Database initialisation required for version 004 Migrations: Creating tables... Migrations: Creating table: posts Migrations: Creating table: users Migrations: Creating table: roles Migrations: Creating table: roles_users Migrations: Creating table: permissions Migrations: Creating table: permissions_users Migrations: Creating table: permissions_roles Migrations: Creating table: permissions_apps Migrations: Creating table: settings Migrations: Creating table: tags Migrations: Creating table: posts_tags Migrations: Creating table: apps Migrations: Creating table: app_settings Migrations: Creating table: app_fields Migrations: Creating table: clients Migrations: Creating table: client_trusted_domains Migrations: Creating table: accesstokens Migrations: Creating table: refreshtokens Migrations: Running fixture populations Migrations: Creating owner Migrations: Ensuring default settings Ghost is running in production... Your blog is now available on http://192.168.99.100 Ctrl+C to shut down

Navigating to http://192.168.99.100 I see the following page.

Woohoo! Done! Well, not quite.

There's a little more to deploying it, but first I'm going to put what we have so far into an Docker Hub Automated Build for the sake of convenience. Now whenever we want to use this image, we can just refer to it as download13/ghost .

If you want to deploy this on your personal server, you're probably going to want to keep the contents of your blog even if you have to re-create it's container.

There are two directories that need to persist across containers if you want your blog to work correctly.

/ghost/content/data contains the sqlite database file which stores all your users/posts/tags/etc.

/ghost/content/images is where all uploaded images are kept.

Let's add these volumes to our container.

# mkdir -p ~/ghost_data/{data,images} # docker run -it -e "BLOG_URL=http://192.168.99.100" -p 80:80 -v ~/ghost_data/data:/ghost/content/data -v ~/ghost_data/images:/ghost/content/images download13/ghost

Of course running it from Docker Compose is a bit easier.

docker-compose.yml

version: "2" services: blog: image: download13/ghost restart: always volumes: - ~/ghost_data/data:/ghost/content/data - ~/ghost_data/images:/ghost/content/images environment: BLOG_URL: http://192.168.99.100

# mkdir -p ~/ghost_data/{data,images} # docker-compose up -d Creating ghosttest_blog_1

And that's it! Now you've got a working Ghost blog!