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.
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
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.dto change the configuration of vhosts (needed by Let's Encrypt in future post)
nginx-html:usr/share/nginx/htmlto 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
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
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.
url environment variable lets ghost know which URL is to be used to access the blog. The
NODE_ENV variable sets ghost in either
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
If everything worked correctly, you should be able to type
http://localhost into your browser and you should be greeted by your new blog!