Using Traefik to develop locally with Docker (Part 2/x)
This is a continuation of the previous post explaining how to leverage Traefik to have a nice setup for local development of your projects using docker. If you read and followed the previous part, you should have a valid setup to spin up any new docker project that needs to listen to HTTP. Now, to make things more useful and keep our local setup as close to production as possible (with proper CORS working, etc…), it’d be ideal to have HTTPS working as well so let’s see how we can do that.
HTTPS & TLS#
Traefik has built-in support for HTTPS & TLS and it works terminating the TLS connection, meaning, Traefik can communicate using HTTPS with the browser and using HTTP. In order to use this feature,
(sidenote:
In my case, I own the domain j3j5.uy
(where you're reading this) and I have a record for *.dev.j3j5.uy
pointing to localhost, meaning, all subdomains of dev.j3j5.uy
will always resolve to 127.0.0.1
.
)
,
(sidenote:
This isn't entirely true, and below you can find a bit more about a possible alternative, backloop.dev
)
. You can provide Traefik with your own certificates or tell it to request them from Let’s Encrypt dynamically. In this case, we plan to use a domain that will be pointing to localhost, hence, it won’t be accesible to Let’s Encrypt so the second option is discarded. The way I set it up for my personal development is I create a wildcard subdomain on my DNS provider pointing to localhost (eg.- *.local.example.com
-> 127.0.0.1
), and then, using
(sidenote:
Certbot has plugins for a bunch of DNS providers but if yours isn't available, maybe other ACME protocol client has, or you can always create the certificate using the manual process.
)
, create a wildcard certificate for all the subdomains. As everything in life, this approach has its pros and cons, and I’ll try to dive into them later, but first, let’s start with Traefik’s config. We need to add port 443 to the bound ports (we were already binding port 80). If you followed the previous post, just add the new port to the docker-compose.yml
on the Traefik folder:
# docker-compose.yml
services:
traefik:
# The official docker image for Traefik, 2.9 is the latest at the time of writing
image: traefik:v2.9
ports:
# Bind ports 80 and 443 so they're accesible from outside the container
- "80:80"
- "443:443" # <-- ADD THIS LINE
volumes:
# Mount the docker socket so traefik can see changes in docker
- /var/run/docker.sock:/var/run/docker.sock:ro
# Make the network we created previously ("gateway") available as "default" network
networks:
default:
external: true
name: gateway
(sidenote:
You may have noticed that we've removed the line that specified docker
as a provider from docker-compose's config, later on we're creating a config file (traefik.yml
) so we'll keep it all there.
)
, let see how we can get our certificates into Traefik.
Wildcard Certificate#
We need to generate a wildcard certificate for all our “local” domains (using Certbot or any other ACME protocol client with DNS support) and then provide it to Traefik to store it on what they call a Certificate Store.
(sidenote:
If you don't own your own domain or don't want to use it or rather not deal with Let's Encrypt (for whatever reason), there's an alternative called backloop.dev. They have all the subdomains *.backloop.dev
pointing to 127.0.0.1
and share a valid SSL certificate with its key for those submdomains. If you don't mind using a domain owned by a 3rd party, this solution will work just the same. Just download their cert and key and use a subdomain of backloop.dev for your projects.
)
, first, we need the certificate and its key (usually a .cer
and a .key
files). Once we have them, we’ll create a folder called certs
on our traefik folder and put both files there.
Now we need to tell Traefik that we have certificates on there. TLS info can only be added through the dynamic configuration of Traefik, in this case, we want to share this with all our projects, so it doesn’t make sense to pick a specific project to include this configuration. Instead, we’ll add a new file provider within our Traefik project and put it on there. Since we are adding more configuration options than --providers.docker=true
, let’s create a traefik.yml
to keep it all in once place. Note that we removed the command
key from docker-compose.yml
because we’ll be using this config file from now on. Since we added a new port to access Traefik (443), we’ll create two entrypoints with two names for each port, so we can reference them on the projects’ labels.
# traefik.yml
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
docker: {}
file:
filename: "/etc/traefik/tls-info.yml"
You may have noticed that the filename key points to a path on /etc/traefik
and you may be thinking that you don’t have that folder on your /etc
. Well, that’s referring to a path within Traefik’s container, which does have that folder, but now we need to create the file and mount it on there so the container can access it.
(sidenote:
The filenames for the cert and the key need to match whatever you called the files when you put them on your certs
folder created earlier.
)
tls-info.yml
:
# tls-info.yml
tls:
certificates:
- certFile: /etc/traefik/tls/_wildcard.local.example.com.cer
keyFile: /etc/traefik/tls/_wildcard.local.example.com.key
stores:
default:
defaultCertificate:
certFile: /etc/traefik/tls/_wildcard.local.example.com.cer
keyFile: /etc/traefik/tls/_wildcard.local.example.com.key
Now, let’s add both config files to our docker-compose.yml
so the container can access them:
# docker-compose.yml
services:
# [...]
volumes:
# The docker socket is mounted for auto-discovery of new services
- /var/run/docker.sock:/var/run/docker.sock:ro
# Mount traefik's config files
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./tls-info.yml:/etc/traefik/tls-info.yml:ro
# Mount the certs
- ./certs:/etc/traefik/tls:ro
Now when traefik starts, it’ll be able to read the config files and the newly added cert config. In order to test this new setup, let’s try with tweaking the whoami
server we were using on the previous post:
# docker-compose.yml - whoami
services:
traefik:
# http://whoami.docker.localhost
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.local.example.com`)"
- "traefik.http.routers.whoamisecure.rule=Host(`whoami.local.example.com`)"
- "traefik.http.routers.whoamisecure.tls=true"
networks:
default:
external: true
name: gateway
Now accessing both, http://whoami.local.example.com and https://whoami.local.example.com should work and we should get a valid TLS connection to access the service running on our machine. I recommend digging into Traefik’s docs to see what options you have so you can configure it as you like (eg.- HTTP redirection to HTTPS).
Pros
- Startup of new projects it’s easy as the wildcard certificate will work for all different subdomains. You just need a unique domain name among your projects.
- Less load on Let’s Encrypt servers since you only need to create one cert and renew it every 3 months, after all it’s a free service so it’s something to take into account (and if you can, donate or get involved!).
Cons
- When the certificate needs to get renewed, you need to manually copy it to your
certs
folder on Traefik and restart it. If you’re sharing this setup with more people (dev team), everybody needs to get the renewed cert on their machines so you’ll probably want to put Traefik and its config (and certs!) into a git repo and add it to your onboarding processes (you have those, don’t you?).
Conclusion#
With this setup, we have valid TLS connections for the projects running locally on docker. Once it’s set up, it’s trivial
(sidenote:
Even if you're using a third party project where there's already a docker-compose.yml
, you can use the overrides feature from docker compose to add your own labels and let Traefik know about it.
)
to a new project and come up with a unique subdomain to have it accessible through HTTPS. I still have pending a post with a wrap up of the whole thing and its use on a project with some convenience tools. Keep posted!