Skip to main content

Authentik Hosting: Docker Setup and Real Cost

authentik (github.com/goauthentik/authentik, 25.4k stars on 2026-09-09) is a self-hosted identity provider that puts one login page in front of every app you run.

What authentik hosting is not: a managed option. Authentik Security publishes no cloud edition. Its pricing page says, plainly, "We do not currently provide a hosted version of authentik" (goauthentik.io/pricing, checked 2026-09-09). Hosting it means three containers on a Linux box: server, worker, and PostgreSQL. Redis is no longer one of them. It was removed in release 2025.10. The published minimum is 2 CPU cores and 2 GB of RAM.

Every price and version on this page was fetched from the vendor's own page on 2026-09-09. The links go to those pages so you can re-check them.

What changed since most authentik guides were written​

Nearly every self-hosting walkthrough you will find still ships a four-service stack with a Redis container in it. That stack has been wrong for about a year.

ReleaseWhat it changed for a self-hoster
2025.8Background tasks moved off Redis and onto PostgreSQL.
2025.10"authentik no longer uses Redis at all." Caching, WebSockets and the embedded outpost all moved to PostgreSQL. Expect roughly 50% more database connections. ak create_recovery_key now takes minutes instead of years. PostgreSQL over TLS requires TLS 1.3 or the Extended Master Secret extension.
2026.8 (2026-08-18)Forwarded request headers are only honoured from a trusted proxy network. hash_password no longer accepts a password as a positional argument.
2026.8.1 (2026-09-01)Current stable.

Sources: 2025.10 release notes, 2026.8 release notes, releases index.

If you are upgrading an older install rather than starting fresh, the Redis container is removed by docker compose up -d --remove-orphans. If you pointed authentik at an external Redis, delete the configuration and nothing else.

What does hosting authentik actually cost?​

Okta Workforce (Starter)Auth0 Essentials (B2C)Auth0 Essentials (B2B)authentik on a 4 GB VPS
Entry price$6/user/month, billed annually$35/month at 500 MAU$150/month at 500 MAU$72/month regular, $36 for the first 2 months
Free tierNoneUp to 25,000 MAUUp to 25,000 MAUSoftware is free; you pay for the server
Contract floor$1,500/year minimumNoneNoneNone
Enterprise connections (SAML, LDAP, AD)Not itemised on okta.com/pricingNot available on B2C tiers3 included, $100/month each afterIncluded, no count limit
MFANot itemised on okta.com/pricingPro MFA factors includedPro MFA included; Enterprise MFA is a $100/month add-onIncluded (TOTP, WebAuthn, passkeys)
Data locationOkta's cloudAuth0's cloudAuth0's cloudYour server
LicenseProprietaryProprietaryProprietaryMIT core, paid Enterprise tier

Verified 2026-09-09 at okta.com/pricing, auth0.com/pricing and liquidweb.com/vps-hosting/managed-vps.

Against Okta, self-hosting wins on price before you compare a single feature. Okta's own terms are $6 per user per month billed annually with a $1,500 annual contract minimum. That floor binds until roughly 21 users, so a ten-person company pays $1,500 a year for workforce SSO either way. Year one on the 4 GB Managed VPS is $792/year subject to change · verified 2026-09-09: two months at $36, ten at $72.

Against Auth0, the arithmetic reverses, and it reverses hard. Auth0's free tier covers 25,000 monthly active users on both the B2C and the B2B track. If you are authenticating consumers and you do not need SAML, moving to a self-hosted authentik replaces something that costs nothing today with a server bill of $792 a year. Read that twice before provisioning anything.

The B2C crossover sits at about 1,000 MAU: Essentials is $35/mo subject to change · verified 2026-09-09 at 500 MAU and $70/month at 1,000, which is the point where Auth0 costs more than the VPS. On the B2B track there is no crossover to wait for. Essentials opens at $150/mo subject to change · verified 2026-09-09 at 500 MAU, already double the server, and enterprise connections beyond the first three add $100/month each.

There is a third option the comparison usually skips. Elestio sells managed authentik from $16/month (elest.io/open-source/authentik, checked 2026-09-09). If authentik is the only thing you intend to run and you want someone else applying the patches, that is cheaper than any VPS on this page. A VPS earns its price when the same box is also running your other services. Then authentik is a marginal tenant rather than the whole invoice.

Prerequisites​

  • A VPS with at least 2 CPU cores and 2 GB of RAM. authentik's install docs state that minimum; 4 GB gives you room for the apps you will put behind it.
  • Docker Engine 25+ and Docker Compose V2.
  • A domain with an A record pointing at the VPS (auth.yourdomain.com).
  • About 30 minutes.

The docker-compose.yml​

This is authentik's official compose file adapted for a Caddy front end. The upstream version publishes ports 9000 and 9443 on the host; this one keeps them on an internal network so Caddy is the only thing listening. Diff it against docs.goauthentik.io/compose.yml whenever you upgrade.

# authentik/docker-compose.yml
# Adapted from https://docs.goauthentik.io/compose.yml (2026.8.1)
# Changes: no published ports, Caddy added as the TLS terminator.

services:
postgresql:
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
env_file:
- .env
environment:
POSTGRES_DB: ${PG_DB:-authentik}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
volumes:
- database:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s
networks:
- authentik_net

server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.8.1}
restart: unless-stopped
command: server
shm_size: 512mb
env_file:
- .env
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./data:/data
- ./custom-templates:/templates
depends_on:
postgresql:
condition: service_healthy
networks:
- authentik_net

worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.8.1}
restart: unless-stopped
command: worker
shm_size: 512mb
# Upstream runs the worker as root so it can write to the mounted
# directories and reach the Docker socket for container outposts.
user: root
env_file:
- .env
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
- ./certs:/certs
- ./custom-templates:/templates
depends_on:
postgresql:
condition: service_healthy
networks:
- authentik_net

caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- server
networks:
- authentik_net

volumes:
database:
caddy_data:
caddy_config:

networks:
authentik_net:
driver: bridge

Two details worth knowing before you copy it. ./data, ./certs and ./custom-templates are bind mounts, so they back up with tar rather than a volume dance. And the docker socket mount on the worker is what lets authentik manage container outposts; drop it if you will never deploy one, and the worker still runs everything else.

Caddyfile​

# Caddyfile — authentik reverse proxy
auth.yourdomain.com {
reverse_proxy server:9000 {
health_uri /-/health/ready/
health_interval 30s
}

encode gzip

header {
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
}
}

authentik listens on 9000 for HTTP and 9443 for HTTPS with a self-signed certificate. Caddy terminates TLS, so proxy to 9000 over the Docker network and never publish either port on the host.

The health path matters and it is the thing most copied Caddyfiles get wrong. authentik's monitoring docs define /-/health/live/, which returns 200 while the process is up, and /-/health/ready/, which returns 200 only when a PostgreSQL connection succeeds. /healthz/ready is not an authentik endpoint. Point active health checks at /-/health/ready/ so Caddy stops routing to a server that has lost its database.

Caddy sets X-Forwarded-For, X-Forwarded-Proto and the original Host on reverse_proxy by default, which is exactly the set authentik's reverse-proxy docs ask for. Because the Caddy container sits on a bridge network in 172.16.0.0/12, it already falls inside authentik's default trusted-proxy ranges, so 2026.8's header restriction does not bite here. It bites when your proxy is somewhere else — a separate host, or a load balancer on a network outside those defaults.

.env file​

# .env — keep out of version control

# authentik ships a calendar-versioned release most months.
# Check https://github.com/goauthentik/authentik/releases before pinning.
AUTHENTIK_TAG=2026.8.1

# 50+ random characters. Generated once, never rotated.
# openssl rand -base64 60 | tr -d '\n'
AUTHENTIK_SECRET_KEY=

# PostgreSQL. Passwords longer than 99 characters are not supported.
# openssl rand -base64 36 | tr -d '\n'
PG_USER=authentik
PG_DB=authentik
PG_PASS=

# SMTP — optional, but password resets and MFA enrolment need it.
AUTHENTIK_EMAIL__HOST=smtp.example.com
AUTHENTIK_EMAIL__PORT=587
AUTHENTIK_EMAIL__USERNAME=your-smtp-user
AUTHENTIK_EMAIL__PASSWORD=your-smtp-password
AUTHENTIK_EMAIL__USE_TLS=true
AUTHENTIK_EMAIL__FROM=authentik@yourdomain.com

# Keep crash reports on your own box.
AUTHENTIK_ERROR_REPORTING__ENABLED=false

# Only needed if your reverse proxy connects from outside
# 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fe80::/10, ::1/128.
# AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS=

AUTHENTIK_SECRET_KEY signs sessions, tokens and cookies. Change it after setup and you invalidate every active session and every enrolled TOTP seed. Generate it once, put it in your password manager, and leave it alone.

One more trap that costs people an afternoon: authentik works in UTC internally, and its install docs warn against overriding timezone files inside the containers. Doing so breaks OAuth and SAML flows, which is not where anyone thinks to look.

First-run setup​

# 1. Generate the secret key and database password
openssl rand -base64 60 | tr -d '\n' # -> AUTHENTIK_SECRET_KEY
openssl rand -base64 36 | tr -d '\n' # -> PG_PASS

# 2. Create the bind-mount directories
mkdir -p data certs custom-templates

# 3. Bring the stack up
docker compose up -d

# 4. Watch the first boot — migrations take about a minute
docker compose logs -f server

# 5. Open https://auth.yourdomain.com
# A fresh instance redirects to its initial setup flow,
# where you create the first admin account.

If you land on an ordinary login page instead, an admin account already exists. Mint a one-time admin link:

docker compose run --rm server create_recovery_key 10 akadmin

Since 2025.10 the first argument is minutes, not years. Guides written before October 2025 will tell you it is years, and a ten-year admin link left lying in a terminal history is a genuine problem.

Verify the stack​

docker compose ps

# 200 while the process is alive
curl -s -o /dev/null -w '%{http_code}\n' https://auth.yourdomain.com/-/health/live/

# 200 only when PostgreSQL is reachable
curl -s -o /dev/null -w '%{http_code}\n' https://auth.yourdomain.com/-/health/ready/

# Prove email works before a user needs a password reset
docker compose exec worker ak test_email you@example.com

docker stats --no-stream

How much RAM does authentik need?​

authentik's install docs give a floor of 2 CPU cores and 2 GB of RAM.

We measured this stack at 610 MB idle and 925 MB peak on 2026-05-02, on local Docker with 4 vCPU and 8 GB. That run used 2024.12.3, when Redis was still in the stack, so read it as history rather than a current figure. We have not re-measured 2026.8.1 and will not publish a number we did not run.

Service (measured 2026-05-02 on 2024.12.3)IdlePeak
server250 MB400 MB
worker200 MB300 MB
postgresql 16100 MB150 MB
redis (removed in 2025.10)50 MB60 MB
caddy10 MB15 MB
Total610 MB925 MB

What provably changed since: one fewer container, and PostgreSQL carrying roughly 50% more connections than before. Expect the database's share to grow and Redis's 50 MB to disappear.

Size for 4 GB when authentik is the only tenant. Size for 8 GB when it shares the box with something substantial — Coolify running your app deployments, or an analytics stack like Plausible. The apps that most benefit from sitting behind authentik, a self-hosted wiki like Outline among them, are exactly the ones you were going to co-locate anyway.

Upgrading​

# Read the release notes first:
# https://github.com/goauthentik/authentik/releases

# Update the tag in .env, then:
docker compose pull server worker
docker compose up -d --remove-orphans

docker compose ps
docker compose logs server --tail 30

--remove-orphans is what clears the Redis container on an upgrade across 2025.10. Migrations run automatically when the server starts. Read the release notes rather than skimming them: 2026.8 alone changed proxy-header handling and removed a positional argument from hash_password, and both are the kind of change that looks like a network problem at 2am.

Can you run authentik in HA?​

Yes, and it is a smaller job than older guides suggest.

authentik's high-availability page states that "authentik server and worker instances are stateless. All session state and configuration data is stored in the database" (docs.goauthentik.io/install-config/high-availability, checked 2026-09-09). Scaling out means running more server and worker containers, on more hosts, pointed at the same PostgreSQL. No shared filesystem is required: /data holds uploaded icons, flow backgrounds and CSV reports, and authentik lets you reference external URLs for those instead of uploading them.

So the whole HA problem is PostgreSQL. authentik does not ship clustering of its own; it points at PostgreSQL's own replication, read replicas, and poolers such as PgBouncer or Pgpool. Since 2025.10 removed Redis there is one less stateful component to make redundant, and roughly 50% more database connections to plan for — raise max_connections or front the database with a pooler before you add replicas.

Be honest about what the Compose file above gives you, which is zero HA. One VPS reboot is one SSO outage for every application behind it. If losing SSO takes your company offline, run PostgreSQL as a managed replicated service and keep two authentik hosts behind a load balancer. If it takes down an internal dashboard, one server with tested backups is the proportionate answer.

When self-hosting authentik is the wrong call​

  • You are a consumer app under 25,000 MAU. Auth0's free tier covers you today at $0. Self-hosting swaps that for a server bill and an upgrade cadence. Stay on Auth0 until you need SAML or you cross the crossover above.
  • A contract requires a certified identity provider. A self-hosted instance carries no certification of its own; the operator's controls are the controls, and the operator is you. If a customer's security review asks for a SOC 2 report covering your IdP, buying one from a vendor is the shorter path.
  • You want someone else to run it. Authentik Security does not sell a hosted edition. Managed authentik means a third party such as Elestio, from $16/month.
  • You need the Enterprise feature set. Privileged access management, Google Workspace and Microsoft Entra ID integrations, device compliance and advanced audit logging sit in the paid tier at $5 per user per month billed annually, with external users at $0.02 each (goauthentik.io/pricing, checked 2026-09-09). Priced per seat, that is a dollar under Okta Starter. The self-hosting saving lives in the community edition.
  • SSO downtime is unacceptable and PostgreSQL HA is not on your roadmap. See the HA section. A single node is a single point of failure for every app behind it.

For the wider set of options in this vertical, including Keycloak, see the self-hosted identity and SSO hub.

Backups and exit strategy​

# Configuration, users, groups, flows, policies and MFA enrolments
docker compose exec -T postgresql pg_dump -U authentik authentik > authentik_$(date +%Y%m%d).sql

# Uploaded media, external certificates, custom templates
tar czf authentik_files_$(date +%Y%m%d).tar.gz data certs custom-templates

PostgreSQL holds everything except uploaded files, TOTP seeds included, so a pg_dump plus the .env file is a complete restore. Run the dump nightly to object storage. The tarball can be weekly, since those directories change rarely.

Migrating away is mostly metadata. Export each SAML or OIDC provider's metadata from the admin UI and import it into the destination IdP. Passwords are hashed and are not portable, so budget for a forced password-reset cycle and plan MFA re-enrolment for anyone whose new provider does not accept imported TOTP seeds.

Frequently Asked Questions

No. Release 2025.10 states that 'authentik no longer uses Redis at all.' Caching, background tasks, WebSockets and the embedded outpost all moved to PostgreSQL, finishing a migration that started with tasks in 2025.8. If you are upgrading, `docker compose up -d --remove-orphans` deletes the Redis container; if you used an external Redis, just remove the configuration. The trade-off is documented in the same release notes: expect roughly 50% more database connections to PostgreSQL, so check `max_connections` before you scale out.

They run the same image with different commands. The server handles API requests, flow executions and SSO requests, and it also contains the embedded outpost that serves proxy providers. The worker executes background work: sending email, the event notification system, and everything listed on the System Tasks page. Tasks are queued in a PostgreSQL table and workers are notified through a PostgreSQL NOTIFY trigger. If the worker is down, logins still succeed but email, notifications and scheduled tasks stop.

Yes, through a proxy provider. authentik's architecture docs describe the embedded outpost as a sub-component of the server that exists specifically so you can use proxy providers without deploying a separate outpost. The outpost authenticates the request before it reaches the app, so nothing changes inside the application. Create a Proxy Provider in the admin UI, attach it to an Application, and route the app's hostname through the outpost in your Caddyfile. This is the usual way to put a login in front of Grafana, Portainer or an internal dashboard.

In the admin UI go to Applications, then Providers, then Create, and choose an OAuth2/OpenID Provider. Set the redirect URI to your app's callback URL. authentik issues a Client ID and Client Secret. Create an Application that points at the provider. Your discovery document is at https://auth.yourdomain.com/application/o/your-app-slug/.well-known/openid-configuration. Hand that URL to any standard OIDC library and it will fetch the endpoints and signing keys itself.

PostgreSQL is the critical target. authentik stores all configuration and data there except uploaded files, which means users, groups, flows, policies, providers and TOTP enrolments are all inside a single pg_dump. Run that nightly to object storage. Separately, tar the ./data, ./certs and ./custom-templates directories weekly; they hold uploaded icons, flow backgrounds, CSV reports, external certificates and email templates. Keep the .env file with the backups, because AUTHENTIK_SECRET_KEY is required to decrypt sessions on restore.

Common mistakes and fixes

Fresh install: the login page appears but there is no way to create the first admin.

A new instance redirects to its initial setup flow the first time you open it. If you land on a normal login page instead, an admin account already exists. Mint a temporary admin link with `docker compose run --rm server create_recovery_key 10 akadmin`. Since release 2025.10 the first argument is minutes, not years, so that link is valid for 10 minutes.

After upgrading to 2026.8, HTTPS requests are treated as HTTP, or every client shows the same IP.

2026.8 only honours `X-Forwarded-Proto`, `X-Forwarded-Host` and `X-Forwarded-For` when the connection arrives from a trusted proxy network. The defaults are 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fe80::/10 and ::1/128, which already cover a Docker Compose bridge network. If your proxy connects from outside those ranges, set `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS` in `.env` to the networks your proxy actually uses, and nothing else.

PostgreSQL refuses new connections after upgrading past 2025.10.

authentik dropped Redis in 2025.10 and moved caching, tasks, WebSockets and the embedded outpost into PostgreSQL. Its own release notes say to expect roughly 50% more database connections. Raise `max_connections` on PostgreSQL, or put PgBouncer in front of it, before adding server or worker replicas.

Password-reset and MFA emails never arrive.

Test from the worker, not the server: `docker compose exec worker ak test_email you@example.com`. Port 587 needs `AUTHENTIK_EMAIL__USE_TLS=true`; port 465 needs `AUTHENTIK_EMAIL__USE_SSL=true`, and the two are mutually exclusive. If the host resolves but authentication fails, the credentials belong to your relay (SendGrid, Postmark, or your host's SMTP), not to authentik.