How to handle https with docker-compose and mkcert for local development

Published on

May 21, 2021

Docker and docker-compose are now widely used in the world of web development. Since our first Hackathon in 2015, Docker became a standard on all projects:
It helps a lot to maintain our stacks and having specific configurations for local , staging or production environments.

Nevertheless, Antoine has seen a lot of Docker configurations for development environment that are quite far from the staging or production environments. That's why he wrote this article in order to help you understanding, why you need your different environments to be close enough to each other. It's the best way to fail fast on your local environment in order to avoid shipping bugs on production.

Most of the time, local Docker configuration provides access to services directly on a specific port. The holy grail is using a reverse-proxy (such as Traefik) even on development. But today I would like to focus on a very specific topic: the TLS encryption on local environment.


This article was created using these versions of Docker and mkcert

  • docker: 20.10.15
  • docker-compose: 1.28.5
  • mkcert: 1.4.3-1

The following configuration might work for lower versions but I didn't check.


Before we start, we need to talk a bit about Traefik in case you never heard about it.

Basically, Traefik is a reverse proxy, which means it is the door to your platform.
It intercepts and routes every incoming request: it knows all the logic and every rule that determines which services handle which requests
(based on the path, the host, headers, and so on ...).


The cool thing with Traefik is that you don't have much configuration to write.
With only few docker-compose labels on services, you have a powerful reverse-proxy that handles the traffic of your stack.


mkcert is a tool to generate locally-trusted development certificates that can be shared with Traefik so it can perform a TLS encryption.

Mkcert can only be used in local environment. Public domain will require you to provide a certificate that is trusted by an external authority.

You can follow the installation guide on the mkcert documentation.

Getting started

We will use the containous/whoami Docker image to demonstrate the usage of a TLS certificate on a docker-compose service.

mkcert certificates

First we have to create and store the TLS certificates with mkcert in the ./certs folder.
These certificates are locally-trusted which mean they are valid only on your machine. Therefore you should gitignore them!
Every developer needs to generate their own certificate when they build the project.

mkdir -p certs
mkcert -cert-file certs/local-cert.pem -key-file certs/local-key.pem "app.localhost" "*.app.localhost" "domain.local" "*.domain.local"

Traefik configuration

Then create the folders: ./docker/traefik

mkdir -p docker/traefik

Create the ./docker/traefik/dynamic_conf.yaml that allows to configure the available subdomains served by Traefik.
It also declares the path of the TLS certifcates (generated with mkcert) that will be shared with Traefik through a Docker volume.

      rule: "Host(``)"
      service: "api@internal"
          - main: "app.localhost"
              - "*.app.localhost"
          - main: "domain.local"
              - "*.domain.local"

    - certFile: "/etc/certs/local-cert.pem"
      keyFile: "/etc/certs/local-key.pem"

Then you have to create the ./docker/traefik/traefik.yaml configuration file.

  sendAnonymousUsage: false

  dashboard: true
  insecure: true

    endpoint: unix:///var/run/docker.sock
    watch: true
    exposedbydefault: false

    filename: /etc/traefik/dynamic_conf.yaml
    watch: true

  level: DEBUG
  format: common

    address: ':80'
          to: websecure
          scheme: https

    address: ':443'

I am not going to explain every section of this file but here are some details about the providers and entryPoints sections:


This section tells Traefik what and how services are accessible from the outside. It is basically the routing configuration.

Here we have 2 different providers:

  • docker: Use containers labels to retrieve routing configuration (more information on the next section).
  • file: Load a configuration file. Here the dynamic_conf.yaml we created earlier.


This section is quite simple and allows to redirect every incoming request on http (port 80) to https (port 443).
Now you can be sure that Traefik will not let http requests go further to your services.

docker-compose file

In the file, put:

version: '3.8'

    image: traefik:v2.4.7
    container_name: traefik
      - "traefik.enable=true"
      - "traefik.http.routers.traefik=true"
      - "traefik.http.routers.traefik.tls=true"
      - ""
      - 80:80
      - 443:443
    restart: unless-stopped
      - no-new-privileges:true
      - ./docker/traefik/dynamic_conf.yaml:/etc/traefik/dynamic_conf.yaml:ro
      - ./docker/traefik/traefik.yaml:/etc/traefik/traefik.yaml:ro
      - ./certs:/etc/certs:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro

    image: containous/whoami:latest
    container_name: whoami
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(``)"
      - "traefik.http.routers.whoami.tls=true"
    restart: unless-stopped

    external: true

Here you will find the declarations of services in your stack. Here are some more details about this configuration:


  • ports: Binds both ports 80 and 443 to the host. Notice that the whoami service does not expose any port. All incoming requests to your stack will be handled by Traefik.
  • volumes: We share configuration files, certificates and the Docker socket (Traefik requires access to the Docker socket to get its dynamic configuration).


We define 3 labels:

  • traefik.enable=true: Explicitly tells Traefik to expose this container.
  • traefik.http.routers.whoami.rule=Host(''): The domain the service will respond to.
  • traefik.http.routers.whoami.tls=true: Enable HTTPS on this route.

Run the stack

docker-compose -f up -p

And voilà! You should now be able to go to

Any questions or feedback? Ping us on Twitter !

Written by

Antoine Lelaisant
Antoine Lelaisant


Front and backend developer with a pref for mobile apps. Loves to share his XP with clients & KNPeers during our trainings.

Francois Pasquier
Francois Pasquier