- Overview
- Initial setup
- Prerequisites
- Step 1 - Create DNS records
- Step 2 - Copy static content or define upstream service
- Step 3 - Perform an initial setup using the CLI tool
- Step 4 - Start the services
- Step 5 - Verify that HTTPS works with the test certificates
- Step 6 - Switch to a Let's Encrypt production environment
- Step 7 - Verify that HTTPS works with the production certificates
- Adding new domains without downtime
- Removing existing domains without downtime
- Manually renewing all Let's Encrypt certificates
- Running on a local machine not directed to by DNS records
- Advanced Nginx configuration
- Running Docker containers as a non-root user
- SSL configuration for A+ rating
Nginx and Let’s Encrypt with Docker Compose in less than 3 minutes.
This example automatically obtains and renews Let's Encrypt free SSL/TLS certificates and sets up HTTPS in Nginx for multiple domain names using Docker Compose.
You can run Nginx with IPv4, IPv6, HTTP/1.1, and HTTP/2 support and set up HTTPS with Let's Encrypt TLS certificates for your domain names and get an A+ rating in SSL Labs SSL Server Test using Docker Compose and letsencrypt-docker-compose interactive CLI tool.
Let's Encrypt is a certificate authority that provides free X.509 certificates for TLS encryption. The certificates are valid for 90 days and can be renewed. Both initial creation and renewal can be automated using Certbot.
When using Kubernetes Let's Encrypt TLS certificates can be easily obtained and installed using cloud native certificate management solutions. For simple websites and applications, Kubernetes is too much overhead and Docker Compose is more suitable. But for Docker Compose there is no such popular and robust tool for TLS certificate management.
The project supports separate TLS certificates for multiple domain names.
The idea is simple. There are three main services:
nginx
,certbot
for obtaining and renewing certificates,cron
for triggering certificates renewal,
and one additional service cli
for interactive configuration.
The sequence of actions:
- You perform an initial setup with letsencrypt-docker-compose CLI tool.
- Nginx generates self-signed "dummy" certificates to pass ACME challenge for obtaining Let's Encrypt certificates.
- Certbot waits for Nginx to become ready and obtains certificates.
- Cron triggers Certbot to try to renew certificates and Nginx to reload configuration daily.
- Docker and Docker Compose are installed
- You have a domain name
- You have a server with a publicly routable IP address
- You have cloned this repository (or created and cloned a fork):
git clone https://github.com/evgeniy-khist/letsencrypt-docker-compose.git
For simplicity, this example deals with domain names a.evgeniy-khyst.com
and b.evgeniy-khyst.com
,
but in reality, domain names can be any (e.g., example.com
, anotherdomain.net
).
For all domain names create DNS A or AAAA record, or both to point to a server where Docker containers will be running.
Also, create CNAME records for the www
subdomains if needed.
DNS records
Type | Hostname | Value |
---|---|---|
A | a.evgeniy-khyst.com |
directs to IPv4 address |
A | b.evgeniy-khyst.com |
directs to IPv4 address |
AAAA | a.evgeniy-khyst.com |
directs to IPv6 address |
AAAA | b.evgeniy-khyst.com |
directs to IPv6 address |
CNAME | www.a.evgeniy-khyst.com |
is an alias of a.evgeniy-khyst.com |
CNAME | www.a.evgeniy-khyst.com |
is an alias of a.evgeniy-khyst.com |
Nginx can be configured
- to serve static content,
- as a reverse proxy (e.g., proxying all requests to a backend server).
Copy your static content to html/${domain}
directory.
cp -R ./examples/html/ ./html/a.evgeniy-khyst.com
The docker-compose.yml
contains the example-backend
service.
It's a simple Node.js web app listening on port 8080.
Replace it with your backend service or remove it.
services:
example-backend:
build: ./examples/nodejs-backend
image: evgeniy-khyst/expressjs-helloworld
restart: unless-stopped
Run the CLI tool and follow the instructions to perform an initial setup.
docker compose run --rm cli
On the first run, choose to obtain a test certificate from a Let's Encrypt staging server. We will switch to a Let's Encrypt production environment after verifying that HTTPS is working with the test certificate.
On the first run, build the services.
docker compose build
Start the services.
docker compose up -d
Check the logs.
docker compose logs -f
For each domain wait for the following log messages:
Switching Nginx to use Let's Encrypt certificate
Reloading Nginx configuration
For each domain, check https://${domain}
and https://www.${domain}
if you configured the www
subdomain.
Certificates issued by (STAGING) Let's Encrypt
are considered not secure by browsers and cURL.
curl --insecure https://a.evgeniy-khyst.com
curl --insecure https://www.a.evgeniy-khyst.com
curl --insecure https://b.evgeniy-khyst.com/hello?name=Eugene
curl --insecure https://www.b.evgeniy-khyst.com/hello?name=Eugene
Run the CLI tool, choose Switch to a Let's Encrypt production environment
and follow the instructions.
docker compose run --rm cli
For each domain, check https://${domain}
and https://www.${domain}
if you configured the www
subdomain.
Certificates issued by Let's Encrypt
are considered secure by browsers and cURL.
curl https://a.evgeniy-khyst.com
curl https://www.a.evgeniy-khyst.com
curl https://b.evgeniy-khyst.com/hello?name=Eugene
curl https://www.b.evgeniy-khyst.com/hello?name=Eugene
Optionally check your domains with SSL Labs SSL Server Test and review the SSL Reports.
The cron
service will automatically renew the Let's Encrypt production certificates when the time comes.
Create DNS A or AAAA record, or both.
Also, create CNAME record for www
subdomain if needed.
DNS records
Type | Hostname | Value |
---|---|---|
A | c.evgeniy-khyst.com |
directs to IPv4 address |
AAAA | c.evgeniy-khyst.com |
directs to IPv6 address |
CNAME | www.c.evgeniy-khyst.com |
is an alias of c.evgeniy-khyst.com |
Repeat the actions described in the subsection of the same name in the "Initial setup" section.
Run the CLI tool, choose Add new domains
and follow the instructions.
docker compose run --rm cli
For each new domain, check https://${domain}
and https://www.${domain}
if you configured the www
subdomain.
Run the CLI tool, choose Remove existing domains
and follow the instructions.
docker compose run --rm cli
You can manually renew all of your certificates.
Certbot renewal will be executed with --force-renewal
flag that causes the expiration time of the certificates to be ignored when considering renewal, and attempts to renew each and every installed certificate regardless of its age.
This operation is not appropriate to run daily because each certificate will be renewed every day, which will quickly run into the Let's Encrypt rate limit.
Run the CLI tool, choose Manually renew all Let's Encrypt certificates (force renewal)
and follow the instructions.
docker compose run --rm cli
Running Certbot on a local machine not directed to by DNS records makes no sense because Let’s Encrypt servers will fail to validate that you control the domain names in the certificate.
But it may be useful to run all services locally with disabled Certbot. It is possible in dry run mode.
docker compose run --rm cli
Enable dry run mode by setting the environment variable DRY_RUN=true
.
DRY_RUN=true docker compose up -d
You can configure Nginx by manually editing the nginx-conf/nginx.conf
.
Configure virtual hosts (server
blocks) by editing the nginx-conf/conf.d/${domain}.conf
.
Any .conf
file from the nginx-conf/conf.d
directory is included in the Nginx configuration.
For example, to declare upstream servers, edit nginx-conf/conf.d/upstreams.conf
upstream backend {
server backend1.example.com:8080;
server backend2.example.com:8080;
}
After editing the Nginx configuration, do a hot reload of the Nginx configuration.
docker compose exec --no-TTY nginx nginx -s reload
Manual edits of the nginx-conf/nginx.conf
and nginx-conf/conf.d/${domain}.conf
are lost after running the CLI tool
(e.g., adding or removing domains or switching to a Let's Encrypt production environment).
The CLI tool generates the Nginx configuration files based on the config.json
.
To make Nginx configuration changes persistent, edit the Handlebars templates used for their generation
By default, Docker is only accessible with root privileges (sudo
).
The CLI tool creates the following files in the hosts' project root directory mounted into the container:
config.json
,nginx-conf/nginx.conf
,nginx-conf/conf.d/${domain}.conf
.
These files will be owned by the root
user.
When non-root users try to clean up or edit these files, they get the "permission denied" error.
If you want to use Docker as a regular user, you need to add your user to the docker
group.
To make the CLI tool create files in a way that allows non-root users to edit and delete them,
tell Docker Compose to run as the current user instead of root
.
As the CLI tool runs Docker Compose commands internally, specify the docker
group as a supplementary group,
so the user inside the container will be its member.
We have to use user IDs and group IDs because containers don't know their associated usernames and group names.
Run the CLI tool specifying the current user and docker
group to make it create files owned by the current user.
CURRENT_USER="$(id -u):$(id -g)" DOCKER_GROUP="$(getent group docker | cut -d: -f3)" docker compose run --rm cli
SSL in Nginx is configured accoring to best practices to get A+ rating in SSL Labs SSL Server Test.
Read more about the best practices and rating: