It seems I can contain myself

· Guide

Docker is really useful. You’ve probably heard that, but let me show you exactly why.

The old-fashioned way

Before I got off my ass and took the 5 minutes to learn Docker, adding a post to this website went a little something a-like this:

  1. Have access to an SSH-capable machine (i.e.: my laptop or desktop)
  2. SSH into my (home-run) server
  3. Assemble words into an English language article using Markdown
  4. Run the Hugo command
  5. Copy the files to where Lighttpd looks for files

Now, it runs something like this:

  1. Log in to my Gitea instance
  2. Use Gitea’s online text file editor to write a post on my website’s source repo

That’s it. That’s the whole thing. Gitea is configured to detect pushes (i.e.: changed or added files) to my source code and send off a notification to a Webhook server. That server then automatically runs a containerised Hugo application that pulls my website source and generates ginuwine HTML from the Markdown files into the same directory that my new web server, Caddy, looks for files to serve.

Sound nice? Here’s how to do it. For this guide, I’ll be assuming you have a decent knowledge of the command line and that you’ll look things up if you don’t know what they are or how they work. I’ll explain how to put bits together, but I won’t delve into how Docker works, for example.

Before we begin

This guide is actually something of a supplement to my actual configuration files in my Git repo. It explains what each of the fields do so that you can tinker and tailor things yourself. I’ve done it this way to avoid writing 4000 words that still fail to adequately explain what to do. This way, you can learn by example. Sexy, I know.

Without further ado, here is how to use Docker to automatically build your static site, as well as set up Gitea so you can edit content files with an online editor.

Set up Docker

The first thing you’ll need to do is install Docker and Docker Compose using whatever package manager or other way of installing things your OS uses.

The moving parts

The main bits involved in this cocktail are:

Docker Compose is our mixer. It’ll let you co-ordinate multiple Docker containers using just one easy configuration file.


Head on over to the official Gitea Docker guide to check out what to put in your docker-compose.yml file for the Gitea bits.


Following the Gitea configuration as a guide, add a Caddy container to the docker-compose.yml file. Chuck it on the gitea network too, so that you can configure it to reverse proxy to your Gitea instance. To do that, you’ll need to make a Caddyfile (a Caddy config file), and add the stuff detailed over at the Gitea guide to reverse proxies. Make sure that your Caddy container knows where your Caddyfile is by adding it as a bind mount underneath its volumes section. You’ll also want to add a volume called something like site, and mount it somewhere useful in the Caddy container (I put mine at /site/).


We’re going to be using a custom image for our Webhook container so that we have Docker available inside the container (to interface with our host and tell it to make a Hugo container). To do that, write a small Dockerfile with the following lines:

FROM almir/webhook:latest RUN apk add --update docker CMD ["-verbose", "-hooks=/etc/webhook/hooks.json", "-hotreload"]

That makes a Docker image based off the Webhook image, installs the Docker client on the image, and sets some default parameters for the webhook command that gets called when we run the container.

To build this into an image, run docker build -t webhook ./. Then, head back to your docker-compose.yml file, and add a section for a Webhook container. As the image, you’ll just want to use webhook:latest, which will pull in your locally-generated Webhook image. Under its volumes section, add a bind mount for hooks.json to /etc/webhook/hooks.json (which is the location our command’s parameters are saying the configuration file will be for Webhook). You’ll also need to bind mount the Docker socket, which will allow the Webhook container to run containers on the actual host. Do that by adding /var/run/docker.sock:/var/run/docker.sock to the volumes section.

Finally, make sure it’s on the gitea network, too.

The command

Nearly there. Now, we need to write a script for Webhook to run, and write a webhook that tells Webhook to run that script. For the former, just write make a file called, and add the following:


docker run --rm --entrypoint "" -v DOCKERCOMPOSEDIR_site:/output -e HUGO_DESTINATION=/output klakegg/hugo:ext-alpine bash -c 'rm -rf /output/*; git clone "WEBSITE GIT REPO URL" /src/; hugo'

That’s a doozy. What this does is create a Docker container using the excellent klakegg/hugo image, with the volume that’s mounted to our Caddy container attached. This container will run a sequence of commands that will clean that volume of any files, pull your website’s source from whatever Git repo URL you feed it, and build it using Hugo. The output of Hugo will be stored back in the mounted volume. For this to work, you’ll need to change DOCKERCOMPOSEDIR to whatever you’ve named the directory where your docker-compose.yml file is. That’s because Docker prefixes volume names defined in a docker-compose.yml file with the name of the file’s parent directory. If you leave it out, it’ll create a new volume that Caddy isn’t using, and you won’t see your website updating at all.

Bind mount this file into /scripts/ on your Webhook container in docker-compose.yml.

The webhook

Last thing is to define a webhook for Webhook. I use a really simple one, and my hooks.json file looks like this:

		"id": "site-build",
		"execute-command": "/scripts/",
				"type": "value",
				"value": "ENTERSECRETVALUE",
					"source": "payload",
					"name": "secret"

Now, run your whole service using docker-compose up. Hopefully, you’ll be able to access your Gitea instance (reverse-proxied by Caddy) using whatever subdomain ( or subdirectory ( you’ve defined in your Caddyfile. Make an account on your Gitea instance, make a repo, upload your site’s files, and then head to the webhooks tab. Tell Gitea to go to http://webhook:9000/hooks/site-build on push events. Docker will make sure that containers on the same network (i.e.: the gitea network we set up) can resolve names, so when we say webhook, Gitea will be pointed in the right direction by Docker.

That’s it! Things should all be working. If they’re not, pay attention to the logs spat out when you run docker-compose up.

A huge mistake that I made was to forget to set the execute permission on my script, which meant my Webhook container kept complaining that it couldn’t find the command. It could find it, it just couldn’t execute it. You’ll need to set that permission on the actual file on your host. Don’t bother trying to do that within the Webhook container, let Docker deal with that mess.

When you’re ready to go, make sure all your containers are set to restart: always, and then run docker-compose up -d to run them detached (i.e.: as a background process) that will restart whenever they go down.

Now, try logging on to your Gitea instance and changing files to see how they get updated!