A members-only companion to the public homelab posts: the actual files, not just the story. Thanks for supporting the work.
The Traefik label set I put on every service
labels:
- traefik.enable=true
- "traefik.http.routers.APP.rule=Host(`app.example.com`)"
- traefik.http.routers.APP.entrypoints=websecure
- traefik.http.routers.APP.tls.certresolver=le
- traefik.http.services.APP.loadbalancer.server.port=PORT
- traefik.http.routers.APP.middlewares=crowdsec@file,sec-headers@file
The @file suffix is load-bearing. Traefik namespaces every object by the provider it came from, so crowdsec@file and crowdsec@docker are different middlewares and referring to the wrong one fails at router creation with a message that does not mention providers at all. Shared middlewares live in the file provider precisely so they survive any one container going away.
CrowdSec and the Traefik bouncer
CrowdSec runs as its own container reading Traefik's access logs; the bouncer (a forward-auth middleware) checks each request against CrowdSec's decisions plus the community blocklist, pulled in stream mode so blocking is near-instant.
Two operational notes. Traefik has to be writing access logs in JSON to a path the CrowdSec container can read, which in practice means a shared volume and a log rotation policy, or the file grows until the disk is full. And the bouncer needs to see the client's real address: if anything sits in front of Traefik, the source IP it logs is that thing, and CrowdSec will cheerfully ban your own front end.
The egress firewall (the part people skip)
# Block an untrusted container network from reaching prod, metadata, and SMTP
iptables -I DOCKER-USER -s 172.30.0.0/24 -d 169.254.169.254 -j DROP # cloud metadata
iptables -I DOCKER-USER -s 172.30.0.0/24 -p tcp --dport 25 -j DROP # SMTP
iptables -I DOCKER-USER -s 172.30.0.0/24 -d 172.18.0.0/16 -j DROP # prod network
iptables -A DOCKER-USER -s 172.30.0.0/24 -j ACCEPT # allow the rest
Order is the whole thing here. -I inserts at the top and -A appends at the bottom, and since the chain is evaluated top to bottom with the first match winning, writing those four lines in a different order gives you a rule set that reads correctly and blocks nothing. Check the result with iptables -L DOCKER-USER -n -v --line-numbers and read it in the order the kernel will.
Two more things worth knowing before you rely on this. These rules are not persistent: a reboot clears them unless you save them with something like iptables-persistent or install them from a systemd unit that runs after Docker. And DOCKER-USER sits in the FORWARD path, so it governs traffic routed between networks; it does not cover a container reaching the host itself, which needs a rule in the INPUT chain instead.
Adapt the subnets to your own networks. Questions? contact@paulhitt.com.




