Introduction
In a previous blog post, I presented a solution to use docker-compose to obtain and renew a Let’s Encrypt SSL certificate and configure NGINX to use it. The solution depended on using two docker-compose files, one for the initialisation and the second for operation, as well as a cron job, and a couple of very simple shell scripts.
In this blog post, I will present a solution that was developed by Jason Wilder that is entirely docker-based and that eliminates the need for the shell scripts and the cron job. The docker images nginx-proxy and acme-companion are both open-source projects initiated and maintained by Jason Wilder and are hosted on GitHub under https://github.com/nginx-proxy/nginx-proxy and https://github.com/nginx-proxy/acme-companion respectively. It is worth it to mention that at the time of writing this blog post, Nicolas Duchon is the maintainer of the nginx-proxy repository of Github.
Before we start, I have to mentioned that this solution requires sharing the Docker socket file of the host machine with one of the docker containers which goes against the recommendations of OWASP regarding Docker security. I wrote a blog post related to this issue, that also presents a slightly more secure solution.
The Solution
For the sake of simplicity, and to make comparison easier, I will use the same example I used for the in the previous related blog post, which involved deploying an instance of Matomo. . Let’s start with the docker-compose.yaml we developed in that post, and introduce nginx-proxy and acme-companions.
version: "3"
services:
matomo:
image: matomo
container_name: matomo
restart: always
depends_on:
- db
volumes:
- ./matomo:/var/www/html
db:
image: mariadb
container_name: db
command: --max-allowed-packet=64MB
restart: always
environment:
- MARIADB_DATABASE=matomo
- MARIADB_USER
- MARIADB_PASSWORD
- MARIADB_ROOT_PASSWORD
volumes:
- ./db:/var/lib/mysql
nginx:
container_name: nginx
image: nginx:latest
restart: unless-stopped
environment:
- DOMAIN
depends_on:
- matomo
ports:
- 80:80
- 443:443
volumes:
- ./etc/nginx/templates:/etc/nginx/templates:ro
- ./etc/letsencrypt:/etc/letsencrypt:ro
- ./certbot/data:/var/www/certbot
certbot:
container_name: certbot
image: certbot/certbot:latest
depends_on:
- nginx
command: >-
certonly --reinstall --webroot --webroot-path=/var/www/certbot
--email ${EMAIL} --agree-tos --no-eff-email
-d ${DOMAIN}
volumes:
- ./etc/letsencrypt:/etc/letsencrypt
- ./certbot/data:/var/www/certbot
Now we need to replace Nginx, and Certbot with nginx-proxy and acme-companion. Changes must be made also to the Matomo service where some necessary environment variables must be added. Changes are highlighted in the following code block.
version: "3"
services:
matomo:
image: matomo
container_name: matomo
restart: always
environment:
VIRTUAL_HOST: ${THE_DOMAIN_NAME}
VIRTUAL_PORT: ${THE_PORT_MATOMO_IS_LISTENING_FOR}
LETSENCRYPT_HOST: ${THE_DOMAIN_NAME}
LETSENCRYPT_EMAIL: ${THE_EMAIL_USED_FOR_LETS_ENCRYPT}
depends_on:
- db
volumes:
- matomo:/var/www/html
db:
image: mariadb
container_name: db
command: --max-allowed-packet=64MB
restart: always
environment:
- MARIADB_DATABASE=matomo
- MARIADB_USER
- MARIADB_PASSWORD
- MARIADB_ROOT_PASSWORD
volumes:
- db:/var/lib/mysql
proxy:
image: nginxproxy/nginx-proxy:alpine
restart: always
ports:
- 80:80
- 443:443
labels:
com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"
volumes:
- certs:/etc/nginx/certs:ro
- vhostd:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
# WARNING: OWASP discorages sharing /var/run/docker.sock even in read-only mode
# Read this: https://blog.jarrousse.org/2024/09/01/a-slightly-more-secure-docker-first-solution/
- /var/run/docker.sock:/tmp/docker.sock:ro
networks:
- proxy-tier
- default
letsencrypt-companion:
image: nginxproxy/acme-companion
restart: always
volumes:
- certs:/etc/nginx/certs
- acme:/etc/acme.sh
- vhostd:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy-tier
depends_on:
- proxy
networks:
proxy-tier
volumes:
matomo
db:
certs:
acme:
vhostd:
html:
The environment variables in the WebApp backend, in this case matomo, are necessary for nginx-proxy, and acme-companion to obtain the ssl certificate from Let’s Encrypt and to setup the reverse proxy the the WebApp:
environment:
VIRTUAL_HOST: your_matomo_server.com
VIRTUAL_PORT: 80
LETSENCRYPT_HOST: your_matomo_server.com
LETSENCRYPT_EMAIL: email@your_matomo_server.com
That’s it. Now it is only the matter of running docker compose.
$ docker-compose up -d
Conclusion
This solution is much simpler than the one suggested in my previous post, but comes at the expense of clarity and control… and the and the additional risk of going against an OWASP recommendation, as well as running code maintained by a person rather than an organisation.