Traefik is a reverse proxy with excellent docker integration. It uses labeln attached to containers to route traffic to them.
A common label set looks similar to this:
whoami/docker-compose.yamlservices:
whoami:
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls=true"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
In this example:
whoamithe name of the "main" service (2)whoamithe also the default compose project name- Traefik is activated (5)
- It is exposed under
whoamisubdomain (6) - It is served over https (7)
- A preconfigured certresolver named
letsencryptis used fortls(8-9)
The problem
In my other post about gitops with docker I introduced a concept of using git
as the sources of truth for docker deployments using docker compose.
Now there are some requirements to this approach:
- every stack is exposer under
<stack_name>.<your_domain> - every stack is protected with
tls
We could of course edit every docker-compose.yaml file and add the required labels, but it quickly becomes obvious,
that all the labels are the same!
- Enable Traefik
- Assign a subdomain
- Enable HTTPS
The solution
By using $COMPOSE_PROJECT_NAME
variable we can create a more generic template.
This ensures that the Traefik rules are consistent.
<stack_name>/docker-compose.yamlservices:
<main_service>:
labels:
- "traefik.enable=true"
- "traefik.http.routers.$COMPOSE_PROJECT_NAME.rule=Host(`<stack_name>.example.com`)"
- "traefik.http.routers.$COMPOSE_PROJECT_NAME.entrypoints=websecure"
- "traefik.http.routers.$COMPOSE_PROJECT_NAME.tls=true"
- "traefik.http.routers.$COMPOSE_PROJECT_NAME.tls.certresolver=letsencrypt"
The good news is that Traefik lets us configure some defaults that will cover the above boilerplate!
Let's us define two simple conventions:
appis the main container, where Traefik will route the traffic<stack_name>(the compose project name) is the subdomain
whoami/docker-compose.yamlservices:
app:
image: traefik/whoami
Now let's configure traefik to do "the magic"
traefik/docker-compose.yamlvolumes:
letsencrypt:
services:
traefik:
container_name: traefik
restart: always
image: traefik:3
network_mode: host
command:
- --certificatesresolvers.letsencrypt.acme.httpchallenge=true
- --certificatesresolvers.letsencrypt.acme.email=<your_email_here>
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.http.tls.certresolver=letsencrypt
- --providers.docker
- --providers.docker.defaultrule=Host(`{{ trimPrefix `app-` .Name }}.example.com`)
- --providers.docker.constraints=Label(`com.docker.compose.service`,`app`)
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /letsencrypt:/letsencrypt
Let's explain this a little
- We configure a Let's Encrypt certificates resolvers (11-14)
- We listen on port 80 and 443 (16-17)
- We redirect all http traffic to https (19-20)
- and attach the configured certificates resolvers to it (21)
- We setup the docker provider (23-25)
Explanation for lines 24-25
--providers.docker.defaultrule=Host(`{{ trimPrefix 'app-'.Name }}.example.com`)
.Nameis autogenerated as<service-name>-<stack_name>so for yourwhoamiexample it would beapp-whoamitrimPrefix 'app-'.Nameresolves inwhoami
--providers.docker.constraints=Label(`com.docker.compose.service`,`app`)
All services should be exposed by default but should be filtered down, only to app services. The label com.docker.compose.service is added by docker-compose to all containers .
Bonus configuration
We can tweak this configuration even more.
Exposing other services in the stack
Sometimes you want to expose more than just the app container.
The current configuration won't route traffic to any other services other than app.
To still be able to use the default configuration method, we need to re-enable it.
--providers.docker.constraints=Label(`com.docker.compose.service`,`app`) || Label(`traefik.enable`, `true`)
Stack name other than directory name
When deploying the stacks, the name is generated based on directory name where the docker-compose.yaml file is located.
We can change the make in several ways, but here is the easiest one:
whoami-example/docker-compose.yamlname: whoami
services:
app:
image: traefik/whoami
