Following on from my first post on this project, this post will go through the steps I take to run Ghost.

Lets start with a diagram.
ghost-docker-diagram

Certbot

Certbot is the first service in docker-compose.yml. I'm using Sebastian Mellor's Docker image for this. In case you aren't familiar, Certbot manages my Let's Encrypt certificates.
I'm running Certbot in standalone mode, which means that Certbot runs its own web server to facilitate the Let's Encrypt domain verification. While it's running its own web server, the container isn't directly accessible from the internet because Nginx proxies the relevant requests through to it.

I'm also carrying through two volumes to the container. These are for the config and for the certificates themselves (which will later be available to the Nginx server).

Let's Encrypt need to be able to make a HTTP connection to this machine to verify that we can request the certificate, which is why we're proxying HTTP requests for /.well-known.

    certbot:
      image: certbot
      networks:
        - backend
      build:
        context: ./certbot
        dockerfile: Dockerfile
      volumes:
        - ./certbot/.data/etc/letsencrypt:/etc/letsencrypt
        - ./certbot/.data/var/lib/letsencrypt:/var/lib/letsencrypt
      restart: always

Nginx

Nginx is next in docker-compose.yml. I'm sure you're familiar with the Nginx web server so I'm not going to talk too much about it.

Nginx is on the frontend and backend networks. Fontend because it is getting ports forwarded from the host. Backend because it is using that network to proxy requests to the Certbot and Ghost containers.

The UPSTREAMS build argument is a space-separated list of upstream container names. each of these gets added to the upstreams config of Nginx during the build.

Nginx Dockerfile

RUN echo "upstream certbot { server certbot:80; }" > /etc/nginx/conf.d/upstream.conf; \
	for UPSTREAM in $UPSTREAMS; \
	do echo "upstream $UPSTREAM { server $UPSTREAM:2368; }" > /etc/nginx/conf.d/upstream.conf; \
	done

The sites volume has the Nginx site configs for each blog we're running.
As you can see, the certificates from the Certbot container are being passed through as a volume.
The ports are set from a .env file so I can have a dev environment that doesn't interfere with my production environment.

    nginx:
      image: nginx
      networks:
        - frontend
        - backend
      build:
        args:
          UPSTREAMS: "tomsalmon-ca"
        context: ./nginx
        dockerfile: Dockerfile
      volumes:
        - ./nginx/sites:/etc/nginx/sites-available
        - ./certbot/.data/etc/letsencrypt:/etc/letsencrypt
      restart: always
      ports:
        - ${NGINX_HTTP_PORT}:80
        - ${NGINX_HTTPS_PORT}:443

Ghost

After Nginx comes the blog(s) itself. I'll show you my blog.

  • The ghost-gcloud image is based from the official Ghost Docker image, with some tweaks.
  • This container is only on the backend network as it should always sit behing Nginx.
  • The GHOST_VERSION build argument sets the version of Ghost to install. The official image also specifies the ghost-cli version, but I have set mine to just use the latest because I got fed up trying to find out what the latest ghost-cli version is (I find out the latest Ghost version from the banner I get telling me there is an upgrade available).
  • I pass through the Ghost content from a volume to catch anything that doesn't end up in Google Cloud Storage.
  • The first JSON file is the API auth for Google CLoud Storage. That file name is specified in the Ghost config file.
  • config.production.json is the config file for Ghost
  • 2368 is the port used for Ghost's built in web server. Nginx proxies the front-end connections on HTTPS(443) to the back-end on HTTP(2368)
    tomsalmon-ca:
      image: ghost-gcloud
      networks:
        - backend
      build:
        args:
          GHOST_VERSION: "1.19.0"
        context: ./ghost-gcloud
        dockerfile: Dockerfile
      volumes:
        - ./tomsalmon-ca/content:/var/lib/ghost/content
        - ./tomsalmon-ca/toms-blog-a67844d28629.json:/var/lib/ghost/toms-blog-a67844d28629.json
        - ./tomsalmon-ca/config.production.json:/var/lib/ghost/config.production.json
      restart: always
      expose:
        - "2368"

And finally, here are the bits of the Ghost Dockerfile that set up the Google Cloud Storage adapter.

RUN npm install -g ghost-storage-adapter-gcloud
RUN mkdir -p /var/lib/ghost/versions/"$GHOST_VERSION"/core/server/adapters/storage/gcloud; \
    cp /usr/local/lib/node_modules/ghost-storage-adapter-gcloud/index.js /var/lib/ghost/versions/"$GHOST_VERSION"/core/server/adapters/storage/gcloud/index.js; \
    npm install --save @google-cloud/storage; \
    chown -R node:node /var/lib/ghost/versions/"$GHOST_VERSION"/core/server/adapters/storage/gcloud; \
    chown -R node:node "$GHOST_INSTALL"

Watch this space, once I have a cleaned up public Git repo for this project I'll post it here.