Post

Build a homelab auth gateway with Traefik and Authentik

A practical, secret-free walkthrough of using Traefik and Authentik as the front door for self-hosted services.

Build a homelab auth gateway with Traefik and Authentik

Running apps in a homelab is easy. Running them safely on real domains is where the design starts to matter.

The pattern I use is simple:

  1. Traefik owns ports 80 and 443.
  2. Every app joins one shared Docker network called proxy.
  3. Apps do not publish host ports unless they truly need to.
  4. Authentik becomes the central login layer for apps that do not have their own authentication.
  5. Secrets stay in .env files and never in public documentation.

This post documents the gateway pattern in a way you can copy for your own lab.

This article intentionally uses placeholders like <your-domain> and <secret>. Never publish real API tokens, database passwords, S3 secrets, or private keys.


Architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Internet
  │
  ▼
Cloudflare DNS
  │
  ▼
Traefik container :80/:443
  │
  ├── public app route
  ├── HTTPS certificates via DNS challenge
  └── Authentik forward-auth middleware
       │
       ▼
Authentik embedded proxy outpost
  │
  ▼
Protected Docker service

The important part is that Traefik is the only public edge. Services sit behind it on Docker networking.


The shared Docker network

Create one external network once:

1
docker network create proxy

Every public service joins it:

1
2
3
networks:
  proxy:
    external: true

Traefik baseline

A minimal Traefik service looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - "80:80"
      - "443:443"
    env_file:
      - .env
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/acme.json:/acme.json
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-secure.entrypoints=https"
      - "traefik.http.routers.traefik-secure.rule=Host(`traefik.<your-domain>`)"
      - "traefik.http.routers.traefik-secure.tls=true"
      - "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare"
      - "traefik.http.routers.traefik-secure.service=api@internal"

networks:
  proxy:
    external: true

Store Cloudflare credentials in .env, not in the compose file:

CF_API_EMAIL=<your-cloudflare-email>
CF_DNS_API_TOKEN=<your-cloudflare-dns-token>

Authentik stack

Authentik needs a database and the server/worker pair:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
services:
  postgresql:
    image: postgres:16-alpine
    restart: unless-stopped
    env_file: .env
    environment:
      POSTGRES_DB: ${PG_DB:-authentik}
      POSTGRES_USER: ${PG_USER:-authentik}
      POSTGRES_PASSWORD: ${PG_PASS}
    volumes:
      - database:/var/lib/postgresql/data

  server:
    image: ghcr.io/goauthentik/server:2026.2.2
    container_name: authentik-server
    restart: unless-stopped
    command: server
    env_file: .env
    networks:
      - proxy
      - authentik_internal
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.authentik-secure.entrypoints=https"
      - "traefik.http.routers.authentik-secure.rule=Host(`auth.<your-domain>`)"
      - "traefik.http.routers.authentik-secure.tls=true"
      - "traefik.http.routers.authentik-secure.tls.certresolver=cloudflare"
      - "traefik.http.services.authentik.loadbalancer.server.port=9000"

      # Reusable middleware for protected apps
      - "traefik.http.middlewares.authentik.forwardauth.address=http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
      - "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true"
      - "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid"

  worker:
    image: ghcr.io/goauthentik/server:2026.2.2
    restart: unless-stopped
    command: worker
    env_file: .env
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

volumes:
  database:

networks:
  proxy:
    external: true
  authentik_internal:
    internal: true

Example secret file:

PG_DB=authentik
PG_USER=authentik
PG_PASS=<generate-a-long-password>
AUTHENTIK_SECRET_KEY=<generate-a-long-random-secret>
AUTHENTIK_ERROR_REPORTING__ENABLED=false
AUTHENTIK_HOST=https://auth.<your-domain>

Generate safe values:

1
openssl rand -base64 48

Protecting a service with Authentik

For a protected app, the HTTPS router gets this middleware:

1
- "traefik.http.routers.myapp-secure.middlewares=authentik@docker"

But that is only half of the setup. Authentik also needs a callback path for the same host:

1
2
3
4
5
6
- "traefik.http.routers.authentik-myapp-outpost.entrypoints=https"
- "traefik.http.routers.authentik-myapp-outpost.rule=Host(`myapp.<your-domain>`) && PathPrefix(`/outpost.goauthentik.io/`)"
- "traefik.http.routers.authentik-myapp-outpost.priority=100"
- "traefik.http.routers.authentik-myapp-outpost.tls=true"
- "traefik.http.routers.authentik-myapp-outpost.tls.certresolver=cloudflare"
- "traefik.http.routers.authentik-myapp-outpost.service=authentik"

Then create a Proxy Provider and Application inside Authentik:

  • Provider type: Proxy Provider
  • Mode: Forward auth / single application
  • External host: https://myapp.<your-domain>
  • Outpost: embedded proxy outpost

Testing

Check the app route:

1
curl -k -I https://myapp.<your-domain>/

Expected unauthenticated result:

1
2
HTTP/2 302
location: https://auth.<your-domain>/application/o/authorize/...

Check the outpost callback route:

1
curl -k -I https://myapp.<your-domain>/outpost.goauthentik.io/ping

Expected:

1
HTTP/2 204

If /ping is not 204, your callback router is missing or lower priority than the app router.


What not to protect with forward-auth

Do not put browser SSO in front of machine APIs that expect signed requests, webhooks, or CLI clients. Examples:

  • S3 API endpoints
  • container registry APIs
  • webhook URLs
  • OAuth callback endpoints not designed for it

For those, protect the admin UI but leave the API authentication native.


Backup checklist

1
2
cp docker-compose.yml backups/docker-compose.yml.$(date +%Y%m%d-%H%M%S)
cp .env backups/.env.$(date +%Y%m%d-%H%M%S)

Do not commit the backup folder if it contains .env files.


Final pattern

This gateway gives you one clean security story:

  • Traefik handles routing and TLS.
  • Authentik handles human login.
  • Apps stay on private Docker networks.
  • Secrets stay out of public docs.

These posts connect to this topic and help build the bigger homelab picture:

This post is licensed under CC BY 4.0 by the author.