How to set up Traefik and Authentik with Docker Compose
In this guide I will show you how to set up Traefik and Authentik in docker compose with both of them as services.
All with Let's Encrypt support so you can use https for the services behind Traefik.
Set up Docker
If you already have docker and docker compose installed click here to skip to the next step.
I'm using Ubuntu 22.04 so these steps will reflect that.
First let's set up the Docker repository as described on their website.
Run the following commands:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Now, install docker and the docker compose plugin with:
sudo apt-get install docker.io
sudo apt-get install docker-compose-plugin
Verify that both have been installed correctly:
$ docker -v
Docker version 20.10.21, build 20.10.21-0ubuntu1~22.04.3
$ docker compose version
Docker Compose version v2.19.1
If either of these respond with "Command not found" then something isn't right. Try to repeat the steps above or check Docker's website for updated steps.
Tip: if you want to run docker compose commands without sudoing every time run sudo usermod -aG docker $USER
to add your user to the docker group.
Docker Compose
This is the yaml file we're going to use. Name it compose.yaml.
services:
traefik:
image: traefik:2.10.3
container_name: traefik
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./shared/traefik/static.yaml:/etc/traefik/traefik.yaml:ro
- ./shared/traefik/dynamic.yaml:/etc/traefik/dynamic.yaml:ro
- letsencrypt:/letsencrypt
ports:
- 80:80
- 443:443
authentik_server:
image: ghcr.io/goauthentik/server:2023.6.0
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik_redis
AUTHENTIK_POSTGRESQL__HOST: authentik_postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASS}
volumes:
- ./shared/authentik/media:/media
env_file:
- .env
depends_on:
- authentik_postgresql
- authentik_redis
authentik_worker:
image: ghcr.io/goauthentik/server:2023.6.0
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: authentik_redis
AUTHENTIK_POSTGRESQL__HOST: authentik_postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASS}
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./shared/authentik/media:/media
env_file:
- .env
depends_on:
- authentik_postgresql
- authentik_redis
authentik_postgresql:
image: docker.io/library/postgres:12-alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- ./shared/authentik/database:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASS:?database password required}
POSTGRES_USER: authentik
POSTGRES_DB: authentik
env_file:
- .env
authentik_redis:
image: docker.io/library/redis:alpine
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- authentik_redis:/data
volumes:
letsencrypt:
driver: local
authentik_redis:
driver: local
As you can see we set up five services, four of which are for authentik. Two even use the same image, though if you look closely one runs as a "server" and the other as a "worker". This is authentik specific lingo so don't worry too much about that.
In addition to those we also run an instance of postgres, where authentik will store its data and another for redis that it uses for caching.
You may also have noticed that we reference an environment variable: AUTHENTIK_DB_PASS
. Let's address that too.
Create a file next to compose.yaml named .env and run the following commands to generate your database password and secret key:
echo "AUTHENTIK_DB_PASS=$(pwgen -s 40 1)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(pwgen -s 50 1)" >> .env
If pwgen is not available you'll need to install it by running
sudo apt install pwgen
Optional: You can replace other values in the compose.yaml file with variables gathered from the .env file just like we did for the database password.
Next, we'll be mounting some of our container's folders as volumes within a folder called shared (notice the volumes
directives). So go ahead and create it.
At this point your file structure should look something like this:
/home/biscuit/tutorial/
├── compose.yaml
├── .env
├── shared/
Traefik
I will be showing you how to configure the Traefik reverse proxy without using labels. Most of the examples I was seeing online for similar setups were using labels but I ended up favoring standalone configuration files for a few reasons:
- Traefik's documentation favors this format;
- It's way more readable, especially as you add more services;
- The Nextcloud AIO image which I am also using (though not covered here) does not support Traefik configuration as labels because it has a setup where that container creates and manages other containers. I think it's fair to chalk it up as a +1 for better compatibility.
Configuration files
The dynamic configuration contains everything that defines how the requests are handled by your system. This configuration can change and is seamlessly hot-reloaded, without any request interruption or connection loss." - traefik.io
We'll be creating two configuration files under ./shared/traefik: static.yaml and dynamic.yaml:
/home/biscuit/tutorial/
├── compose.yaml
├── .env
├── shared/
├── traefik/
├── static.yaml
├── dynamic.yaml
static.yaml
api: {}
entryPoints:
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: :443
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
filename: "/etc/traefik/dynamic.yaml"
watch: true
certificatesResolvers:
leresolver:
acme:
email: "[email protected]"
storage: "/letsencrypt/acme.json"
tlsChallenge: {} # Used for tlsChallenge
Some notes here:
api {}
will enable access to the traefik dashboard;- We'll be using Let's Encrypt to issue SSL certificates for the domains and sub domains that will be served by Traefik. Replace
[email protected]
with your own e-mail address. This is only used by Let's Encrypt to send you notices about your certificates; - Good to know: We're redirecting all http traffic to https, notice how this is achieved with the
redirections
block inside http. The names I used for the entrypointsweb
andwebsecure
are arbitrary. You can name them whatever, the important part is the ports they bind to.
dynamic.yaml
http:
middlewares:
authentik:
forwardAuth:
address: "http://authentik_server:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader: true
authResponseHeaders:
- "X-authentik-username"
- "X-authentik-groups"
- "X-authentik-email"
- "X-authentik-name"
- "X-authentik-uid"
- "X-authentik-jwt"
- "X-authentik-meta-jwks"
- "X-authentik-meta-outpost"
- "X-authentik-meta-provider"
- "X-authentik-meta-app"
- "X-authentik-meta-version"
services:
authentik-srv:
loadBalancer:
servers:
- url: "http://authentik_server:9000"
routers:
traefik:
rule: "Host(`traefik.example.com`) && (PathPrefix(`/`) || PathPrefix(`/api`))"
service: "api@internal"
middlewares:
- "authentik"
tls:
certresolver: "leresolver"
entryPoints:
- "websecure"
authentik-rtr:
rule: "Host(`auth.example.com`)"
service: "authentik-srv"
tls:
certresolver: "leresolver"
entryPoints:
- "websecure"
A few things I'd like to highlight here:
- With Docker the hostname will default to match the service name in the compose file. That's why we can proxy to
http://authentik_server:9000
; - We added a middleware for authentik which we then use for the traefik dashboard. For this to work we still need to go into authentik and do some configuration;
- Replace
example.com
with your own domain.
Authentik
Now with Traefik configured you should be able to run the command docker compose up -d
in the same directory where compose.yaml is to build and start your services. Give it a minute or two and you'll see Authentik running at https://auth.example.com/if/flow/initial-setup/:
Type your e-mail and desired password and press continue.
Now you'll land on https://auth.example.com/if/user/#/library
with an empty list of applications:
Press "Create a new application" to create your first application behind Authentik's auth. In this case, that will be Traefik's dashboard. Set the name to "Traefik Dashboard" and the slug to "traefik-dashboard" and press "Create".
Then go to Provider settings (https://auth.example.com/if/admin/#/core/providers) and create a new Provider:
Then go back to Application settings and set this provider for the Traefik Dashboard application:
Lastly, go to Outpost settings (https://auth.example.com/if/admin/#/outpost/outposts) and make sure to:
- Select "Traefik Dashboard" in the list;
- Change the value of "authentik_host" to https://auth.example.com
Update it and you're done!
When you now go to https://traefik.example.com you should see a screen like this:
Press "Continue" and
Done!
If you try to go to https://traefik.example.com in an incognito window you'll see the login screen again:
You now have a service that requires you to be authenticated before you can access it!