Traefik Reverse Proxy in Docker on Synology
DSM's built-in reverse proxy is fine until you have a dozen containers and you're clicking the same hostname, port and WebSocket headers into it for the tenth time. That's when I gave up on it for containers. Traefik works from the other side: it watches the Docker socket, reads labels on your containers and sets up routes and Let's Encrypt certificates by itself. Add a few labels to a container and it's online under its hostname with HTTPS a minute later. This is Traefik 2.2 in Docker on DSM 6.2.
The catch on Synology: DSM's nginx already owns ports 80 and 443. You can move DSM off them, or let Traefik use other ports and forward 80/443 from the router to it. I do the latter. So the setup needs the Docker package, SSH, a docker share, a domain pointing at your IP (a wildcard *.example.com is nice), and router forwards from public 80 to NAS 8080 and public 443 to NAS 8443.
Folders and network
sudo mkdir -p /volume1/docker/traefik/letsencrypt
sudo touch /volume1/docker/traefik/letsencrypt/acme.json
sudo chmod 600 /volume1/docker/traefik/letsencrypt/acme.json
sudo docker network create proxy
Every container Traefik should reach joins the proxy network; databases and other internal containers do not.
Traefik's compose file
/volume1/docker/traefik/docker-compose.yml:
version: '2.4'
services:
traefik:
image: traefik:v2.2
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.le.acme.email=admin@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.tlschallenge=true"
- "--api.dashboard=true"
- "--log.level=INFO"
ports:
- "8080:80"
- "8443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /volume1/docker/traefik/letsencrypt:/letsencrypt
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=le"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$X9Ck3vJ2$$pLq4d7ZkQyD0mKk1rL8Yk/"
networks:
proxy:
external: true
The TLS challenge works on port 443 only, which is why the router forward to 8443 must exist before you start. Generate the basic-auth hash with htpasswd -nb admin secret on any Linux box and double every $ in the YAML.
cd /volume1/docker/traefik
sudo docker-compose up -d
sudo docker-compose logs -f
Publishing a container with labels
Take an existing service, for example a Gitea container. Its compose file gets the proxy network and labels, and loses its published port:
services:
gitea:
image: gitea/gitea:1.11
restart: unless-stopped
networks:
- proxy
- internal
labels:
- "traefik.enable=true"
- "traefik.http.routers.gitea.rule=Host(`git.example.com`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=le"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
networks:
proxy:
external: true
internal:
server.port tells Traefik which container port to talk to; it is required when the image exposes more than one. Run docker-compose up -d for that project and Traefik picks it up without a restart.
Non-Docker services
DSM itself, or a service on another machine, can be routed with a file provider. Add --providers.file.directory=/config to the command list, mount /volume1/docker/traefik/config:/config, and create /volume1/docker/traefik/config/dsm.yml:
http:
routers:
dsm:
rule: "Host(`nas.example.com`)"
entryPoints: [websecure]
tls:
certResolver: le
service: dsm
services:
dsm:
loadBalancer:
servers:
- url: "https://192.168.1.10:5001"
serversTransport: insecure
serversTransports:
insecure:
insecureSkipVerify: true
Firewall
Control Panel → Security → Firewall: allow 8080 and 8443 from anywhere, and restrict 5000/5001 to the LAN so DSM is only reachable through Traefik or locally. I don't leave 5001 open to the world, even with a valid certificate.
Testing
Expected to work:
curl -sI https://git.example.com/ | head -n 3
curl -sI http://git.example.com:80/ | grep -i location
sudo docker exec traefik cat /letsencrypt/acme.json | grep -c '"main"'
You should see HTTP/2 200, a redirect to https://, and a count equal to the number of hostnames Traefik has certificates for. The dashboard at https://traefik.example.com lists routers and services with green status.
Where it broke for me
- Traefik serves the default self-signed "TRAEFIK DEFAULT CERT" and the log shows
Unable to obtain ACME certificate for domains ... acme: error: 400 :: urn:ietf:params:acme:error:connection: port 443 from the Internet does not reach Traefik. Check the 443 → 8443 forward and the DSM firewall. permissions 644 for /letsencrypt/acme.json are too open: Traefik refuses to use the file.chmod 600it and restart.- A container is not picked up and the dashboard shows nothing:
traefik.enable=trueis missing (required because ofexposedbydefault=false), or the container is not on theproxynetwork. Gateway Timeoutfor one service: Traefik connected on the wrong network; settraefik.docker.network=proxyon the container's labels if it joins several networks.
DSM's reverse proxy still has a job for me: anything I want on the standard 443 without going through a container.