A simple way to host your own blog, if you don’t want to rely on 3rd party services such as Medium, is to generate static files using something like Jekyll or Gatsby, which you can then host on services like GitHub Pages or on your own server. There are plenty of tutorials on how to do that.

What I found more tricky was how to set it up in such a way that I can use one machine to host multiple Jekyll websites behind a single NGINX instance, which acts as a proxy. Also, I wanted the files to be served over HTTPS using the free certificates generated by Let’s Encrypt. And all this with using the least amount of configuration, so I can easily replicate the setup on multiple machines.

The basic setup

Say we have a plain VPS with Ubuntu 20.04.

We first create a user that will run our docker containers: adduser www && usermod -aG sudo www.

I won’t go through the steps of installing Docker and Docker Compose, as you can find them here and here.

The magic

What we need in order to make this happen is the docker-compose-letsencrypt-nginx-proxy-companion. This provides us with a Docker Compose file which runs NGINX and, in addition to that, whenever another service joins its network, it will automatically generate Let’s Encrypt certificates and update the NGINX configuration to forward the traffic to the virtual host.

As complicated as it sounds, it is actually very easy to set up.

Basically just clone the repo, then cp .env.sample .env and edit .env to set the public IP address, then run ./start.sh.

The Jekyll containers

Now for every Jekyll website we want to serve, we need a Dockerfile that looks like this:

FROM jekyll/builder AS dev_builder

WORKDIR /tmp

COPY Gemfile Gemfile*.lock ./

RUN bundle install

COPY . .

ENV JEKYLL_ENV=production
RUN jekyll build

FROM nginx:alpine AS prod_serv

COPY --from=dev_builder /tmp/_site /usr/share/nginx/html

EXPOSE 80

This Dockerfile basically builds the static files, then copies the output to an NGINX image that serves them over the port 80 of the container.

To build the container using the above Dockerfile, use docker build -t USER/PROJNAME .. Replace USER/PROJNAME with your Docker Hub username and a project name.

To start the image, do docker run -d -e VIRTUAL_HOST=FQDN --network=webproxy --name=PROJNAME -e LETSENCRYPT_HOST=FQDN -e LETSENCRYPT_EMAIL=EMAIL USER/PROJNAME. FQDN is the fully qualified domain name, like example.com, without http, EMAIL should be a valid email address and USER/PROJNAME should match the ones used in the build command above.

When running the command in this way, we are basically making the container join the network named webproxy, which is the network used by the NGINX proxy companion. Note that we are passing the FQDN twice: once to be used in the NGINX configuration file as a virtual host and once for obtaining the SSL certificate from Let’s Encrypt.

To stop the container, run docker stop PROJNAME.

Conclusion

I find this setup beautiful because I don’t need to deal with configuration files or with generating SSL certificates myself. Everything is automatically taken care of. Also, once this is up and running, adding a new website is trivial: all that’s needed is the Dockerfile, which can be just copied without any changes, and then the website can be rebuilt and started/stopped using simple Docker commands.