Introduction #
In this tutorial, we will set up a secure Single Sign-On (SSO) infrastructure using Docker, Traefik, Authelia, and SSL certificates from Let’s Encrypt.
With this setup, you’ll have a powerful combination of tools that ensure your applications are secure, scalable, and easy to manage. Let’s dive in and start building our project!
What is Traefik? #
Traefik is a modern reverse proxy and load balancer that makes deploying microservices easy. It automatically discovers services and updates its configuration, simplifying the management of dynamic environments. Traefik supports multiple backends, such as Docker and Kubernetes, and handles SSL termination, load balancing, and HTTP routing.
What is Authelia? #
Authelia is an open-source authentication and authorization server, providing two-factor authentication and single sign-on (SSO) for your applications.
SSO is an authentication process that allows a user to access multiple applications with one set of login credentials. It enhances security by adding an additional layer of protection to your services.
Prerequisites #
- Docker running on a Linux Server
- A domain name with the ability to change DNS entries
Setting DNS records #
We start by creating wildcard DNS records for our domain example.com
. If you are following this guide, replace example.com
with your own domain name.
Type | Name | Content |
---|---|---|
A | * | your IPv4 address |
AAAA | * | your IPv6 address |
A | @ | your IPv4 address |
AAAA | @ | your IPv6 address |
Creating the Project Structure #
Let’s create our project structure:
mkdir -p $HOME/docker-projects/{traefik-authelia,hello-world}
mkdir $HOME/docker-projects/traefik-authelia/{config,data,ssl}
touch $HOME/docker-projects/traefik-authelia/ssl/acme.json
chmod 600 $HOME/docker-projects/traefik-authelia/ssl/acme.json
Configure Traefik and Authelia #
Setting up docker-compose.yml #
Create a docker-compose.yml
file inside docker-projects/traefik-authelia
:
services:
traefik:
image: traefik:v3.1
container_name: traefik
restart: unless-stopped
volumes:
- ./ssl/acme.json:/acme.json
- /var/run/docker.sock:/var/run/docker.sock
networks:
- docknet
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.api.rule=Host(`traefik.example.com`)'
- 'traefik.http.routers.api.entrypoints=https'
- 'traefik.http.routers.api.service=api@internal'
- 'traefik.http.routers.api.tls=true'
- 'traefik.http.routers.api.tls.certresolver=letsencrypt'
depends_on:
authelia-service:
condition: service_healthy
ports:
- 80:80
- 443:443
command:
- '--api'
- '--providers.docker=true'
- '--providers.docker.exposedByDefault=false'
- '--entrypoints.http=true'
- '--entrypoints.http.address=:80'
- '--entrypoints.http.http.redirections.entrypoint.to=https'
- '--entrypoints.http.http.redirections.entrypoint.scheme=https'
- '--entrypoints.https=true'
- '--entrypoints.https.address=:443'
- '--certificatesResolvers.letsencrypt.acme.email=ggn@example.com'
- '--certificatesResolvers.letsencrypt.acme.storage=acme.json'
- '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http'
- '--log=true'
- '--log.level=INFO'
logging:
driver: "json-file"
options:
max-size: "1m"
authelia-service:
image: authelia/authelia:4
container_name: authelia-service
restart: unless-stopped
environment:
- TZ=Europe/Berlin
volumes:
- ./config:/config
- ./data:/data
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.authelia.rule=Host(`auth.example.com`)'
- 'traefik.http.routers.authelia.entrypoints=https'
- 'traefik.http.routers.authelia.tls=true'
- 'traefik.http.routers.authelia.tls.certresolver=letsencrypt'
- 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia-service:9091/api/verify?rd=https://auth.example.com'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email'
depends_on:
- authelia-redis
networks:
- docknet
logging:
driver: "json-file"
options:
max-size: "1m"
authelia-redis:
image: redis:7
container_name: authelia-redis
restart: unless-stopped
environment:
- TZ=Europe/Berlin
networks:
- docknet
logging:
driver: "json-file"
options:
max-size: "1m"
networks:
docknet:
name: docknet
Update the traefik.http.routers.authelia.rule=Host()
labels with your domain name and Europe/Berlin
with the timezone you’re living in.
Setting up configuration.yml #
Create two secrets using openssl rand -hex 24
:
openssl rand -hex 24
6c6934b9f93d3ef49122b5014ed5e2f48b8af18564d29f4b
openssl rand -hex 24
bb1a9d0395252126a854d32c4d176c405c4e94ac14aac161
The configuration.yml
file is used by Authelia and should be stored in the folder docker-projects/traefik-authelia/config
.
Add the secrets to identity_validation.reset_password.jwt_secret
and encryption_key
:
server.address: 0.0.0.0:9091
log:
level: debug
identity_validation.reset_password.jwt_secret: 6c6934b9f93d3ef49122b5014ed5e2f48b8af18564d29f4b
default_redirection_url: https://public.example.com
totp:
issuer: example.com
authentication_backend:
file:
path: /config/users_database.yml
access_control:
default_policy: deny
rules:
- domain: public.example.com
policy: bypass
- domain: one-factor.example.com
policy: one_factor
- domain: two-factor.example.com
policy: two_factor
- domain: example.com
policy: bypass
session:
name: authelia_session
secret: unsecure_session_secret
expiration: 3600
inactivity: 300
domain: example.com
redis:
host: authelia-redis
port: 6379
regulation:
max_retries: 3
find_time: 120
ban_time: 300
storage:
encryption_key: bb1a9d0395252126a854d32c4d176c405c4e94ac14aac161
local:
path: /data/db.sqlite3
ntp:
address: 'udp://time.cloudflare.com:123'
version: 3
max_desync: '3s'
disable_startup_check: true
disable_failure: true
notifier:
disable_startup_check: false
filesystem:
filename: '/config/notification.txt'
# smtp:
# username: apikey
# password: secret
# host: smtp.sendgrid.net
# port: 587
# sender: no-reply@mydomain.com
While the filesystem notifier is okay for a simple testing setup, I would suggest adding an SMTP notifier before deploying the setup to production. Just uncomment the SMTP section at the end of the file. You will notice the disadvantages when adding an OTP secret to your account in one of the next steps.
Setting up users_database.yml #
Run the following command to hash your password:
docker run --rm authelia/authelia:4 authelia crypto hash generate argon2 --password top-secret-password
Digest: $argon2id$v=19$m=65536,t=3,p=4$70wyNzdFDfhMO1hV6Hkx4w$i8i7oQZBix8xpXxYTWOWzU4SUbJwAoRis4/93D6XBTo
Replace the placeholders with your own credentials and store the file users_database.yml
in /docker-projects/traefik-authelia/config/
:
users:
<username>:
displayname: <display_name>
password: <hashed_password>
email: <email>
groups:
- admins
- dev
Start the containers #
Now it’s time to start the containers in docker-projects/traefik-authelia
using docker compose up -d
:
docker compose up -d
[+] Running 3/3
✔ Container authelia-redis Started 0.5s
✔ Container authelia-service Healthy 31.2s
✔ Container traefik Started 31.6s
As you can see, this takes a while. The container authelia-service
needs some time to start and as specified in the docker-compose.yml
, the container traefik
waits for authelia-service
until its state is service-healty
.
Use your favorite web browser and open https://traefik.example.com. You should see the web interface of Traefik:
Now, open https://auth.example.com in your browser. You should see a Sign in page, provided by Authelia. Try to sign in using your credentials you have defined in docker-projects/traefik-authelia/config/users_database.yml
.
Setting up one-time-passwords (OTP) #
Once you successfully logged in, you should add a One-Time-Password (OTP) by clicking on Register device
:
Click on Add
in the One-Time-Password section:
It’s required to verify your identity. As we don’t configured email server settings, Authelia will not send us an email, but the email text was stored in docker-projects/traefik-authelia/config/notification.txt
.
cat docker-projects/traefik-authelia/config/notification.txt
Date: 2024-07-18 12:54:25.92379532 +0200 CEST m=+505.360960681
Recipient: {Gerald <redacted>}
Subject: Confirm your identity
Hi Gerald,
This email has been sent to you in order to validate your identity. Purpose: Confirm your identity.
If you did not initiate the process your credentials might have been compromised and you should:
1. Visit the revocation link
2. Reset your password or other login credentials
3. Contact an Administrator
To confirm your identity please use the following single-use code: YR9GYGAY
This email was generated by a user with the IP 172.80.32.1.
The following link can be utilized to revoke the code (this is a logged event): https://auth.example.com/revoke/one-time-code?id=<redacted>
Paste the single-use code into the form and simply follow the steps:
Register your One-Time-Password by scanning the QR-code with your Smartphone and enter the displayed OTP in your app:
Congratulations, you have successfully created the Traefik + Authelia setup and configured One-Time-Passwords in your Authelia account.
Secure Traefik Dashboard with two-factor authentication (Important!) #
Since the URL https://traefik.example.com is accessible to anyone, it is important to secure the Traefik Dashboard with two-factor authentication.
Add the following line to the labels
section in your docker-projects/traefik-authelia/docker-compose.yml
:
services:
traefik:
[...]
labels:
[...]
- 'traefik.http.routers.api.middlewares=authelia@docker'
Add the following lines to the access_control
/rules
section in your docker-projects/traefik-authelia/config/configuration.yml
:
access_control:
default_policy: deny
rules:
[...]
- domain: traefik.example.com
policy: two_factor
Restart your containers:
docker compose down
docker compose up -d
Open https://traefik.example.com again and see if you need to authenticate using a second factor.
Create some other containers #
This step is optional, but used to demonstrate the functionality of our setup.
Create Dockerfile #
Create a Dockerfile
in docker-projects/hello-world/
:
FROM busybox
WORKDIR /usr/share/httpd
CMD sh -c 'echo "$APP_TITLE" > index.html && /bin/httpd -p 80 -f'
The only job of the container is to display a message specified by the APP_TITLE environment variable.
Create docker-compose.yml #
We are creating three services:
- one-factor-service
Only accessible if a user is authenticated with one factor (e.g., password) - two-factor-service
Only accessible if a user is authenticated with two factors (e.g., password and OTP) - public-service
Accessible for unauthenticated and authenticated users
Create a docker-compose.yml
file in docker-projects/hello-world/
:
services:
one-factor-service:
build: .
container_name: one-factor-service
restart: unless-stopped
environment:
- APP_TITLE=One Factor Service
networks:
- docknet
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.one-factor.rule=Host(`one-factor.example.com`)'
- 'traefik.http.routers.one-factor.entrypoints=https'
- 'traefik.http.routers.one-factor.tls=true'
- 'traefik.http.routers.one-factor.tls.certresolver=letsencrypt'
- 'traefik.http.routers.one-factor.middlewares=authelia@docker'
- 'traefik.http.routers.one-factor.service=one-factor-service'
- 'traefik.http.services.one-factor-service.loadbalancer.server.port=80'
expose:
- 80
logging:
driver: "json-file"
options:
max-size: "1m"
two-factor-service:
build: .
container_name: two-factor-service
restart: unless-stopped
environment:
- APP_TITLE=Two Factor Service
networks:
- docknet
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.two-factor.rule=Host(`two-factor.example.com`)'
- 'traefik.http.routers.two-factor.entrypoints=https'
- 'traefik.http.routers.two-factor.tls=true'
- 'traefik.http.routers.two-factor.tls.certresolver=letsencrypt'
- 'traefik.http.routers.two-factor.middlewares=authelia@docker'
- 'traefik.http.routers.two-factor.service=two-factor-service'
- 'traefik.http.services.two-factor-service.loadbalancer.server.port=80'
expose:
- 80
logging:
driver: "json-file"
options:
max-size: "1m"
public-service:
build: .
container_name: public-service
restart: unless-stopped
environment:
- APP_TITLE=Public Service
networks:
- docknet
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.public.rule=Host(`public.example.com`)'
- 'traefik.http.routers.public.entrypoints=https'
- 'traefik.http.routers.public.tls=true'
- 'traefik.http.routers.public.tls.certresolver=letsencrypt'
- 'traefik.http.routers.public.middlewares=authelia@docker'
- 'traefik.http.routers.public.service=public-service'
- 'traefik.http.services.public-service.loadbalancer.server.port=80'
expose:
- 80
logging:
driver: "json-file"
options:
max-size: "1m"
networks:
docknet:
name: docknet
external: true
Start the containers again in the docker-projects/hello-world
folder using docker compose up -d
:
docker compose up -d
[+] Running 3/3
✔ Container two-factor-service Started 0.7s
✔ Container public-service Started 0.7s
✔ Container one-factor-service Started 0.7s
If you run docker ps
, you should see the following containers:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ac54255c2725 hello-world-one-factor-service "sh -c '/bin/httpd -…" 4 minutes ago Up 4 minutes 80/tcp one-factor-service
a4a6d306a315 hello-world-public-service "sh -c '/bin/httpd -…" 4 minutes ago Up 4 minutes 80/tcp public-service
eed87878fc20 hello-world-two-factor-service "sh -c '/bin/httpd -…" 4 minutes ago Up 4 minutes 80/tcp two-factor-service
ac7214f84b83 traefik:v3.1 "/entrypoint.sh --ap…" 2 hours ago Up 2 hours 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp traefik
31d6d7edce07 authelia/authelia:4 "/app/entrypoint.sh" 2 hours ago Up 2 hours (healthy) 9091/tcp authelia-service
2d89a25f8e0f redis:7 "docker-entrypoint.s…" 2 hours ago Up 2 hours 6379/tcp authelia-redis
Testing our setup #
Accessing the public service #
Open your web browser and go to https://public.example.com. As you can see, you can access the public service without any authentication:
Accessing the one-factor service #
Open your web browser and go to https://one-factor.example.com. You’ll probably see the sign in screen of Authelia again:
After signing in, you should see the one-factor service:
Accessing the two-factor service #
Open your web browser and go to https://two-factor.example.com. As you are already logged in, you won’t see the sign-in page, but you will have to authenticate yourself using OTP to access the site:
Conclusion #
In this tutorial, we’ve successfully set up a secure Single Sign-On (SSO) infrastructure using Docker, Traefik, Authelia, and Let’s Encrypt SSL certificates. We’ve configured Traefik as our reverse proxy and load balancer, ensuring secure and efficient routing of traffic to our services. Authelia provided robust authentication and authorization features, enhancing the security of our applications with SSO and two-factor authentication.
By following this guide, you now have a powerful and scalable setup that simplifies the management of dynamic environments, improves security, and provides a seamless user experience. Whether you’re deploying in a development environment or moving towards production, this combination of tools offers a flexible and reliable solution for modern web applications.
Remember, this setup can be further customized and extended based on your specific requirements. For example, you can integrate additional services, configure different authentication backends, or fine-tune the access control rules in Authelia.
Next Steps #
- Expand and Customize: Add more services and refine your configurations to match your specific needs.
- Monitor and Maintain: Regularly monitor your services and ensure that your configurations are up-to-date.
- Explore Advanced Features: Look into advanced features of Traefik and Authelia for even more powerful capabilities.
Troubleshooting #
General #
Docker logs are a valuable resource for identifying issues with your containers. Use the following command to view the log of a container:
docker logs <container_name, e.g., traefik>
You can see a list of all Docker containers with this command:
docker ps
Common Issues and Solutions #
Bad Gateway Error #
-
Cause: Traefik cannot establish a connection to a Docker container.
-
Solution: Ensure the container is running and the port specified in the docker-compose.yml file matches the exposed port of the container. Verify the container status with:
docker ps -a
SSL Certificate Issues #
-
Cause: Problems with SSL certificates, permissions, or ACME challenges.
-
Solution: Check the permissions of acme.json:
chmod 600 $HOME/docker-projects/traefik-authelia/ssl/acme.json
Also, check Traefik logs for detailed error messages.
Service Health Check Failures #
- Cause: Containers not starting properly or health checks failing.
- Solution: Review the container logs to identify the root cause. Ensure all dependencies (e.g.,, Redis for Authelia) are up and running.
Authentication Failures #
- Cause: Issues with Authelia’s configuration or user credentials.
- Solution: Verify the Authelia configuration files (
configuration.yml
andusers_database.yml
). Ensure secrets and hashed passwords are correctly set.
Network Issues #
- Cause: Misconfigured Docker networks.
- Solution: Ensure all services are connected to the correct Docker network. Verify network configurations in the docker-compose.yml file. Check that IPv6 is properly configured on your system and in the Docker config file
/etc/docker/daemon.json
.
Thank you #
I appreciate your feedback on this tutorial. If you have suggestions, improvements, or encounter any bugs, please don’t hesitate to reach out. I’ll strive to keep this guide updated with the latest best practices and features.
Thank you for following along, and I hope this tutorial has been helpful in setting up your secure SSO infrastructure. Happy deploying!