How to handle https with docker-compose and mkcert for local development
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
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.
We will use the
containous/whoami Docker image to demonstrate the usage of a TLS certificate on a docker-compose service.
First we have to create and store the TLS certificates with
mkcert in the
These certificates are locally-trusted which mean they are valid only on your machine. Therefore you should
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"
Then create the folders:
mkdir -p docker/traefik
./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.
http: routers: traefik: rule: "Host(`traefik.app.localhost`)" service: "api@internal" tls: domains: - main: "app.localhost" sans: - "*.app.localhost" - main: "domain.local" sans: - "*.domain.local" tls: certificates: - certFile: "/etc/certs/local-cert.pem" keyFile: "/etc/certs/local-key.pem"
Then you have to create the
./docker/traefik/traefik.yaml configuration file.
global: sendAnonymousUsage: false api: dashboard: true insecure: true providers: docker: endpoint: unix:///var/run/docker.sock watch: true exposedbydefault: false file: filename: /etc/traefik/dynamic_conf.yaml watch: true log: level: DEBUG format: common entryPoints: web: address: ':80' http: redirections: entryPoint: to: websecure scheme: https websecure: address: ':443'
I am not going to explain every section of this file but here are some details about the
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.yamlwe 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.dev.yml file, put:
version: '3.8' services: reverse-proxy: image: traefik:v2.4.7 container_name: traefik labels: - "traefik.enable=true" - "traefik.http.routers.traefik=true" - "traefik.http.routers.traefik.tls=true" - "traefik.http.services.traefik.loadbalancer.server.port=8080" ports: - 80:80 - 443:443 restart: unless-stopped security_opt: - no-new-privileges:true volumes: - ./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 whoami: image: containous/whoami:latest container_name: whoami labels: - "traefik.enable=true" - "traefik.http.routers.whoami.rule=Host(`whoami.app.localhost`)" - "traefik.http.routers.whoami.tls=true" restart: unless-stopped networks: proxy: 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
whoamiservice 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('whoami.app.localhost'): The domain the service will respond to.
traefik.http.routers.whoami.tls=true: Enable HTTPS on this route.
Run the stack
docker-compose -f docker-compose.dev.yml up -p
And voilà! You should now be able to go to https://whoami.app.localhost.
Any questions or feedback? Ping us on Twitter ! https://twitter.com/KNPLabs