Combine Certbot and Nginx Into One Container
21 Jan 2025I have had some issues with my previous Nginx and certbot install. Nginx will not use the new updated certificates from certbot unless I stop and start the docker container again. So now I’m going to combine the Nginx and certbot into one docker container and see if that will fix my issues.
Custom docker image
Let’s start with the custom docker image. Considering my research on the topic I have decided to start from the certbot image and add Nginx to it. Because it looks like it’s an easier install to use the certbot image.
nginx-img/Dockerfile
FROM certbot/certbot:v3.1.0
VOLUME /etc/letsencrypt
EXPOSE 443
EXPOSE 80
# Install some extra packages
RUN apk update && apk add openssl curl tzdata rsyslog nginx
# Copy nginx-certbot.conf for ACME challenge folder, will be used in server {} config
COPY nginx-certbot.conf /etc/nginx/
# Disable imklog in rsyslog, due to no access to /proc/kmsg in container
RUN sed -i '/imklog/s/^/#/' /etc/rsyslog.conf
# Make sure certbot folder exists
RUN mkdir -p /var/www/certbot
# Add crontab for running certbot
ADD crontab /etc/crontabs
# Copy scripts
COPY *.sh /opt/
RUN chmod +x /opt/entrypoint.sh
RUN chmod +x /opt/certbot_renew.sh
ENTRYPOINT [ "/opt/entrypoint.sh" ]
So now we have the Dockerfile for our custom nginx docker image finished. But we need to create few scripts as well.
We begin with the shortest one, crontab
nginx-img/crontab
# Run at 03:00 every day
0 3 * * * /opt/certbot_renew.sh
for this cron job to work we need the certbot_rewnew.sh
script
nginx-img/certbot_renew.sh
#!/bin/sh
echo "----------------------------------------"
echo "Running certbot_renew"
echo $(date)
echo
if [ "$1" == "--dry-run" ]; then
echo "Dry run"
certbot renew --dry-run --webroot -w /var/www/certbot
else
certbot renew --webroot -w /var/www/certbot 2&>1 > /var/log/certbot.log
fi
This script is just a simple certbot renew
line, I like to do it like this so the crontab is easier to read.
This way I could add the --dry-run
option as well, very useful when testing the container and certbot.
To make the container run, we need to create the entrypoint.sh
script
nginx-img/entrypoint.sh
#!/bin/sh
echo $(date)
echo "nginx and certbot container started"
echo "----------------------------------------"
# Make sure the cron.log exists
touch /var/log/cron.log
# Lets start crond for perodic updates
exec rsyslogd &
exec crond &
# Now start nginx
nginx -g 'daemon off;'
Just a simple script to make sure the /var/log/cron.log exists before we start syslog and cron, and finally starts nginx.
Finally I have added a nginx-certbot.conf
that can be included in my nginx server {}
blocks for the ACME challenge.
nginx-img/nginx-certbot.conf
location ^~ /.well-known/acme-challenge/ {
allow all;
default_type "text/plain";
root /var/www/certbot;
}
# Hide /acme-challenge sub directory
location = /.well-known/acme-challenge/ {
return 404;
}
Now the folder structure would have 1 sub folder and 5 files
nginx/
├─ nginx-img/
│ ├─ Dockerfile
│ ├─ crontab
│ ├─ certbot_renew.sh
│ ├─ entrypoint.sh
│ ├─ nginx-certbot.conf
Docker compose
Now we can finally create the docker-compose file.
This is a pretty simple docker compose file needed to build the container
docker-compose.yml
services:
nginx:
build: nginx-img/. # Build custom certbot with nginx image
container_name: nginx
restart: always
environment:
- TZ=Europe/Stockholm
ports:
- "80:80"
- "443:443"
volumes:
- ./user-data/letsencrypt:/etc/letsencrypt # Certificates
# nginx config
- ./user-data/conf.d:/etc/nginx/conf.d # nginx conf.d
- ./user-data/html:/var/www/html # html root
I have some defined volumes
./user-data/letsencrypt:/etc/letsencrypt
- Folder to save the certificates generated by certbot./user-data/conf.d:/etc/nginx/conf.d
- nginx server config files./user-data/html:/var/www/html
- html root
You can add an extra line if you need a custom nginx.conf
./user-data/nginx.conf:/etc/nginx/nginx.conf
Now you can build the image by running
docker compose build
Folder structure with the new files and folders
nginx/
├─ nginx-img/
│ ├─ Dockerfile
│ ├─ crontab
│ ├─ certbot_renew.sh
│ ├─ entrypoint.sh
│ ├─ nginx-certbot.conf
├─ user-data/
│ ├─ letsencrypt/
│ ├─ conf.d/
│ ├─ html/
├─ docker-compose.yml
Setup the site
To create new certificates with certbot for your site you can run this command
docker compose run --rm -p 80:80 -p 443:443 nginx certbot certonly --standalone -d www.example.com -d example.com --non-interactive --agree-tos --email your-email@example.com --expand
A basic server config for nginx and the host example.com
user-data/conf.d/default.conf
server {
listen 443 ssl;
http2 on;
server_name www.example.com example.com;
ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
# Include for certbot ACME challenge
include /etc/conf.d/nginx-certbot.conf;
location / {
root /var/www/html;
index index.html;
}
}
server {
listen 80;
server_name www.example.com example.com;
# Just redirect to https
location / {
return 301 https://$host$request_uri;
}
}
A simple page to serve for testing
user-data/html/index.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
Hello world!
Using https and certificates with certbot
</body>
</html>
Now the folder structure will look like this, with a bunch of certificate files under user-data/letsencrypt
nginx/
├─ nginx-img/
│ ├─ Dockerfile
│ ├─ crontab
│ ├─ certbot_renew.sh
│ ├─ entrypoint.sh
│ ├─ nginx-certbot.conf
├─ user-data/
│ ├─ letsencrypt/
│ ├─ conf.d/
│ │ ├─ default.conf
│ ├─ html/
│ │ ├─ index.html
├─ docker-compose.yml
Finally we can run the new combine nginx and certbot container
docker compose up -d