I’ve been using Swag since I started with self-hosting. It’s been working flawlessly and I never had any issue with it. The only thing is if I wanted to expose a new service to the internet, I would have to configure it manually on a separate file. Swag already comes with the sample configuration files for most of popular services, though, it still felt like a chore I had to do before going to bed. I started looking for alternatives and this is when I came to know more about Traefik. The first time I came across it was when I configured a small k3s cluster. It came by default but I just uninsatalled it due to lack of familiarity.
Looking more into Traefik
I took reference from two sources to understand how Traefik works and how it can be used with my docker containers on Unraid. The yaml template I used is also sourced from the Ibracorp link.
In summary, Traefik can query docker via API with the help of container labels. Rules depend on those labels and so as long as you have the correct yaml configuration, you can treat it as set and forget. For those containers that you will be running in the times ahead, you just need to follow the same label structure.
The tutorial in the link includes configuring MFA by setting up a middleware. But I have a basic requirement of just providing external access to my services. So I will be saving this part for later in case I still require it in the future.
I had the following goals in mind:
- Be able to access containers named <container_name> externally from the internet via <container_name>.su-root.net.
- Be able to access <container_name> externally from the internet via <container_name>.su-root.net.
- Configure #1 and #2 with the least manual configuration required (but still secure).
Given this, I will only go through the necessary configuration in as little footprint as possible.
Starting with traefik
There are two different configuration files, a static one read at startup, and a dynamic one used during runtime.
Static configuration (traefik.yml) -> Define the entry points, providers, certificate provider.
Dynamic coniguration (fileConfig.yml) -> Define the routing and middleware.
There are multiple providers supported by Traefik but I will only focus on docker that is natively used by Unraid.
I can simply make use of the labels if I want to route traffic to specific containers that have multiple ports or those having a different container name against the cname I want to use.
traefik.yml configuration
The first part is to define the entry points where incoming traffic coming is expected.
- Define http entry point and redirect to https
- Define https entry point to use a certificate generated by Let’s Encrypt.
- Define the middleware so we enforce the addition of STS header.
Next is to define the providers:
- fileConfig.yml file that contains the dynamic routing configuration
- Docker by quering using API via dockerproxy.
Enable the UI and print info level logs.
And the last but not the least, the settings for Let’s Encrypt, the certificate provider.
global:
checkNewVersion: true
sendAnonymousUsage: false
serversTransport:
insecureSkipVerify: true
entryPoints:
http:
address: ':80'
http:
redirections:
entryPoint:
to: https
scheme: https
https:
address: ':443'
http:
tls:
certResolver: letsencrypt
domains:
- main: mydomain.com
sans:
- '*.mydomain.com'
middlewares:
- 'securityHeaders@file'
providers:
providersThrottleDuration: 2s
file:
filename: /etc/traefik/fileConfig.yml
watch: true
docker:
watch: true
# Default host rule to containername.domain.example
defaultRule: "Host(`{{ lower (trimPrefix `/` .Name )}}.mydomain.com`)" # Replace with your domain
exposedByDefault: false
endpoint: "tcp://dockersocket:2375" # Uncomment if you are using docker socket proxy
# Enable traefik ui
api:
dashboard: true
insecure: true
log:
level: INFO
# Use letsencrypt to generate ssl serficiates
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /etc/traefik/acme.json
dnsChallenge:
provider: cloudflare
# Used to make sure the dns challenge is propagated to the rights dns servers
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
Before I go to the other next yaml file, it’s important to note that I use IPvlan networking for all my docker containers. This provides a good level of network isolation against the host and I just find it way easier to manage my applications with unique IP addresses. The need to mention is due to the fact that I cannot get Traefik to detect the exposed port on the external IP address unless I explicitly define it with a label. I cannot find any mention of this in the documentation and I haven’t done enough experiments to say that this works without a label when using a different network type such a bridge or a custom docker networks, and if it makes sense, even with an untagged ipvlan network.
Sample error I get without the loadbalancer label:
“level=error msg=“service "kuma" error: port is missing” providerName=docker container=kuma-cf32b….”
fileConfig.yml configuration
Moving over to the dynamic configuration, if you want to route to non-docker services OR if for any reason you require specific routing for a container, you can define them here.
First, you have to specify the entry point for the router. Next is the rule that needs to fulfilled for this router to be activated. And third, the mapped service containing the destination and port.
The middleware you defined in the first yaml file will also be referenced from here. TLS options can also be in this file but it is not a requirement. I haven’t gone through each of header but since I can see this enables STS, then I will just leave it as it is.
http:
routers:
opnsense:
entryPoints:
- https
rule: 'Host(`opnsense.mydomain.com`)'
service: opnsense
proxmox:
entryPoints:
- https
rule: 'Host(`proxmox.mydomain.com`)'
service: proxmox
services:
opnsense:
loadBalancer:
servers:
- url: https://10.100.0.1/
proxmox:
loadBalancer:
servers:
- url: https://10.100.0.2:8006/
middlewares:
securityHeaders:
headers:
customResponseHeaders:
X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex"
X-Forwarded-Proto: "https"
server: ""
customRequestHeaders:
X-Forwarded-Proto: "https"
sslProxyHeaders:
X-Forwarded-Proto: "https"
referrerPolicy: "same-origin"
hostsProxyHeaders:
- "X-Forwarded-Host"
contentTypeNosniff: true
browserXssFilter: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsSeconds: 63072000
stsPreload: true
# Only use secure ciphers - https://ssl-config.mozilla.org/#server=traefik&version=2.6.0&config=intermediate&guideline=5.6
tls:
options:
default:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
Exposing containers
Traefik will automatically and implicitly create a router and a service for each of the exposed container and the only thing you need is a set of labels:
For traefik to detect the container
traefik.enable=true
To define the allowed entry point
traefik.http.routers.APP_NAME.entryPoints=https
So traefik knows to which port it has to route the packets
traefik.http.services.APP_NAME.loadbalancer.server.port=APP_PORT
To tell traefik to use some another router name. (optional)
traefik.http.routers.APP_NAME.rule=Host(\
APP_NAME.mydomain.com`)`
The last one in the list is required only if you want traefik to route a sub-domain that is different from the container name. e.g. you want traefik to route kuma.mydomain.net to a container named uptime-kuma.
Below is an example of a container having three labels:
Make sure you have the respective cnames on your DNS pointing to Traefik, and once that is all set, you can start accessing your services over https. I would also suggest to test the redirection at least for the first service you are exposing.
When you have multiple services exposed, you might want to check out the Traefik UI to get a good visibility of your configuration. Do note that you will not be able to do any action from here and it’s solely for checking the active routes.
Understanding Traefik might be a bit confusing at first but the more you read about the documentation, the more it gets interesting. Perhaps I’ll be able to use more of its other functions in future projects!