DEV Community

Elder Fernandes
Elder Fernandes

Posted on Originally published at selfhoststack-8z4.pages.dev

Self-Hosted Security Stack: Hardening Docker with CrowdSec, Traefik & Authentik

Exposing self-hosted applications to the public internet can be daunting. Bot scanners, credential stuffers, and automated zero-day exploits probe open ports continuously.

Rather than relying on proprietary cloud proxies or leaving default ports exposed, you can deploy an enterprise-grade defense perimeter directly on your server using three open-source cornerstones:

  1. Traefik v3: Dynamic reverse proxy with automated Let's Encrypt TLS certificates.
  2. CrowdSec: Collaborative, open-source IPS/WAF that parses container logs, analyzes attack behaviors, and leverages a global threat intelligence network.
  3. Authentik: Unified OpenID Connect / SAML identity provider with multi-factor authentication (MFA/Passkeys) and forward-auth middleware.

Here is the architectural blueprint to bulletproof your self-hosted stack.


The 3-Layer Defensive Architecture

[ Incoming Web Request ]
           │
           ▼
[ Port 80 / 443 — Traefik Reverse Proxy ]
           │
           ├──► [ CrowdSec Bouncer Plugin ] ── (Blocks malicious IPs via global consensus)
           │
           ├──► [ Authentik Forward Auth ]  ── (Enforces 2FA / WebAuthn before reaching app)
           │
           ▼
[ Protected Container App (e.g., n8n, Nextcloud, Grafana, Portainer) ]
Enter fullscreen mode Exit fullscreen mode

1. Traefik + CrowdSec Bouncer Docker Compose

version: '3.8'

services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: always
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entryPoints.web.address=:80"
      - "--entryPoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      # CrowdSec Bouncer Middleware Plugin
      - "--experimental.plugins.crowdsec-bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
      - "--experimental.plugins.crowdsec-bouncer.version=v1.3.3"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_certs:/letsencrypt
    networks:
      - web-gateway

  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: always
    environment:
      PGID: "1000"
      COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/whitelist-good-actors crowdsecurity/linux"
    volumes:
      - /var/log/auth.log:/var/log/auth.log:ro
      - /var/log/syslog:/var/log/syslog:ro
      - crowdsec_db:/var/lib/crowdsec/data/
      - crowdsec_config:/etc/crowdsec/
    networks:
      - web-gateway

networks:
  web-gateway:
    name: web-gateway

volumes:
  traefik_certs:
  crowdsec_db:
  crowdsec_config:
Enter fullscreen mode Exit fullscreen mode

2. Protecting Any Upstream App with Forward-Auth

To protect any admin tool (e.g. Portainer, n8n, Uptime Kuma, GlitchTip) behind Authentik SSO + CrowdSec WAF, simply add Traefik middleware labels to your app service in Docker Compose:

services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n-app
    restart: always
    networks:
      - web-gateway
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`automation.yourdomain.com`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
      - "traefik.http.routers.n8n.middlewares=crowdsec-bouncer@docker,authentik-auth@docker"
Enter fullscreen mode Exit fullscreen mode

With this configuration:

  1. Malicious IPs identified by CrowdSec's global network are dropped with 0 ms CPU waste.
  2. Legitimate visitors must authenticate via Passkey or TOTP before seeing the application UI.
  3. Internal app vulnerabilities (CVEs) cannot be probed without valid credentials.

5 Essential Hardening Rules for 2026

  1. Never Expose Raw DB Ports: Bind 5432 (Postgres), 6379 (Redis), and 3306 (MySQL) to Docker internal bridge networks only.
  2. Use Read-Only Container Filesystems: Add read_only: true and mount temporary paths via tmpfs: /tmp.
  3. Automate Container Vulnerability Scans: Run Trivy or Grype in CI/CD before deploying images.
  4. Deploy Fail2ban / CrowdSec for SSH: Disable password login (PasswordAuthentication no) and use Ed25519 keys.
  5. Set Automated Offsite Snapshots: Keep encrypted restic / Kopia backups in an independent S3 bucket or Hetzner Storage Box.

Build Your Complete Stack

Explore interactive Docker Compose generators, security benchmarks, and migration blueprints on SelfHostStack:

Top comments (0)