Part 2: Ghost
After reading Jake Hamilton's interesting post on hosting a Ghost blog with Docker, I thought it would be good to share my own Ghost + Docker blog setup.
This will be a two-part post, with this second post focussing on integrating Ghost.
If you haven't read the previous post on Docker setup, check that out first!
Introduction
This post (like Part 1) will assume a basic understanding of Docker and how to work with Docker containers.
The setup outlined in this post will support running a Ghost blog at a subdomain of your own domain (in these examples blog.agchapman.com
), but can also be run on it's own domain.
The examples here will assume you're using basically the same setup as outlined in Part 1, so some modification may be needed to suit your own environment.
Setting up your DB
Ghost, by default, ships using an SQLite database for data and configuration, but personally I prefer to use a "real" database, so I'm going to be using a separate MariaDB instance. Fortunately, Docker Compose makes this trivial:
version: '2'
services:
db:
image: 'bitnami/mariadb:latest'
Running docker-compose up -d
now will only create a new empty MariaDB container, so let's leave that for a moment.
Bitnami
Who is this bitnami/mariadb
and why should you trust them? Bitnami are an awesome crowd who build ready-to-deploy, batteries-included packages for a wide range of applications across Linux, Windows and macOS. They use simple installers, come pre-configured (as much as possible), and are dead-simple to work with. Even better, they provide Docker images of many of their packages applications, making using them in complicated deployments a snap.
Adding Ghost to the mix
Now, it's time for the real app: Ghost. Installing Ghost from one of Bitnami's Docker images couldn't be easier. Just add a new service to your docker-compose.yml
:
version: '2'
services:
db: ...
blog:
image: 'bitnami/ghost:latest'
expose:
- '2368'
depends_on:
- mariadb
There is also an official
ghost
image available, which uses the built-in SQLite DB.
Now, if we ran docker-compose up -d
, you'd also get a Ghost blog, but it would be running on a custom port (2368), and only from within linked Docker containers. To expose our Ghost blog, we only need to add a few extra options:
version: '2'
services:
db: ...
blog:
image: 'bitnami/ghost:latest'
expose:
- '2368'
depends_on:
- mariadb
environment:
- VIRTUAL_HOST=blog.agchapman.com
That all-important VIRTUAL_HOST
variable tells the nginx-proxy
container we set up in Part 1 to create a new reverse proxy config in Nginx for the blog.agchapman.com
domain name. By default, the config will point at the only port that the blog
service exposes: 2368
.
Finally, we can run docker-compose up
and watch as Docker creates a new MariaDB container and a new Ghost container and links them together. In the background, the nginx-proxy
container will create a new config entry for the new container.
You should now be able to browse to blog.agchapman.com
in a browser and (assuming you have your DNS set correctly), it will bring you to your new Ghost blog.
SSL
As I outlined in Part 1, if you want SSL support (powered by the letsencrypt-nginx-proxy-companion
container), we also need to add a few more variables to our configuration:
version: '2'
services:
db: ...
blog:
image: 'bitnami/ghost:latest'
expose:
- '2368'
depends_on:
- mariadb
environment:
- VIRTUAL_HOST=blog.agchapman.com
- LETSENCRYPT_HOST=blog.agchapman.com
- [email protected]
Those two new variables, LETSENCRYPT_HOST
and LETSENCRYPT_EMAIL
, are used by the ssl-companion
container (attached to the nginx-proxy
) to automatically generate a new SSL certificate from Let's Encrypt and inject the correct configuration into the reverse proxy. Note how we did not need to enable SSL or change any configuration in Ghost itself: Our reverse proxy will handle (and terminate) the SSL and communicate with the Ghost instance over plain old HTTP.
Now there's no excuse to run anything over HTTP!
Volumes
This setup will create un-named data volumes in the default location to hold blog and app data: functional, but not the easiest to manage. Alternatively, you can use the volumes
node to add named volumes, or host directories, to mount at /bitnami/mariadb
or /bitnami/ghost
repsectively.
Personally, as a general rule, I use named volumes for DBs and host directories for app data
Next steps
As I mentioned in Part 1, you can apply the same VIRTUAL_HOST
trick used here to any container, so you can also spin up web servers, CMSs, documentation sites, anything really! Simply include VIRTUAL_HOST
(and LETSENCRYPT_HOST
and LETSENCRYPT_EMAIL
for SSL support), and everything else will take care of itself!
Example
Don't believe me? Let's add a website, just for shits and giggles (TM):
version: '2'
services:
web:
image: 'bitnami/apache' #did I mention I love Bitnami!
environment:
- VIRTUAL_HOST=apache.agchapman.com
- VIRTUAL_PORT=80 # since otherwise it will default to 443
... # snipped
That's it! Once you start the web server (with docker-compose up
), hitting apache.agchapman.com
(in this example) will get automatically proxied to our new Apache container on port 80!
Summary
While it seems a little complex at first glance, this is a dead-simple method of running Ghost with zero installation, no upgrade woes, isolated app data and no dependencies to worry about. When integrated with a larger Docker environment (such as the one I use now), you can run a multitude of services, applications and tools out of a single host with almost no configuration. The only thing that slows me down now is adding new DNS names!
As always, leave any feedback in the comments or hit me up on Twitter!
Useful links:
- Jake's post on his simpler but similar setup
- An intro to Bitnami apps
- MariaDB and Ghost images on Docker Hub
- The nginx-proxy image used in both parts
- CodeShip's guide to the Docker ecosystem