Traefik
SealSkin behind Traefik 3 with Docker labels, a router for the web app and one for the session names, either upstream, and Authelia's forward-auth middleware.
Run with Traefik 3.7. Read Behind a reverse proxy first for the settings on the SealSkin side.
The session names, and the wildcard certificate that covers them, are for session isolation; without it the web app's name is all SealSkin needs, and the rule for the session names is harmless and may be left out.
Labels
Two routers share one service: the web app's name, and every name that
starts with a session id. The certificate has to cover both, so the
resolver uses a DNS challenge for example.com and *.example.com.
services:
traefik:
image: traefik:v3
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls.certresolver=letsencrypt
- --entrypoints.websecure.http.tls.domains[0].main=example.com
- --entrypoints.websecure.http.tls.domains[0].sans=*.example.com
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
ports:
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
default:
ipv4_address: 172.20.0.2
sealskin:
image: lscr.io/linuxserver/sealskin:latest
container_name: sealskin
environment:
- HOST_URL=sealskin.example.com:443
- SEALSKIN_PUBLIC_URL=https://sealskin.example.com
- SEALSKIN_SESSION_DOMAIN=example.com
- SEALSKIN_TRUSTED_PROXIES=172.20.0.2
- SEALSKIN_HTTP_PORT=8080
volumes:
- ./config:/config
- ./storage:/storage
- /var/run/docker.sock:/var/run/docker.sock
labels:
- traefik.enable=true
- traefik.http.routers.sealskin.rule=Host(`sealskin.example.com`)
- traefik.http.routers.sealskin.entrypoints=websecure
- traefik.http.routers.sealskin.service=sealskin
- traefik.http.routers.sealskin-sessions.rule=HostRegexp(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.`)
- traefik.http.routers.sealskin-sessions.entrypoints=websecure
- traefik.http.routers.sealskin-sessions.service=sealskin
- traefik.http.services.sealskin.loadbalancer.server.port=8080
networks:
default:
ipam:
config:
- subnet: 172.20.0.0/24
ip_range: 172.20.0.128/25This uses the plain HTTP listener.
Traefik names a WebSocket's scheme wss in X-Forwarded-Proto, which the
listener takes for HTTPS.
The session port instead
To reach 8443 over HTTPS, drop SEALSKIN_HTTP_PORT and tell Traefik not to
verify the self-signed certificate there:
- traefik.http.services.sealskin.loadbalancer.server.port=8443
- traefik.http.services.sealskin.loadbalancer.server.scheme=https command:
- --serverstransport.insecureskipverify=trueThat switch is for every service of this Traefik. To keep it to SealSkin,
define a serversTransport in the file provider and name it in
traefik.http.services.sealskin.loadbalancer.serverstransport.
Forward-auth
To sign users in at SealSkin itself over OpenID Connect or SAML, the labels above are all there is; follow Authelia or Authentik.
To have Traefik ask for the sign-in, define the provider's middleware once, here Authelia's, on the Authelia container:
- traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/authz/forward-auth
- traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true
- traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Email,Remote-Nameand put it on the web app's router:
- traefik.http.routers.sealskin.middlewares=authelia@dockerThen set the sign-in headers on SealSkin as
A proxy that signs users in describes. Traefik replaces the
headers listed in authResponseHeaders with the provider's answer, and
removes the ones the answer leaves out. A router without the middleware
passes a visitor's own Remote-User through, which SealSkin detects and
refuses.
To guard the session names too, add the same middleware to
sealskin-sessions and set SEALSKIN_SESSION_DOMAIN.
The browser extension and the mobile app reach the node on raw paths the sign-in must leave alone (why; leave this out for a web-only deployment): a second router for the web app's name, with a higher priority, the same service, and no middleware:
- traefik.http.routers.sealskin-raw.rule=Host(`sealskin.example.com`) && (PathPrefix(`/companion/`) || PathPrefix(`/public/`) || PathRegexp(`^/((ws/)?room/)?[0-9a-fA-F-]{36}(/|$)`))
- traefik.http.routers.sealskin-raw.priority=100
- traefik.http.routers.sealskin-raw.entrypoints=websecure
- traefik.http.routers.sealskin-raw.tls=true
- traefik.http.routers.sealskin-raw.service=sealskin