Authentik Embedded Outpost with Docker Compose, Kubernetes and NGINX
In light of debugging for many hours and after realizing many people online having issues with Authentik and Docker, I'm going to share my configs that worked. In particular, many people were claiming the latest version of the Authentik embedded outpost no longer worked. While I think the docs are not very good in this area, the outpost is working fine.
Some things to take note of:
- Using the embedded outpost and not a separate manual outpost (although I got this working too)
- version:
authentik version 2024.6.3
Docker-Compose Setup
Configuration
http {
# Basic HTTP configuration
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging settings
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error_debug.log debug;
# This can be very useful for debugging (See usage in blocks below)
log_format detailed_headers_log '[$time_local] $remote_addr - $remote_user - $server_name to: $upstream_addr: $request $status '
'Host: "$host" '
'X-Real-IP: "$remote_addr" '
'X-Forwarded-For: "$proxy_add_x_forwarded_for" '
'X-Forwarded-Proto: "$scheme" '
'X-Forwarded-Host: "$host" '
'X-Forwarded-Port: "$server_port" '
'X-Original-URL: "$scheme://$http_host$request_uri" '
'http_host: "$http_host" '
'request_uri: "$request_uri" '
'cookie: "$http_cookie" '
'set_cookie: "$sent_http_set_cookie" '
'upstream_http_set_cookie: "$upstream_http_set_cookie"';
server {
listen 80;
server_name localhost;
# I'm serving a dummy hello world behind auth
root /usr/share/nginx/html;
index index.html;
# Increase buffer size for large headers
# This is needed only if you get 'upstream sent too big header while reading response
# header from upstream' error when trying to access an application protected by goauthentik
proxy_buffers 8 16k;
proxy_buffer_size 32k;
# Make sure not to redirect traffic to a port 4443
port_in_redirect off;
location / {
try_files $uri $uri/ /index.html;
proxy_set_header Host $host;
##############################
# authentik-specific config
##############################
auth_request /outpost.goauthentik.io/auth/nginx;
error_page 401 = @goauthentik_proxy_signin;
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
# translate headers from the outposts back to the actual upstream
auth_request_set $authentik_username $upstream_http_x_authentik_username;
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
auth_request_set $authentik_email $upstream_http_x_authentik_email;
auth_request_set $authentik_name $upstream_http_x_authentik_name;
auth_request_set $authentik_uid $upstream_http_x_authentik_uid;
proxy_set_header X-authentik-username $authentik_username;
proxy_set_header X-authentik-groups $authentik_groups;
proxy_set_header X-authentik-email $authentik_email;
proxy_set_header X-authentik-name $authentik_name;
proxy_set_header X-authentik-uid $authentik_uid;
}
location /outpost.goauthentik.io {
proxy_pass http://server:9000/outpost.goauthentik.io;
proxy_set_header Host $http_host;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
# More detailed debug logs for header debug
access_log /var/log/nginx/authentik_access_debug.log detailed_headers_log;
add_header Set-Cookie $auth_cookie;
auth_request_set $auth_cookie $upstream_http_set_cookie;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
location @goauthentik_proxy_signin {
internal;
# More detailed debug logs for header debug
access_log /var/log/nginx/redirect_authentik_access_debug.log detailed_headers_log;
add_header Set-Cookie $auth_cookie;
return 302 $scheme://$http_host/outpost.goauthentik.io/start?rd=$request_uri;
}
}
}
events {
worker_connections 1024;
}
version: "3.4"
services:
# For Authentik persistence
postgresql:
image: docker.io/library/postgres:12-alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB} -U ${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- database:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_DB: ${PG_DB:-authentik}
env_file:
- .env
# For Authentik persistence
redis:
image: docker.io/library/redis:alpine
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- redis:/data
# Authentik Core
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-latest}
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./media:/media
- ./custom-templates:/templates
env_file:
- .env
ports:
- "${COMPOSE_PORT_HTTP:-9000}:9000"
- "${COMPOSE_PORT_HTTPS:-9443}:9443"
depends_on:
- postgresql
- redis
# Authentik Worker Node
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-latest}
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./media:/media
- ./certs:/certs
- ./custom-templates:/templates
env_file:
- .env
depends_on:
- postgresql
- redis
# Reverse proxy for talking to our applications that are protected by Authentik
nginx:
image: nginx:latest
ports:
- "8181:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./index.html:/usr/share/nginx/html/index.html:ro
- ./index.html:/usr/share/nginx/index.html:ro
restart: unless-stopped
volumes:
database:
driver: local
redis:
driver: local
<html>
<body>
hi
</body>
</html
Tips & Debugging
Debugging NGINX is what really saved me here from logs.
Add an error log (see above) with detailed logging fields containing the headers:
error_log /var/log/nginx/error_debug.log debug;
# This can be very useful for debugging (See usage in blocks below)
log_format detailed_headers_log '[$time_local] $remote_addr - $remote_user - $server_name to: $upstream_addr: $request $status '
'Host: "$host" '
'X-Real-IP: "$remote_addr" '
'X-Forwarded-For: "$proxy_add_x_forwarded_for" '
'X-Forwarded-Proto: "$scheme" '
'X-Forwarded-Host: "$host" '
'X-Forwarded-Port: "$server_port" '
'X-Original-URL: "$scheme://$http_host$request_uri" '
'http_host: "$http_host" '
'request_uri: "$request_uri" '
'cookie: "$http_cookie" '
'set_cookie: "$sent_http_set_cookie" '
'upstream_http_set_cookie: "$upstream_http_set_cookie"';
Add them to each /location block by dropping in:
access_log /var/log/nginx/authentik_access_debug.log detailed_headers_log;
Kubernetes Setup
First, read this https://docs.goauthentik.io/docs/providers/proxy/server_nginx.
I am using an NGINX ingress where:
test.homelab.devis my protected routesso.homelab.devis my authentik serverauthentik-server:80is the internal service and port- Runs in default namespace
Enable auth snippets on NGINX ingress controller
In order for the recommended annotations to work, I needed to enable auth snippets on my K8s cluster.
See: https://stackoverflow.com/questions/77274854/ingress-controller-does-not-allow-snippets
- Get the config map related to your ingress controller
$ kubectl get -A cm | grep ingress | grep controller
kube-system rke2-ingress-nginx-controller 1 377d
- Edit the config map by changing allow-snippet-annotations: "false" to allow-snippet-annotations: "true"
$ kubectl -n kube-system edit cm rke2-ingress-nginx-controller
"/tmp/kubectl-edit-1940673600.yaml" 25L, 910C written
configmap/rke2-ingress-nginx-controller edited
- Retart the controller
$ kubectl -n kube-system rollout restart ds rke2-ingress-nginx-controller
daemonset.apps/rke2-ingress-nginx-controller restarted
Configure NGINX ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: authentik-outpost
namespace: default
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/auth-url: http://authentik-server.default.svc.cluster.local/outpost.goauthentik.io/auth/nginx
nginx.ingress.kubernetes.io/auth-signin: https://test.homelab.dev/outpost.goauthentik.io/start?rd=$escaped_request_uri
nginx.ingress.kubernetes.io/auth-response-headers: Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
spec:
tls:
- hosts:
- test.homelab.dev
secretName: homelab-wildcard-tls
rules:
- host: test.homelab.dev
http:
paths:
- path: /outpost.goauthentik.io
pathType: Prefix
backend:
service:
name: authentik-server
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
Setup Authentik Outpost
- Go to https://sso.homelab.dev/if/admin/#/outpost/outposts (note: your URL may be different).
- Edit outpost
- Make sure it's type
Proxy - Add your application by clicking arrow over to
Selected Applications - Advanced settings:
...
authentik_host: https://sso.homelab.dev
...
- Hit
Update
Setup Authentik Provider
The External host will be my application https://test.homelab.dev.