New setup of Containerized websites on the same server:  Nginx-Reverse Proxy + Let's Encrypt

The goal of the new setup was the following:

  • stable
  • simple
  • easily customizable

Basic setup:

All websites have their own container (or multiple if really needed), this limits their scope and also allows for turning, tweaking specific websites without it messing up for other websites on the same server.
Each container which is meant to be publicly accessible in the browser should be open on the port 80 within a shared network with the reverse proxy.
The reverse proxy is responsible for all http/https traffic going in and out of the server and is also the place where we add config to connect to each of the websites and the SSL-certificates for https using certbot.

With that covered, let's actually make this work, let's start with a very basic website. This now turns into a tutorial which assumes you already have a server that is set up, configured DNS and docker and docker-compose are installed.

Initial website

To separate each website I recommed making a separate folder for each website.

First let's set up a docker-compose.yml and run docker-compose up in deamon-mode: docker-compose up -d.

version: '3.7'
services:
server:
    image: nginx:latest
  container_name: a_nice_webserver
  ports:
      - "80:80"
    volumes:
      - ./app:/usr/share/nginx/html

Note, you could also make a volume for /etc/nginx/conf.d/ to manage the nginx-config of that server specifically, however for this the default config is perfectly fine.

Put a basic html file there using: echo "Hello world!" >> app/index.html

This should make your website accessible and show Hello world!

Reverse Proxy + your webserver

First remove your old container:

docker stop aNiceWebServer && docker rm aNiceWebServer

then replace the docker-compose.yml and run docker compose up -d again.

version: '3.7'
services:
server:
    image: nginx:latest
  container_name: aNiceWebServer
    restart: unless-stopped
  expose:
      - "80:80"
    volumes:
      - ./app:/usr/share/nginx/html

in a different folder let's set up the reverse proxy, again make docker-compose.yml and run docker compose up -d again. This time with the following content:

version: '3.7'
services:
  reverse-proxy:
    image: nginx:latest
    container_name: reverse-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./proxy/conf:/etc/nginx/conf.d
    - ./logs:/var/log/nginx
    - ./letsencrypt:/etc/letsencrypt

after this we should add the following file, proxy/a_nice_webserver.conf, be sure to replace the <domain_name> accordingly:

server {
server_name <domain_name>;
access_log /var/log/nginx/<domain_name>-access.log;
error_log  /var/log/nginx/<domain_name>-error.log warn;
proxy_buffering off;
  location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
              proxy_pass http://aNiceWebServer;
  }
}

Note that the proxy-pass refers to the container-name of the webserver we have, this only works if they are on the same docker-network. It might seem like an option to make this a part of the docker-compose file, this does NOT work, it will prefix its name with something such that each docker-compose file has its own scope (which is a good default btw). 

To activate this config use docker exec -ti reverse-proxy nginx -t to verify its correctness and docker exec -ti reverse-proxy nginx -s reload to apply it

Let's create a network that we put both on, for instance anicewebservernetwork, it might be tempting to use bridge, that is already a thing for legacy reasons, do not use that name.

docker network create  anicewebservernetwork
docker network connect anicewebservernetwork aNiceWebServer
docker network connect anicewebservernetwork reverse-proxy

Now it should work once more and you will be greeted with the old "hello world". You might have noticed the odd long name for the network, if you want more websites on the same server you simply repeat this process. Especially if you are to open more ports it is nice to put each website-container on its dedicated network for interfacing with the reverse proxy for security-reasons (this is at least part of the reason why this exists in the first place).

With this fully working on http we can switch to https:

We install certbot, certbot-nginx-extension and let it fetch a certificate and apply its automatic changes to our proxy-config. We do this by executing the following commands (apt because the container is based on debian):

docker exec -ti reverse-proxy apt update -y
docker exec -ti reverse-proxy apt install certbot python3-certbot-nginx -y
docker exec -ti reverse-proxy certbot --nginx -d <domain>

At the last of these commands you need to put in your details for Let's encrypt to properly go through the process.

To keep the certificates valid, set up a cron-job that updates the certificates automatically, for example each 2 months (they are valid for 3 months) with the following command inside:

docker exec -ti reverse-proxy certbot renew

Having executed this your website should now be up and running on https, with a redirect to the http version to https, enjoy!