Skip to content

Nginx and Let’s Encrypt with Docker Compose in less than 3 minutes

License

Notifications You must be signed in to change notification settings

techgrind/letsencrypt-docker-compose

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

letsencrypt-docker-compose

Overview

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:

  1. You perform an initial setup with letsencrypt-docker-compose CLI tool.
  2. Nginx generates self-signed "dummy" certificates to pass ACME challenge for obtaining Let's Encrypt certificates.
  3. Certbot waits for Nginx to become ready and obtains certificates.
  4. Cron triggers Certbot to try to renew certificates and Nginx to reload configuration daily.

Back to top

Initial setup

Prerequisites

  1. Docker and Docker Compose are installed
  2. You have a domain name
  3. You have a server with a publicly routable IP address
  4. You have cloned this repository (or created and cloned a fork):
    git clone https://github.com/evgeniy-khist/letsencrypt-docker-compose.git

Step 1 - Create DNS records

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

Step 2 - Copy static content or define upstream service

Nginx can be configured

  • to serve static content,
  • as a reverse proxy (e.g., proxying all requests to a backend server).

Static content

Copy your static content to html/${domain} directory.

cp -R ./examples/html/ ./html/a.evgeniy-khyst.com

Reverse proxy

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

Step 3 - Perform an initial setup using the CLI tool

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.

letsencrypt-docker-compose CLI initial setup

Step 4 - Start the services

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

Step 5 - Verify that HTTPS works with the test certificates

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

Step 6 - Switch to a Let's Encrypt production environment

Run the CLI tool, choose Switch to a Let's Encrypt production environment and follow the instructions.

docker compose run --rm cli

letsencrypt-docker-compose CLI switch to a Let's Encrypt production environment

Step 7 - Verify that HTTPS works with the production certificates

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.

Back to top

Adding new domains without downtime

Step 1 - Create new DNS records

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

Step 2 - Copy static content or define upstream service

Repeat the actions described in the subsection of the same name in the "Initial setup" section.

Step 3 - Update the configuration using the CLI tool

Run the CLI tool, choose Add new domains and follow the instructions.

docker compose run --rm cli

Step 4 - Verify that HTTPS works

For each new domain, check https://${domain} and https://www.${domain} if you configured the www subdomain.

Back to top

Removing existing domains without downtime

Run the CLI tool, choose Remove existing domains and follow the instructions.

docker compose run --rm cli

Back to top

Manually renewing all Let's Encrypt certificates

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

Back to top

Running on a local machine not directed to by DNS records

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.

Step 1 - Perform an initial setup using the CLI tool

docker compose run --rm cli

Step 2 - Start the services in dry run mode

Enable dry run mode by setting the environment variable DRY_RUN=true.

DRY_RUN=true docker compose up -d

Back to top

Advanced Nginx configuration

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

Back to top

Running Docker containers as a non-root user

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

Back to top

SSL configuration for A+ rating

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:

Back to top

About

Nginx and Let’s Encrypt with Docker Compose in less than 3 minutes

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 59.6%
  • Shell 25.2%
  • Handlebars 9.8%
  • Dockerfile 5.4%