How to Install Traefik as a Container using Docker or Podman?

What is Traefik?
Traefik is a Modern reverse proxy application that supports complex dynamic routing requirements without restarting the proxy application.
You can refer to official documentation if you want to learn more about it.
Usually, Traefik is installed using Docker daemon. But, in this post, I will install it on Podman since I have all my other containers running on Podman.
If you feel comfortable installing it on Docker, you still follow the below steps and replace the "podman" keyword with the "docker" keyword, and it should still work as expected.
Note: there are some advantages of installing it on Docker since Traefik is designed to auto-detect containers (existing or new) and expose them as services. But, in my case, I don't have any docker containers.
1. Set up the directory structure
Before initiating the containers, the required directory structure needs to be in place.
TRAEFIKHOSTDIR="/opt/traefik" #you can change it if you want
mkdir -p ${TRAEFIKHOSTDIR}/dynamic
touch ${TRAEFIKHOSTDIR}/acme.json #Used for LetsEncrypt Key generation
touch ${TRAEFIKHOSTDIR}/traefik.log
2. Prepare the Configuration
Tarefik can be installed using multiple types of configuration sources, but I will use the file approach in this post since it is the simplest way.
Once you understand the configuration format and values, installing or setting up new routes in your Traefik instance is straightforward.
Traefik is designed to run on two sets of configurations.
1. Static Configuration
As the name states, the configuration is mainly used to initiate the application and HTTP ports to listen to the traffic. The static configuration is loaded only once during the application startup, and any modifications to it require a Traefik container restart.
The below code snippet is a sample static configuration with both HTTP and HTTPS routes.
In this sample,
- entryPoints: configures both HTTP and HTTPS routes, and the HTTP route is always redirected to the HTTPS route.
- certificateResolvers: configures "LetsEncrypt" certificate authority as SSL provider using Cloudflare DNS challenge method. Note: You can set it to the "LetsEncrypt" staging server for initial testing so that you won't run into the LetsEncrypt rate limit issues.
Note: route names ("portal", "static_images", "home", "authelia") are custom ones, so you can name route names whatever you want.
- Providers: specify the sources of services available on the network. For example, if you would like to configure and expose all docker containers to Traefik for servicing, you can uncomment the docker provider section. In my case, I am just using the file provider (directory with a list of files), as I will configure them manually.
- api: allows you to enable dashboard access without a password.
- log: Specifies the log file configuration, which you can set to DEBUG in case of any errors or unexpected behavior.
- File saved as ${TRAEFIK_HOST_DIR}/traefik.yml in the host system, where you can pick any location per your need for the "TRAEFIK_HOST_DIR" variable.
global:
checkNewVersion: false
sendAnonymousUsage: false
entryPoints:
http:
address: :80
http:
redirections:
entryPoint:
to: https
scheme: https
https:
address: ":443"
http:
tls:
certResolver: letsEncrypt
domains:
- main: "*.vtulluru.com"
sans:
- "*.vtulluru.com"
certificatesResolvers:
letsEncrypt:
acme:
caServer: "https://acme-v02.api.letsencrypt.org/directory"
#caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
email: vinay@vtulluru.com
storage: /acme.json
dnsChallenge:
provider: cloudflare
delayBeforeCheck: 0
resolvers: 1.1.1.1:53,1.0.0.1:53
providers:
# docker:
# endpoint: unix:///var/run/docker.sock
# exposedByDefault: false
file:
directory: /etc/traefik/dynamic/
api:
insecure: true
log:
filePath: "/traefik.log"
format: common #json
level: INFO
2. Dynamic Configuration
Unlike static configuration, the dynamic configuration is more flexible and maintains the active routes of services that need to be communicated with incoming requests. Traefik constantly monitors the dynamic configuration, and the routes get updated immediately (based on configuration value) after configuration file modification.
The below code snippet is a sample static configuration to show how you can route external traffic to your internal network endpoints running on different hosts and ports. Our dynamic configuration can define three different routes (HTTP, TCP, UDP). But, I will cover only the HTTP route since it is a typical use case.
In this sample,
- http: the "http" node configures all the routes under the HTTP route type.
- routers: specifies all the HTTP routes configured under the HTTP node.
- Note: route names ("portal", "static_images", "home", "authelia") are custom ones, so you can name route names whatever you want.
- rule: specifies the filtering criteria for incoming traffic. For example, the "portal" route below will get triggered if the incoming hostname matches with "home.vtulluru.com" and the URL path prefix matches with "/abc". (https://home.vtulluru.com/abc/blahblah)
- service: specifies the custom service name that needs to be called if the incoming rules match the specified value. For example, if the "portal" rule is triggered based on criteria, then it runs the "abc" service configured under the "services" section.
- middlewares: specifies the intermediary steps that the route needs to run before passing the request to the configured service. For example, you can do some prechecks (like authentication) before passing the service request.
- tls: specifies the SSL certificate that it needs to use for this specific route. If you have multiple certificates, you can configure them in certificateResolvers and mention that value here.
- priority: it is an optional configuration value but will be handy if you want to configure multiple routes with the same hostname. For example, the 'abc' route is checked first in the below sample, and if it doesn't match, it goes to the 'bcd' route.
- services: this is one of the leaf nodes under the "http" node, which carries all the services configuration underneath.
Note: service names ("abc", "bcd", "home", "authelia") are custom ones, so you can name service names whatever you want.
- loadBalancer: this configuration is used for routing the HTTP traffic to a specific URL. If you have multiple endpoints servicing the same requests, you can specify various "url" values under loadBalancer/servers section.
- middlwares: this is another leaf node under the "http" node, which carries all the middleware configuration values.
Note: Like route names and service names, middleware names can be named with whatever you want.
Note: I have added sample configurations for both Basic authentication and Oauth authentication using Authelia service for your reference, so that you will know how to use them.
- You need to create your hash password for the basic auth by running the "podman exec -it authelia bash authelia hash-password <secretPassword>" command and paste the output value in the dynamic configuration file.
- For authelia service to work, you would need to set up the authelia container. I might create a separate post on how to set up authelia if you are not aware of setting it up. I have tried Google Oauth Middleware before using authelia and felt that running local authentication on my server is much more secure than running it in the cloud environment.
- File saved as ${TRAEFIK_HOST_DIR}/dynamic/http.yml in the host system, where you can pick any location per your need for the "TRAEFIK_HOST_DIR" variable.
http:
routers:
portal:
rule: (Host(`home.vtulluru.com`) && PathPrefix(`/abc`))
service: abc
middlewares:
- chain-authelia
tls:
certresolver: letsEncrypt
priority: 3
static_images:
rule: (Host(`home.vtulluru.com`) && PathPrefix(`/bcd/`))
service: bcd
middlewares:
- chain-no-auth
tls:
certresolver: letsEncrypt
priority: 2
home:
rule: Host(`home.vtulluru.com`)
service: home
middlewares:
- chain-no-auth
tls:
certresolver: letsEncrypt
priority: 1
authelia:
rule: Host(`login.vtulluru.com`)
service: authelia
middlewares:
- chain-no-auth
tls:
certresolver: letsEncrypt
services:
home:
loadBalancer:
passHostHeader: true
servers:
- url: http://10.0.0.111:8069
abc:
loadBalancer:
passHostHeader: true
servers:
- url: http://0.0.0.0
bcd:
loadBalancer:
passHostHeader: true
servers:
- url: http://0.0.0.0
authelia:
loadBalancer:
passHostHeader: true
servers:
- url: http://localhost:9999
middlewares:
myAuth:
basicAuth:
users:
- 'username:${pass}' #run "authelia
hash-password ", if you want to use basic auth and replace ${pass} value accordingly
headerField: "X-WebAuth-User"
removeHeader: true
authelia: #use authelia if you want to have local oauth service
forwardAuth:
address: "http://localhost:9999/api/verify?rd=https://login.vtulluru.com/"
trustForwardHeader: true
authResponseHeaders:
- "adminGroup"
chain-authelia:
chain:
middlewares:
- middlewares-rate-limit
- middlewares-secure-headers
- authelia
chain-no-auth:
chain:
middlewares:
- middlewares-rate-limit
- middlewares-secure-headers
middlewares-rate-limit:
rateLimit:
average: 100
burst: 50
middlewares-secure-headers:
headers:
accessControlAllowMethods:
- GET
- OPTIONS
- PUT
accessControlMaxAge: 100
hostsProxyHeaders:
- "X-Forwarded-Host"
sslRedirect: true
stsSeconds: 63072000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
# frameDeny: true #overwritten by customFrameOptionsValue
customFrameOptionsValue: "allow-from https:vtulluru.com" #CSP takes care of this but may be needed for organizr.
contentTypeNosniff: true
browserXssFilter: true
# sslForceHost: true # add sslHost to all of the services
# sslHost: "vtulluru.com"
referrerPolicy: "same-origin"
# Setting contentSecurityPolicy is more secure but it can break things. Proper auth will reduce the risk.
# the below line also breaks some apps due to 'none' - sonarr, radarr, etc.
# contentSecurityPolicy: "frame-ancestors '*.vtulluru.com:*';object-src 'none';script-src 'none';"
featurePolicy: "camera 'none'; geolocation 'none'; microphone 'none'; payment 'none'; usb 'none'; vr 'none';"
customResponseHeaders:
X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex,"
server: ""
3. Run the container image
Run the container in a daemon mode to persist with host server reboots.
Note: In case get port already assigned error, then you try mapping it to different host port. For example, "-p 81:80".
Note: In case you are not using letEnrcypt, you can remove all "CF*" environment variables and acme volume file, which are meant for clodflare dns challenge.
Note: For TZ variable, you can set your own time zone.
podman run -d --restart=always \
-p 8080:8080 -p 80:80 -p 444:443 \
-e CF_API_EMAIL="vinay@vtulluru.com" \
-e CF_DNS_API_TOKEN=bbbbhfghhghghddgfdgdfdfgdd \
-e CF_ZONE_API_TOKEN=sdaassdaaaaaaaaaaaaaaaaaaaaaaaaasdadsa \
-e CF_API_KEY=rteetrererrererrrrerrrterrrrteeee \
-e "TZ=America/Chicago" \
-v ${TRAEFIK_HOST_DIR}/:/etc/traefik/ \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ${TRAEFIK_HOST_DIR}/acme.json:/acme.json \
-v ${TRAEFIK_HOST_DIR}/traefik.log:/traefik.log \
--name traefik traefik
4. Run the firewalld command to open ports(Docker only)
If you run the Traefik container using Podman, Podman automatically opens the ports for you on the host system. If you are running it on the docker engine, you need to open the network ports manually on the server.
Note: I have included firewalld command, but if you are using "iptables", then you need to run equivalent command.
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --reload
5. Port forward from your network router to host
You would need to port forward the external traffic to the ports listening on the host where the Traefik container is running.
Note: You can exclude port 8080 for port forwarding since it is only for your local monitoring. You can also expose that if you implement any authentication middleware mentioned above.
6. Validate your setup by navigating to the dashboard
Navigate to the host system on the dashboard link to verify if it is accessible from the local network and public IP address if you exposed it with authentication in the above steps.
Dashboard url: http://<hostname>:8080 (sample screenshot below)
You can play with modifying the dynamic configuration and see if the dashboard is automatically picking or not.
Good luck with your setup. Let me know if you are stuck somewhere, and I can try to help you.