Docker, Caddy and Ghost

July 14, 2017

So I’ve moved this Blog entirely over to Docker now, and replace nginx with Caddy, also in its own Docker image. Great, because updates and new apps are easy to deploy (in theory!), but the setup has been a nightmare. On the plus side, I got to finally reprovision this instance, and having finally dockerised everything, having to do so again or move hosts should be trivial.

That being said, I want to go through my own setup, because its been a nightmare getting this all working smoothly. Hopefully this may help out anyone else who wants to go down this route.

1. Docker

Well installing Docker is not something I really need to cover. Visit here and handle the installation: Docker. Ensure you’ve installed correctly by running something simple, like: docker version

Once you are happy, you also need to install docker-compose. You can find installation instructions here: Docker Compose.

The last thing to do, (although you can do it later), is to create a network that all your containers can talk to each other from. This is strictly not necessary, but it helps me visualise how my containers interact with each other. This is done though the following command: docker network create backend

What we’ve done here is created a network called backend that our containers can all connect to (if we want them to), to allow them to speak to each other. You can find out more about docker network here.

2. Ghost

Great, so we have Docker ready to go. Net thing to do is to get a ghost instance up and running. For that we need to create our docker-compose.yml

Before we do so, we will be using an existing image that handles the work of setting up the ghost install. That image is Here, so feel free to read more about it.

Here is a minimal docker-compose.yml:

version: '2'
services:
  web:
    restart: always
    image: ghost
    ports:
      - "2368:2368"
    networks:
      - default
      - backend
    volumes:
      - {path_to_save_ghost_data}:/var/lib/ghost
networks:
  backend:
    external: true

Obviously you need to replace {path_to_save_ghost_data} with your own path, as the image readme explains, I won’t cover that here.

With the above configuration and your own ghost config.js, run docker-composer up -d and you should have access to your site on whatever url and port you specified inside your config.js (default port is 2368 I believe). Please note that if you are not using 2368, you need to change the ports listing in docker-compose.yml to the correct port.

3. Caddy

Great, our ghost blog is accessible to the world, now lets put it behind Caddy! We’ll do this in a separate folder in a separate docker compose config. This isn’t necesary, but I like to keeps things separated.

Configuration is super simple again, we’re gonna use another pre-existing image, you can find Here. Again see its documentation regarding the volumes it wants.

The docker-compose.yml looks like this (again of course replace the paths with your own:

version: '2'
services:
  web:
    container_name: caddy
    restart: always
    external_links:
      - ghost:ghost
    build: .
    networks:
      - default
      - backend
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - {path_to_caddyfile}:/etc/Caddyfile
      - {path_to_certs_folder}:/etc/caddycerts
    environment:
      CADDYPATH: /etc/caddycerts
networks:
  backend:
    external: true

Okay, so what we’ve done here is specified that we want to link the container to the ghost container, and also to use the network backend. This will allow us to proxy requests over to the port ghost is listening on.

The last thing to do is create the Caddyfile itself. Documentation on the Caddyfile can be found Here. We only need a super simple one to get it all working:

YOUR_HOSTNAME {
	proxy / ghost:2368 {
		transparent
	}
	tls YOUR_EMAIL_ADDRESS
}

Replace hostname and email with your info, place it wherever and point your docker-compose.yml to it. Then you can docker-compose up -d on it, and hey presto, you should have SSL up and running on your url without, and no need to specify the port.

In conclusion…

I experienced for hours and hours a 502 between Caddy and Ghost, that I couldn’t get my head around. This turned out to be because the containers can’t communicate with each other by default (makes sense but I didn’t realise it), hence the specified network and container linking.

I wish the documentation was a little more clear, but my end approach is clean and simple. To add more applications, dockerise the app, make sure that the Caddy container shares its network and links to the container, update the Caddyfile and away you go!