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

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.