Skip to main content

Authentik + Coolify + Outline: SSO-Protected Internal Platform on Liquid Web (2026)

· 15 min read
Yassine El Haddad
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.

TL;DR

  • One docker-compose.yml deploys 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:

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

ToolRoleReplaces
AuthentikIdentity provider — SSO, OIDC, SAML, user managementOkta, Auth0, Keycloak
CoolifySelf-hosted PaaS — deploy apps from Git, manage databases, env varsVercel, Railway, Render
OutlineTeam wiki — searchable docs, runbooks, architecture decisionsNotion, Confluence
PostgreSQL 16Shared database (separate DB per app)
RedisShared cache + job queue (separate key namespaces)
CaddyTLS termination, routing for all three subdomains

Architecture:

  • sso.yourdomain.com → Authentik (port 9000) — via our Caddy
  • deploy.yourdomain.com → Coolify (port 8000) — via our Caddy proxying to the Coolify process
  • wiki.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.

  1. Coolify first — install via its own script; it manages its own Docker environment
  2. Authentik + Outline + shared infra — deploy via our docker-compose.yml
  3. Configure Authentik — create OIDC applications for Outline and Coolify
  4. Connect Outline — paste OIDC credentials into Outline's .env
  5. 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

  1. In Authentik → Admin InterfaceApplicationsProvidersCreate
  2. Select OAuth2/OpenID Provider
  3. 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
  4. Click Finish — Authentik generates a Client ID and Client Secret. Copy both.
  5. Go to ApplicationsCreate:
    • Name: Outline Wiki
    • Slug: outline
    • Provider: outline-provider
  6. Paste the Client ID into OUTLINE_OIDC_CLIENT_ID in your .env
  7. Paste the Client Secret into OUTLINE_OIDC_CLIENT_SECRET in your .env
  8. Restart Outline to pick up the new values:
docker compose up -d --force-recreate outline

4b: Coolify OIDC provider

  1. In Authentik → Admin InterfaceApplicationsProvidersCreate
  2. Select OAuth2/OpenID Provider
  3. Configure:
    • Name: coolify-provider
    • Client type: Confidential
    • Redirect URIs: https://deploy.yourdomain.com/auth/source/oauth/callback/authentik
    • Scopes: openid, profile, email
  4. Click Finish — copy the Client ID and Client Secret
  5. Go to ApplicationsCreate:
    • Name: Coolify PaaS
    • Slug: coolify
    • Provider: coolify-provider

Step 5: Configure Coolify OIDC login

  1. Open https://deploy.yourdomain.com and log in with the Coolify admin account created during the installer setup
  2. Go to SettingsAuthenticationOAuth / OIDC
  3. 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
  4. 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:

  1. Open https://wiki.yourdomain.com in a private browser window
  2. Click Sign in with Authentik — you are redirected to sso.yourdomain.com
  3. Log in with your Authentik credentials
  4. You are redirected back to Outline and logged in automatically
  5. 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.

ServiceIdle RAMPeak RAMManaged by
authentik-server350 MB600 MBour Compose
authentik-worker250 MB400 MBour Compose
outline300 MB550 MBour Compose
db (PostgreSQL 16)256 MB500 MBour Compose
redis (shared)50 MB80 MBour Compose
caddy15 MB25 MBour Compose
Subtotal (our stack)1,221 MB2,155 MB
Coolify stack (server + db + proxy)~770 MB~1,200 MBCoolify 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 ProNotion 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.
Frequently Asked Questions

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.

Photo of Yassine El Haddad

Yassine El Haddad

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.