Since I created this blog on Ghost, I tasked my self with the goal of running everything on this droplet using Docker. I believe the benefits of using Docker far outweight all the roadblocks I have stumbled upon while learning how to use it. If you don’t know what Docker is, you better go check it out now
If you are interested in getting a copy of my current docker setup, check out and fork this github repo
Docker-Compose configuration
Initially, I had all my setup in a single docker-compose.yml
file and everything was working just fine. But eventually, I plan to have more than one website in my current droplet and I didn’t want everything to be together in a large monolith. Thus, I decided to break services down in different compose files.
The only side effect I am experiencing right now is in the way I call docker-compose commands. Since I no longer have a docker-compose.yml
file, I have to resort to issue the commands using:
$ docker-compose -f filename.yml COMMAND
But that is small price to pay and I am more than happy with my current workflow.
Nginx setup
For the nginx setup, I had created in advance an external bridge network named ngixn_frontend in the docker host as follow:
$ docker network create nginx_frontend
My nginx compose file is comprised of 3 services:
- Nginx: Uses the official nginx image from docker hub.
- Nginx-gen: From jwilder, this awesome image will automatically generate the reverse proxy configs used by the Nginx service to connect towards the running apps. In this particular case to the Ghost blog app.
- nginx-letsencrypt: From JrCs, it enhances the nginx proxy setup, by handling creation/renewal of Let’s Encrypt certificates.
version: "3"
services:
nginx:
image: nginx
container_name: nginx
ports:
- '80:80'
- '443:443'
volumes:
- nginx-conf:/etc/nginx/conf.d
- nginx-vhost:/etc/nginx/vhost.d
- nginx-html:/usr/share/nginx/html
- nginx-certs:/etc/nginx/certs:ro
labels:
com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: 'true'
restart: always
networks:
- nginx_frontend
nginx-gen:
image: jwilder/docker-gen
command: -notify-sighup nginx -watch -wait 5s:30s /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
container_name: nginx-gen
restart: always
volumes:
- nginx-conf:/etc/nginx/conf.d
- nginx-vhost:/etc/nginx/vhost.d
- nginx-html:/usr/share/nginx/html
- nginx-certs:/etc/nginx/certs:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro
networks:
- nginx_frontend
nginx-letsencrypt:
restart: always
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: nginx-letsencrypt
volumes:
- nginx-conf:/etc/nginx/conf.d
- nginx-vhost:/etc/nginx/vhost.d
- nginx-html:/usr/share/nginx/html
- nginx-certs:/etc/nginx/certs:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- NGINX_DOCKER_GEN_CONTAINER=nginx-gen
- NGINX_PROXY_CONTAINER=nginx
networks:
- nginx_frontend
networks:
nginx_frontend:
external: true
volumes:
nginx-vhost:
nginx-conf:
nginx-html:
nginx-certs:
Most of the configuration is based from the documentation of the respective images. One tiny detail to watch out is the necessity of declaring nginx_frontend network as external to avoid generating a new network when running docker-compose.
Ghost setup
My Ghost app is configured as shown below in a blog.compose.yml
file. The compose file declares to network: the nginx_frontend as external one
It reads several environment variables from a .env file that includes the host directory binds for the config.production.json and the theme directory. The configuration also specifies the values for environment vars used by the nginx-gen and nginx-letsencrypt images to automatically plug in the ghost app with nginx.
The mysql db configuration below is pretty straightforward and the only remark is that I am issuing a command to specify that MySQL should use utf8mb4 for the character set and collation.
Both services communicate between them by the blog_backend network. Besides, the ghost container connects with ngixn services by virtue of being part of the nginx_frontend network.
version: "3"
services:
ghost:
image: ghost
container_name: blog-app
expose:
- "$GHOST_PORT"
restart: always
volumes:
- ghost_content:/var/lib/ghost/content
- $GHOST_THEME:/var/lib/ghost/content/themes/BitKnown
- $GHOST_CONFIG:/var/lib/ghost/config.production.json
environment:
- VIRTUAL_HOST=$BLOG_HOST,www.$BLOG_HOST
- LETSENCRYPT_HOST=$BLOG_HOST
- LETSENCRYPT_EMAIL=$EMAIL
- NODE_ENV=production
depends_on:
- db
networks:
- nginx_frontend
- blog_backend
db:
image: mysql
container_name: blog-db
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- blog_db:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=$GHOST_DB_PASSWORD
- MYSQL_DATABASE=$GHOST_DB_NAME
networks:
- blog_backend
networks:
nginx_frontend:
external: true
blog_backend:
volumes:
ghost_content:
blog_db:
This post was a shorter one, but it took me some time to get everything working properly under the hood. This configuration provides me a lot of flexibility for future endeavors. I can say that I am very happy with the results so far and it has give me a reason to dig deeper in Docker.
Well, this is it for today. I hope this bit of Docker information has been of some help. Once again, thanks for reading till the end. See you soon and stay tuned for more!!