Install letsecrypt certificates on all your docker containers

juanlu_sanz

Juan Luis Sanz

Posted on February 9, 2022

Install letsecrypt certificates on all your docker containers

When dealing with containers running on a same server, you may sometimes run into the following problem:

How do I give each of the containers its own domain/subdomain? and how do I do it securely?

First of all, this problem can be addressed in several ways, and neither is more correct than another. I will simply show you a quick and easy way to do this using:

  • Docker
  • Traefik
  • Letsencrypt

and doing so automatically.

Setup

Let's pretend we want to setup two services:

Service domain port
Grafana grafana.mydomain.com 80, 443
InfluxDB influx.mydomain.com 8086

⚠ This setup is just for testing purposes: any docker container will work with this method (wordpress, mariaDB, mongoDB, ELK stack, Minecraft server, custom images...)

  • Grafana: an amazing monitoring tool which can display data from an impressive array of sources, including influxDB. Let's say we want to monitor some service we have setup elsewhere!
  • InfluxDB: a time-based DB, in which we can store time events (for example, the temperature of the CPU every n seconds or the number of IO operations on our drives)

How do I give each container its own domain/subdomain?

Well that depends on the setup you are running:

a) I run it all from a server with a public, static IP like a VPS

Easy! just head over to your DNS settings, and create an A record for each domain/subdomain. In this case both will point to the same IP address.

A slightly less repetitive approach would be to point one of the domain to the IP address via an A record and any other domains point towards that domain via CNAME records. That way if the IP address changes, you only have to do it once!

b) I run it from a public IP, but it changes every now and then

This scenario would apply if for instance you want to host from your home and the public IP address your ISP assigns you changes periodically. To circumvent this there are several possibilities which I won't go into detail, but they're easy to follow. IT boils down to having your server at home communicating with your DNS provider, telling them your public IP each time it changes, so the DNS entry can be changed accordingly.

Don't worry no need to do this by hand, just look this up online for a quickstart.

  • Use a service like duckdns
  • Cloudfare dynamic IP service

How would my server know to which container to point to, if all DNS entries resolve to the same IP address?

That's where traefik comes in!

Step 1) Create a docker-compose.yml file for traefik

Here a small template, change parameters accordingly!

version: "3"

services:
  traefik:
    image: traefik
    container_name: traefik
    ports:
      - "80:80"         # HTTP port for grafana
      - "443:443"       # HTTPS port for grafana
      - "8086:8086"     # Exposed port for influxDB

      # expose port below only if you need access to the Traefik API
      #- "8080:8080"
    command:
      #- "--log.level=DEBUG"
      #- "--api=true"
      - "--providers.docker=true"

      - "--entryPoints.port443.address=:443"
      - "--entryPoints.port80.address=:80"
      - "--entryPoints.port8086.address=:8086"

      - "--certificatesResolvers.le-ssl.acme.tlsChallenge=true"
      - "--certificatesResolvers.le-ssl.acme.email=myemail@mydomain.com"
      - "--certificatesResolvers.le-ssl.acme.storage=/letsencrypt/acme.json"
    volumes:
      - traefik-data:/letsencrypt/
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always

volumes:
  traefik-data:

Enter fullscreen mode Exit fullscreen mode

Simple, isn't it? if you run it with

docker-compose up
Enter fullscreen mode Exit fullscreen mode

you will notice not a lot has changed, and that's because we need to set the subdomains themselves on the container definitions.

Step 2) Add each container's particular configuration

So, for example, grafana and influxDB could look like so

version: "3"

services:
  influx:
    image: influxdb:1.7 # or any other recent version
    labels:
      # SSL endpoint
      - "traefik.http.routers.influx-ssl.entryPoints=port8086"
      - "traefik.http.routers.influx-ssl.rule=host(`influx.mydomain.com`)"
      - "traefik.http.routers.influx-ssl.tls=true"
      - "traefik.http.routers.influx-ssl.tls.certResolver=le-ssl"
      - "traefik.http.routers.influx-ssl.service=influx-ssl"
      - "traefik.http.services.influx-ssl.loadBalancer.server.port=8086"
    restart: always
    container_name: influxdb
    volumes:
      - influx-data:/var/lib/influxdb
    environment:
      # - INFLUXDB_DB=
      - INFLUXDB_HTTP_ENABLED=true
      - INFLUXDB_HTTP_AUTH_ENABLED=true

      # - INFLUXDB_ADMIN_USER=
      # - INFLUXDB_ADMIN_PASSWORD=

  grafana:
    labels:
      # SSL redirect requires a separate router (https://github.com/containous/traefik/issues/4688#issuecomment-477800500)
      - "traefik.http.routers.grafana.entryPoints=port80"
      - "traefik.http.routers.grafana.rule=host(`grafana.mydomain.com`)"
      - "traefik.http.middlewares.grafana-redirect.redirectScheme.scheme=https"
      - "traefik.http.middlewares.grafana-redirect.redirectScheme.permanent=true"
      - "traefik.http.routers.grafana.middlewares=grafana-redirect"
      # SSL endpoint
      - "traefik.http.routers.grafana-ssl.entryPoints=port443"
      - "traefik.http.routers.grafana-ssl.rule=host(`grafana.mydomain.com`)"
      - "traefik.http.routers.grafana-ssl.tls=true"
      - "traefik.http.routers.grafana-ssl.tls.certResolver=le-ssl"
      - "traefik.http.routers.grafana-ssl.service=grafana-ssl"
      - "traefik.http.services.grafana-ssl.loadBalancer.server.port=3000"
    image: grafana/grafana
    container_name: grafana
    restart: always
    volumes:
      - grafana-data:/var/lib/grafana
    environment:
      - GF_SERVER_ROOT_URL=https://grafana.mydomain.com
      - GF_SERVER_DOMAIN=grafana.mydomain.com
      - GF_USERS_ALLOW_SIGN_UP=false
volumes:
  traefik-data:
  influx-data:
  grafana-data:

Enter fullscreen mode Exit fullscreen mode

Notice the following:

  • We have added several labels to the usual configuration for grafana and influxDB.
  • Port 80 on grafana will redirect to HTTPS port 443.
  • traefik-data volume has been included as well

Running this will still not work because letsencrypt demands one last thing.

Generating certificates

For letsencrypt to generate the required certificates, it must make sure that the domains we claim here are ours. To do so we have to add one more DNS entry for each subdomain.

Example on cloudfare

Each entry must be

  • Type: CAA
  • Name: Subdomain name, not the entire FQDN (in this case just grafana and influx)
  • Flags: 0
  • TTL: Auto
  • Tag: Only allow specific hostnames
  • CA domain name: letsencrypt.org

Done!

That's it! run all containers via

docker-compose up
Enter fullscreen mode Exit fullscreen mode

(add -d for detacched mode)

and head over to https://grafana.mydomain.com to see your working grafana with custom domain and TLS!

Thanks for reading!

Have a lovely day!

💖 💪 🙅 🚩
juanlu_sanz
Juan Luis Sanz

Posted on February 9, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related