Authentik + Coolify + Outline: SSO-Protected Internal Platform on Liquid Web (2026)
TL;DR
- One
docker-compose.ymldeploys Authentik (SSO) + Outline (wiki) + shared PostgreSQL + Redis + Caddy; Coolify installs separately via its own script - Measured idle RAM: ~2 GB (Authentik + Outline stack) + ~770 MB (Coolify stack) = ~2.8 GB total across the host
- Minimum Liquid Web tier: 8 GB Managed VPS (~$33–$40/mo); 16 GB recommended if you plan to deploy many apps via Coolify
- Replaces: Okta (~$20/mo for 10 users) + Vercel Pro ($20/mo) + Notion Team ($160/mo for 10 users) = $200/mo down to ~$33/mo
Most engineering teams pay three separate vendors with no shared identity layer tying them together: Okta handles SSO, Vercel handles deployments, and Notion holds documentation. Each has its own login, its own access controls, and its own billing. This guide deploys the open-source equivalents — Authentik for identity, Coolify for self-hosted PaaS deployments, and Outline for team wiki — on a single Liquid Web 8 GB VPS, connected through OIDC so all three share one set of credentials.
The result is a complete internal operations platform: your team logs in once through Authentik, accesses the deployment dashboard via Coolify, writes runbooks in Outline, and you control all of it from a single identity provider. No per-seat fees, no vendor lock-in, no siloed access logs.
Read the individual project guides before deploying the combo:
- Self-Host Authentik — Authentik architecture, OIDC/SAML setup, and standalone deployment
- Self-Host Coolify — Coolify install model, app deployments, and database management
- Self-Host Outline — Outline architecture, BSL license, and standalone setup
A note on Outline's license: Outline is released under BSL-1.1 (Business Source License). Internal team use is permitted without restriction. If you intend to offer Outline as a service to external customers, review the BSL terms at getoutline.com.
What you get
| Tool | Role | Replaces |
|---|---|---|
| Authentik | Identity provider — SSO, OIDC, SAML, user management | Okta, Auth0, Keycloak |
| Coolify | Self-hosted PaaS — deploy apps from Git, manage databases, env vars | Vercel, Railway, Render |
| Outline | Team wiki — searchable docs, runbooks, architecture decisions | Notion, Confluence |
| PostgreSQL 16 | Shared database (separate DB per app) | — |
| Redis | Shared cache + job queue (separate key namespaces) | — |
| Caddy | TLS termination, routing for all three subdomains | — |
Architecture:
sso.yourdomain.com→ Authentik (port 9000) — via our Caddydeploy.yourdomain.com→ Coolify (port 8000) — via our Caddy proxying to the Coolify processwiki.yourdomain.com→ Outline (port 3000) — via our Caddy
What's not included: transactional email for Outline notifications (bring your own SMTP — Postmark or Mailgun recommended), object storage for Outline file attachments (S3-compatible endpoint recommended for production; local storage works for small teams).
Prerequisites
- A Liquid Web 8 GB Managed VPS — verify pricing
- Docker Engine 25+ and Docker Compose V2
- Three subdomains pointing at the VPS IP:
sso.yourdomain.com,deploy.yourdomain.com,wiki.yourdomain.com - About 90 minutes
Installation order
The order matters because Authentik must be running before you configure OIDC in either Coolify or Outline.
- Coolify first — install via its own script; it manages its own Docker environment
- Authentik + Outline + shared infra — deploy via our
docker-compose.yml - Configure Authentik — create OIDC applications for Outline and Coolify
- Connect Outline — paste OIDC credentials into Outline's
.env - Connect Coolify — configure OIDC in Coolify's Settings → Authentication
Step 1: Install Coolify
Coolify uses its own installer — it sets up its own Docker Compose stack, databases, and reverse proxy internally. Run this as root (or with sudo) on your VPS:
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
The installer takes 2–5 minutes. It starts Coolify on port 8000. Verify it's running:
systemctl status coolify
# or
curl -s http://localhost:8000/api/health
Coolify runs independently of our Compose stack. Our Caddy instance will proxy deploy.yourdomain.com to localhost:8000 on the host.
Step 2: Deploy Authentik + Outline
Create your project directory and place the files below into it.
authentik-outline-stack/
├── docker-compose.yml
├── Caddyfile
├── .env
└── scripts/
└── init-db.sh
The complete docker-compose.yml
This file manages Authentik (server + worker), Outline, shared PostgreSQL 16, shared Redis, and Caddy. Coolify is not in this file — it runs separately via systemd.
# authentik-outline-stack/docker-compose.yml
# Tested 2026-05-02 on local Docker (4 vCPU / 8 GB RAM)
# Idle (this stack): ~1.2 GB RAM | Total host idle with Coolify: ~2 GB
# Coolify installs separately via its own installer script
services:
# ─── Shared infrastructure ────────────────────────────────────────────────
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
- ./scripts/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- stack_net
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
networks:
- stack_net
# ─── Authentik ────────────────────────────────────────────────────────────
authentik-server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_VERSION:-2024.12.3}
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_REDIS__PASSWORD: ${REDIS_PASSWORD}
AUTHENTIK_POSTGRESQL__HOST: db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_EMAIL__HOST: ${SMTP_ADDRESS}
AUTHENTIK_EMAIL__PORT: ${SMTP_PORT:-587}
AUTHENTIK_EMAIL__USERNAME: ${SMTP_USERNAME}
AUTHENTIK_EMAIL__PASSWORD: ${SMTP_PASSWORD}
AUTHENTIK_EMAIL__USE_TLS: "true"
AUTHENTIK_EMAIL__FROM: ${AUTHENTIK_FROM_EMAIL}
volumes:
- authentik_media:/media
- authentik_templates:/templates
ports:
- "9000:9000"
depends_on:
db:
condition: service_healthy
networks:
- stack_net
healthcheck:
test: ["CMD-SHELL", "ak healthcheck"]
interval: 20s
timeout: 10s
retries: 5
authentik-worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_VERSION:-2024.12.3}
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_REDIS__PASSWORD: ${REDIS_PASSWORD}
AUTHENTIK_POSTGRESQL__HOST: db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_EMAIL__HOST: ${SMTP_ADDRESS}
AUTHENTIK_EMAIL__PORT: ${SMTP_PORT:-587}
AUTHENTIK_EMAIL__USERNAME: ${SMTP_USERNAME}
AUTHENTIK_EMAIL__PASSWORD: ${SMTP_PASSWORD}
AUTHENTIK_EMAIL__USE_TLS: "true"
AUTHENTIK_EMAIL__FROM: ${AUTHENTIK_FROM_EMAIL}
volumes:
- authentik_media:/media
depends_on:
authentik-server:
condition: service_healthy
networks:
- stack_net
# ─── Outline ──────────────────────────────────────────────────────────────
outline:
image: outlinewiki/outline:${OUTLINE_VERSION:-0.80.2}
restart: unless-stopped
command: ["sh", "-c", "yarn sequelize:migrate && yarn start"]
environment:
NODE_ENV: production
SECRET_KEY: ${OUTLINE_SECRET_KEY}
UTILS_SECRET: ${OUTLINE_UTILS_SECRET}
DATABASE_URL: postgres://outline:${OUTLINE_DB_PASSWORD}@db:5432/outline
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/1
URL: https://${WIKI_DOMAIN}
PORT: 3000
# OIDC via Authentik — fill in after creating the Authentik application
OIDC_CLIENT_ID: ${OUTLINE_OIDC_CLIENT_ID}
OIDC_CLIENT_SECRET: ${OUTLINE_OIDC_CLIENT_SECRET}
OIDC_AUTH_URI: https://${SSO_DOMAIN}/application/o/outline/
OIDC_TOKEN_URI: https://${SSO_DOMAIN}/application/o/token/
OIDC_USERINFO_URI: https://${SSO_DOMAIN}/application/o/userinfo/
OIDC_DISPLAY_NAME: "Sign in with Authentik"
OIDC_SCOPES: "openid profile email"
# Email (for notifications)
SMTP_HOST: ${SMTP_ADDRESS}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USERNAME: ${SMTP_USERNAME}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_FROM_EMAIL: ${OUTLINE_FROM_EMAIL}
SMTP_SECURE: "false"
# Storage
FILE_STORAGE: local
FILE_STORAGE_LOCAL_ROOT_DIR: /var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE: "26214400"
volumes:
- outline_data:/var/lib/outline/data
depends_on:
db:
condition: service_healthy
authentik-server:
condition: service_healthy
networks:
- stack_net
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/_health || exit 1"]
interval: 20s
timeout: 10s
retries: 5
# ─── Caddy TLS proxy ──────────────────────────────────────────────────────
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:
authentik-server:
condition: service_healthy
outline:
condition: service_healthy
networks:
- stack_net
volumes:
db_data:
redis_data:
authentik_media:
authentik_templates:
outline_data:
caddy_data:
caddy_config:
networks:
stack_net:
driver: bridge
Database init script
Create scripts/init-db.sh to provision separate databases and users for Authentik and Outline:
#!/bin/bash
# scripts/init-db.sh — runs on first postgres start
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
CREATE USER authentik WITH PASSWORD '${AUTHENTIK_DB_PASSWORD}';
CREATE DATABASE authentik OWNER authentik;
CREATE USER outline WITH PASSWORD '${OUTLINE_DB_PASSWORD}';
CREATE DATABASE outline OWNER outline;
EOSQL
Make it executable:
chmod +x scripts/init-db.sh
Caddyfile
Our Caddy container handles TLS for all three subdomains. Coolify (port 8000) is proxied directly from the host network — note the host.docker.internal reference so Caddy can reach the Coolify process running outside the Compose network.
# Caddyfile — Authentik + Coolify + Outline
# Authentik SSO
sso.yourdomain.com {
reverse_proxy authentik-server:9000
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
}
}
# Coolify PaaS — proxies to the host process installed by Coolify's own installer
deploy.yourdomain.com {
reverse_proxy host.docker.internal:8000
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
}
}
# Outline wiki
wiki.yourdomain.com {
reverse_proxy outline:3000
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
}
}
Note on host.docker.internal: On Linux, add the following to the caddy service in your Compose file to make this hostname resolvable:
caddy:
...
extra_hosts:
- "host.docker.internal:host-gateway"
.env file
# .env — keep out of version control
# Versions
AUTHENTIK_VERSION=2024.12.3
OUTLINE_VERSION=0.80.2
# Domains
SSO_DOMAIN=sso.yourdomain.com
DEPLOY_DOMAIN=deploy.yourdomain.com
WIKI_DOMAIN=wiki.yourdomain.com
# Shared PostgreSQL superuser
POSTGRES_PASSWORD=change-me-postgres-superuser-password
# Authentik database
AUTHENTIK_DB_PASSWORD=change-me-authentik-db-password
# Authentik secret — generate with: openssl rand -hex 32
AUTHENTIK_SECRET_KEY=
# Outline database
OUTLINE_DB_PASSWORD=change-me-outline-db-password
# Outline secrets — generate each with: openssl rand -hex 32
OUTLINE_SECRET_KEY=
OUTLINE_UTILS_SECRET=
# Outline OIDC — fill in after creating the Authentik application (Step 4)
OUTLINE_OIDC_CLIENT_ID=
OUTLINE_OIDC_CLIENT_SECRET=
# Shared Redis
REDIS_PASSWORD=change-me-redis-password
# SMTP (for Authentik password resets + Outline notifications)
SMTP_ADDRESS=smtp.postmarkapp.com
SMTP_PORT=587
SMTP_USERNAME=your-postmark-token
SMTP_PASSWORD=your-postmark-token
AUTHENTIK_FROM_EMAIL=sso@yourdomain.com
OUTLINE_FROM_EMAIL=wiki@yourdomain.com
Generate all secrets at once:
for var in AUTHENTIK_SECRET_KEY OUTLINE_SECRET_KEY OUTLINE_UTILS_SECRET; do
echo "${var}=$(openssl rand -hex 32)"
done
Step 3: Start the core stack
Step 3a: Point DNS
sso.yourdomain.com. A <your-vps-ip>
deploy.yourdomain.com. A <your-vps-ip>
wiki.yourdomain.com. A <your-vps-ip>
Step 3b: Start the database and Redis
docker compose up -d db redis
docker compose ps db # wait for healthy (up to 60s)
Step 3c: Start Authentik
docker compose up -d authentik-server authentik-worker
docker compose logs -f authentik-server # wait until you see "Starting server"
Step 3d: Complete the Authentik initial setup
Visit https://sso.yourdomain.com/if/flow/initial-setup/ in your browser. Create the first admin user. This completes the Authentik bootstrap.
Step 3e: Start Outline and Caddy
docker compose up -d outline caddy
docker compose ps # all services should show healthy
Step 4: Create OIDC applications in Authentik
You need two OIDC providers in Authentik — one for Outline and one for Coolify.
4a: Outline OIDC provider
- In Authentik → Admin Interface → Applications → Providers → Create
- Select OAuth2/OpenID Provider
- Configure:
- Name:
outline-provider - Client type: Confidential
- Redirect URIs:
https://wiki.yourdomain.com/auth/oidc.callback - Scopes:
openid,profile,email - Leave all other fields at defaults
- Name:
- Click Finish — Authentik generates a Client ID and Client Secret. Copy both.
- Go to Applications → Create:
- Name:
Outline Wiki - Slug:
outline - Provider:
outline-provider
- Name:
- Paste the Client ID into
OUTLINE_OIDC_CLIENT_IDin your.env - Paste the Client Secret into
OUTLINE_OIDC_CLIENT_SECRETin your.env - Restart Outline to pick up the new values:
docker compose up -d --force-recreate outline
4b: Coolify OIDC provider
- In Authentik → Admin Interface → Applications → Providers → Create
- Select OAuth2/OpenID Provider
- Configure:
- Name:
coolify-provider - Client type: Confidential
- Redirect URIs:
https://deploy.yourdomain.com/auth/source/oauth/callback/authentik - Scopes:
openid,profile,email
- Name:
- Click Finish — copy the Client ID and Client Secret
- Go to Applications → Create:
- Name:
Coolify PaaS - Slug:
coolify - Provider:
coolify-provider
- Name:
Step 5: Configure Coolify OIDC login
- Open
https://deploy.yourdomain.comand log in with the Coolify admin account created during the installer setup - Go to Settings → Authentication → OAuth / OIDC
- Enable the OIDC provider and fill in:
- Client ID: (from step 4b)
- Client Secret: (from step 4b)
- Issuer URL:
https://sso.yourdomain.com/application/o/coolify/ - Redirect URI:
https://deploy.yourdomain.com/auth/source/oauth/callback/authentik
- Save. The Coolify login page will now show a Sign in with Authentik button.
Step 6: Verify the connected platform
# All Compose services healthy
docker compose ps
# Authentik
curl -s https://sso.yourdomain.com/api/v3/root/config/ | grep -q "authentik" && echo "Authentik OK"
# Outline health
curl -s https://wiki.yourdomain.com/_health
# {"status":"ok"}
# Coolify health
curl -s https://deploy.yourdomain.com/api/health
# {"status":"operational"} or similar
# Resource usage across the full host
docker stats --no-stream
End-to-end SSO test:
- Open
https://wiki.yourdomain.comin a private browser window - Click Sign in with Authentik — you are redirected to
sso.yourdomain.com - Log in with your Authentik credentials
- You are redirected back to Outline and logged in automatically
- Repeat for Coolify at
https://deploy.yourdomain.com
Troubleshooting
"Outline login redirects to Authentik but returns 'invalid_client'"
The OIDC client ID or secret in Outline's .env does not match what Authentik generated. In Authentik → Applications → Providers → outline-provider — view the provider details and copy the Client ID and Client Secret exactly into OUTLINE_OIDC_CLIENT_ID and OUTLINE_OIDC_CLIENT_SECRET. Also verify that OIDC_AUTH_URI in the Compose environment is https://sso.yourdomain.com/application/o/outline/ — the trailing slash is significant in some Authentik versions.
"Coolify shows 'SSO login failed — cannot connect to provider'"
Coolify reaches out to the Authentik well-known OIDC endpoint at the public URL. Ensure sso.yourdomain.com is publicly reachable from the VPS itself (not just from your laptop). Test with curl https://sso.yourdomain.com/.well-known/openid-configuration from the VPS shell. The Coolify OIDC issuer URL must be https://sso.yourdomain.com/application/o/coolify/ — using localhost:9000 will not work because Coolify's process is not inside the Compose network.
"Outline migrations fail with 'relation users does not exist'"
Outline requires database migrations to complete before the HTTP server starts. The Compose file's command uses yarn sequelize:migrate && yarn start to run migrations inline. If you override the command or start Outline before the database is healthy, migrations are skipped. Ensure the db healthcheck passes before Outline starts (depends_on: db: condition: service_healthy) and never start Outline with docker compose up -d outline before docker compose ps db shows healthy.
Runtime footprint
Measured 2026-05-02 on local Docker (4 vCPU / 8 GB RAM) — equivalent to a Liquid Web 8 GB Managed VPS.
| Service | Idle RAM | Peak RAM | Managed by |
|---|---|---|---|
| authentik-server | 350 MB | 600 MB | our Compose |
| authentik-worker | 250 MB | 400 MB | our Compose |
| outline | 300 MB | 550 MB | our Compose |
| db (PostgreSQL 16) | 256 MB | 500 MB | our Compose |
| redis (shared) | 50 MB | 80 MB | our Compose |
| caddy | 15 MB | 25 MB | our Compose |
| Subtotal (our stack) | 1,221 MB | 2,155 MB | |
| Coolify stack (server + db + proxy) | ~770 MB | ~1,200 MB | Coolify installer |
| Total host | ~2,000 MB | ~3,350 MB |
An 8 GB Managed VPS has approximately 5.9 GB free after OS overhead. This combined platform idles at ~2 GB and peaks at ~3.4 GB, leaving ~2.5 GB of headroom for apps you deploy through Coolify. Use a 16 GB VPS if you plan to run multiple always-on services through Coolify.
Cost vs Okta + Vercel + Notion
| Okta (10 users) | Vercel Pro | Notion Team (10 users) | This stack | |
|---|---|---|---|---|
| Monthly cost | ~$20/mo | $20/mo | ~$160/mo | ~$33–$40/mo VPS |
| Total | $200/mo | ~$33/mo | ||
| SSO / OIDC | ✓ | ✗ | ✗ | ✓ (Authentik) |
| App deployment PaaS | ✗ | ✓ | ✗ | ✓ (Coolify) |
| Team wiki / docs | ✗ | ✗ | ✓ | ✓ (Outline) |
| Self-hostable | ✗ | ✗ | ✗ | ✓ |
| Per-seat cost | $2/user | — | $16/user | $0 (unlimited users) |
Prices subject to change — verify Okta pricing at okta.com/pricing, Vercel at vercel.com/pricing, Notion at notion.so/pricing, and Liquid Web at liquidweb.com/vps-hosting/managed-vps/.
When this stack isn't right for you
- You need enterprise SAML for third-party SaaS — Authentik supports SAML outbound, but configuring complex enterprise integrations (e.g., AWS SSO, Google Workspace federation) requires careful blueprint setup. Okta has richer pre-built integrations for enterprise SaaS estates.
- You're deploying many concurrent apps via Coolify — each Coolify-managed app consumes RAM on the same host. A busy Coolify instance with 5+ running services can push the host past 6 GB. Move to a 16 GB VPS or provision a second VPS dedicated to Coolify workloads.
- You need Outline's enterprise features — structured permissions per collection, public sharing, and guest access are available in Outline's hosted plan. The self-hosted version under BSL-1.1 covers all core wiki functionality for internal teams.
- Your team is larger than 50 people — at this scale, a dedicated identity provider (Keycloak with clustered deployment, or a managed Okta contract) is more appropriate than a single-server Authentik instance. The single-server model is fine for small-to-midsize engineering teams.
Yes — that is exactly what this guide does. You create two separate OIDC providers (and two applications) within the same Authentik instance: one for Outline and one for Coolify. Each gets its own Client ID and Secret. Authentik handles both login flows independently and applies separate access policies per application.
Update image versions in your .env file (AUTHENTIK_VERSION and OUTLINE_VERSION) and run docker compose pull && docker compose up -d. Authentik runs its own database migrations on startup. Outline runs sequelize:migrate as part of its startup command in this Compose file. Always snapshot your PostgreSQL volume (pg_dumpall) before upgrading. Coolify updates independently via its own update mechanism in the Coolify UI.
Yes. Authentik supports OIDC, SAML, LDAP, and forward auth proxy modes. For any new app you deploy via Coolify, create a new OIDC Provider + Application in Authentik, add the redirect URI for that app, and configure the app's OIDC settings. Apps that don't support OIDC natively can use Authentik's forward auth proxy with Caddy as the middleware.

Software Developer & Automation Specialist
I build production AI agents, web scrapers, and automation pipelines. Most of what I publish here comes from the actual problems they run into: proxies that get banned, anti-bot stacks that fingerprint your client, RAG that drifts when the underlying data moves. Stack: Python, TypeScript, Go, FastAPI, LangChain, Crawlee, Playwright, deployed on AWS, GCP, and Cloudflare.
