If you are reading this, I will assume the time has finally come for you to start your own blog. Like you, I want to document my experiences and share them with the world. After all, sharing is caring :)
By the end of this tutorial, you should have a local copy of your own blog. If you read the entire series, you should have a copy running in a hosted environment. And truth be told, you can use a lot of these steps to host any site you like. So lets dive right in.
Install Docker
First, you will need to install docker and docker-compose. I will start by getting the site running locally, and in the next post move it over to a server. Use what ever method makes sense for you to install docker. I run Arch Linux so I installed docker using the following command in a terminal.
sudo pacman -S docker docker-compose
From here I had to enable and start the docker service
sudo systemctl enable docker.service
sudo systemctl start docker.service
And finally add my user to the docker
group.
sudo gpasswd -a your_user_name docker
Just know, at the time of writing this, any user in the docker
group is root equivalent. See here
Project directory structure
Next you will want to create a folder to hold all the content for this project. I called mine blog
. Inside of blog
, create another folder called ghost
. Finally, inside of the ghost
folder, create one called content. This folders are needed for creating docker volume mounts later on. We want some of the data to live outside of docker so if the container is destroyed, all your blog post and settings don't disappear with it.
Nginx and Docker
Ok. Now for the fun part. In the blog
directory, create a file named docker-compose.yml
and open it in your favorite text editor. Add the following to the file
version: '3.5'
volumes:
nginx-conf:
nginx-vhost:
nginx-html:
services:
nginx:
image: nginx:1-alpine
container_name: nginx
restart: always
ports:
- 80:80
volumes:
- nginx-conf:/etc/nginx/conf.d
- nginx-vhost:/etc/nginx/vhost.d
- nginx-html:/usr/share/nginx/html
Here we have defined an nginx service that will be used as a reverse proxy. I know, nothing too special about it yet but trust me, the magic is coming. While we are here, I want to make a comment about the use of separate named volumes. This was intentional. The volumes are needed to persist the nginx configuration and share it with other containers. If you put all the data in one volume, the wrong file types will be in folders they should not and nginx will crash. I spent a whole day learning that the heard way. Here is a quick description of what will live at each mount point
nginx-vhost:etc/nginx/vhost.d
to change the configuration of vhosts (needed by Let's Encrypt in future post)nginx-html:usr/share/nginx/html
to write challenge files.
Automagically configure Nginx
Now that we have an nginx container defined, we need to configure nginx itself and tell it where to forward request. This could be done manually, but fortunately for you, I'm lazy, which means I found a better way. By the grace of the software gods, there is a tool called docker-gen
which can generate reverse proxy configs, and reload the nginx container when other containers are started and stopped. You can read more about it here.
So lets take advantage of this new awesomeness to configure nginx by appending the following to the docker-compose.yml
file
nginx_dockergen:
image: jwilder/docker-gen:0.7.3
container_name: nginx_dockergen
restart: always
depends_on:
- nginx
volumes:
- ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
- nginx-conf:/etc/nginx/conf.d
- nginx-vhost:/etc/nginx/vhost.d
- nginx-html:/usr/share/nginx/html
command:
"-notify-sighup nginx -watch -wait 5s:30s /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf"
A few things to note here. First, we are reusing a lot of the volumes from the previous service, and secondly, we are mounting the docker socket in this container. Docker-gen needs to be able to send stop and start signals to other containers. A lot of tutorials out there will suggest using the jwilder/nginx-proxy
docker image, which sets up nginx and docker-gen in the same container. Going that route means the docker socket will be bound to a publicly accessible container. I'm no security expert, but I personally did not feel comfortable doing that, which is why I split it into two separate containers.
Lastly, in order for docker-gen to create the config files for you, you will need to obtain the latest nginx template file. If you have curl installed, and are comfortable using the command line, navigate to the blog
directory inside the terminal and run this command.
curl https://raw.githubusercontent.com/jwilder/nginx-proxy/master/nginx.tmpl > /path/to/nginx.tmpl
You can also just paste the above URL in a web browser, copy the text from the page and paste it into a file named nginx.tmpl
.
Ghost setup
We are finally in the home stretch for this post. Now it is time to setup ghost. Once more, we will be adding an entry to the docker-compose.yml
file. Append the following
ghost:
image: ghost:2-alpine
container_name: ghost
restart: always
expose:
- '2368'
depends_on:
- nginx
- nginx_dockergen
volumes:
- ./ghost/content:/var/lib/ghost/content
environment:
url: http://localhost
NODE_ENV: development
VIRTUAL_HOST: localhost
From the text above, we have stated that this container will be listening on port 2368, but not publish it for request from the outside world. All containers being proxied should provide this value. It will be used by the nginx_dockergen
container to generate configs for nginx.
Next you will notice the volume ./ghost/content:/var/lib/ghost/content
. All ghost settings, themes, images, and the sqlite database will live in this directory. You definitely want all this data to persist if the container stop and is destroyed.
Lastly, the url
environment variable lets ghost know which URL is to be used to access the blog. The NODE_ENV
variable sets ghost in either development
or production
mode. I chose development since it is currently on the local machine. And finally, in order for docker-gen to do its thing, you need to add VIRTUAL_HOST
variable so it can use this value in the nginx config files. The value used here should be the same as domain to the site.
Start the blog
After all that, you should now be ready to start your new blog. Open a terminal and run this command in the blog
directory.
docker-compose build
followed by
docker-compose up
If everything worked correctly, you should be able to type http://localhost
into your browser and you should be greeted by your new blog!