Nginx container, based on the Docker Official Nginx image image with acme.sh installed for free and automated Let's Encrypt SSL certificates.
For now, this image is based on the nginx:stable-alpine image, to make it easy for me to generate up to date images when new versions of the base Nginx images are released.
TODO: Using e.g. GitHub actions, automate the building and publishing of new images when new versions of the Nginx image are released. As part of this process, we should also add support for the other alpine based tags of the Nginx image.
TODO: It shouldn't be hard to enhance this repo to also support the Debian based Nginx image tags.
TODO: What about supporting the different architectures supported by the base Nginx image?
-
Create Docker volume that holds the /etc/nginx configuration directory so we can keep the container stateless
docker volume create nginx-acme-etc-vol
-
Create Docker volume that holds the nginx logs
docker volume create nginx-acme-log-vol
-
Create a new user-defined docker bridged network
docker network create nginx-acme-net
-
Start the container
docker run \ -d \ --restart unless-stopped \ --name nginx-acme \ --hostname nginx-acme \ --network nginx-acme-net \ --publish 80:80/TCP \ --publish 443:443/TCP \ -e ACCOUNT_CONF_PATH=/etc/nginx/ssl/acme.sh.conf \ -v nginx-acme-etc-vol:/etc/nginx \ -v nginx-acme-log-vol:/var/log/nginx \ mraming/docker-nginx-acme:stable-alpine
-
Edit the
ssl/acme.sh.conf
file that now resides on thenginx-acme-etc-vol
volume and update the email address. (On my Ubuntu 22.04 test system, this file can be found in/var/lib/docker/volumes/nginx-acme-etc-vol/_data/ssl
as root (sudo)) -
Generate Diffie-Hellman-Parameter to further improve security, we generate Diffie-Hellman parameter with 4096 bits. This will take a while (depending on hardware speed).
docker exec -it nginx-acme openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096
TODO: Automate this step so the dhparam.pem file is automatically generated the first time a container is started.
-
Configure an http server by adding a configuration file to the
conf.d
folder on thenginx-acme-etc-vol
volume. (see sample-conf.d/example.com.conf in this repository for a template). Note: You have toommit the https server section initially when generating the associated SSL certificate for the first time, otherwise Nginx will not load the configuration due to the missing certificate files.You probably also want to remove the
default.conf
file here.Note: Mozilla has a great SSL Configuration Generator tool to help generate this configuration and the settings that we have in our default ssl/ssl.conf file.
-
Test the new Nginx configuration and when no issues are found, reload it. Note: If you use DNS-01 based validation for your certificates, you can skip this set (and you don't have to ommit the https server configuration in the previous step; you can request the certificate first and then reload the Nginx configuration. For HTTP-01 validation, you have to first reload Nginx withou the HTTPS server configuration as described above.)
docker exec nginx-acme nginx -t docker exec nginx-acme nginx -s reload
-
Request the certificate from Let's Encrypt using one of the following commands:
When using HTTP-01 validation:
docker exec nginx-acme acme.sh --issue -w /var/www/example.com -d example.com --server letsencrypt
When using DNS-01 validation, for example using Hurricane Electric's free DNS service. This example assumes that the username and password are set using additional environment variables on the
docker run
command:-e HE_Username="MyHeNetUserName" \ -e HE_Password="LongAndSecureRandomPassword" \
The example below uses the Let's Encrypt staging CA - it's always a good idea to do your initial testing with the staging CA to prevent hitting rate limits for too many failed validations for example. Once you successfully obtained a certificate from the staging CA, you re-request the certificate from the production CA (which will require the --force parameter because the certificate is not due for renewal yet)
docker exec nginx-acme \ acme.sh --issue \ --dns dns_he \ -d example.com -d '*.example.com' \ --server letsencrypt_test \ --force \ --ocsp-must-staple
After the certificate has been renewed, Nginx must be reloaded again (test first):
docker exec nginx-acme nginx -t docker exec nginx-acme nginx -s reload
-
Clone the git repo on a Linux machine with Docker installed - I used WSL2 on my Windows 10 machine with Docker Desktop for Windows installed and configured for use with WSL2:
git clone https://github.com/mraming/docker-nginx-acme.git
-
Ensure you're logged in to the Docker Hub with the
docker login
command so you don't hit rate limits and can publish the image. -
Make the
alpline
folder (that holds theDockerfile
) the current directory -
Build the image with the following command, noting the --no-cache option to ensure the latest Nginx base image is downloaded
docker build --no-cache -t mraming/docker-nginx-acme .
Note: The first time I got an error message (
failed to solve with frontend dockerfile.v0: failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount494864957/Dockerfile: no such file or directory
); the solution/workaround was to use the following command to build the image. On subsequent attempts I used the command above without problems:DOCKER_BUILDKIT=0 docker build --no-cache -t mraming/docker-nginx-acme .
-
Tag the image you just build (adjust the version number to match the version for the current stable version of the Docker official Nginx image) and update the command as required:
docker tag mraming/docker-nginx-acme mraming/docker-nginx-acme:1.22.0-alpine docker tag mraming/docker-nginx-acme mraming/docker-nginx-acme:stable-alpine
-
Finally, you can publish the image in Docker Hub with the following commands:
docker push mraming/docker-nginx-acme:1.22.0-alpine docker push mraming/docker-nginx-acme:stable-alpine