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!
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'
docker-compose up -d now will only create a new empty MariaDB container, so let's leave that for a moment.
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
version: '2' services: db: ... blog: image: 'bitnami/ghost:latest' expose: - '2368' depends_on: - mariadb
There is also an official
ghostimage 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
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:
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.
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_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!
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
Personally, as a general rule, I use named volumes for DBs and host directories for app data
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
LETSENCRYPT_EMAIL for SSL support), and everything else will take care of itself!
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!
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!
- 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