Skip to content

How to Deploy Outline on a VPS

Updated Sep 2026

verified on Ubuntu 26.04 · Sep 2026
We earn commissions when you shop through the links below. Full disclosure →

Self-host the Outline knowledge base with PostgreSQL and Redis, HTTPS through Caddy, and the part most guides skip — the sign-in provider Outline needs before anyone can log in.

Before you start
  • A VPS with 1 GB RAM or more (upstream's floor is 512 MB, 1 GB+ recommended)
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A dedicated domain or subdomain for Outline
  • A sign-in provider: an OIDC identity provider, Google, Microsoft Entra, Slack, Discord or GitLab — or SMTP for email magic links
  • Docker Engine + Compose installed (see the base guide below)
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What Outline is

Outline is a real-time collaborative team knowledge base with a fast editor and good search — the closest self-hosted feel to Notion. It's written in TypeScript on Node.js and needs PostgreSQL and Redis next to it.

Two facts decide whether Outline is right for you, so they come first:

  • The licence is BSL 1.1, not open source. Upstream's docs are direct about it: selling, reselling or hosting Outline as a service breaches the licence and ends your rights under it. Running it for your own team is what the community edition is for.
  • Outline has no username-and-password login. Upstream says so plainly: sign-in happens through an SSO, OIDC or SAML provider, or an emailed "magic link" once SMTP is set up. Without at least one, Outline starts but nobody can log in. Plan the provider before you install.

If that second point is a blocker, Docmost offers similar real-time editing with ordinary accounts — Docmost vs Outline compares them — and BookStack vs Outline covers the simpler option.

Server sizing

Upstream's requirements: at least 512 MB of memory, with 1 GB or more recommended depending on the number of users; PostgreSQL 14+ and Redis 4+. The catalog uses the 1 GB figure as its floor. We don't have a measured idle figure for Outline in the catalog yet, so size from upstream's numbers:

  • 1 GB RAM / 1 vCPU — a small team.
  • 2 GB RAM / 2 vCPU — room to grow. On a single server like this one, Outline runs one process: its startup log says it restricts the process count to 1 unless a separate REDIS_COLLABORATION_URL is configured.

Attachments are stored on local disk in this setup; start with 20 GB.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed, along with a non-root user and a ufw firewall. If not, work through Docker & Compose on Ubuntu first.

sudo ufw status verbose

Only SSH, 80 and 443 should be allowed. Upstream also lists a dedicated domain or subdomain as a requirement — Outline is served from the root of its own hostname.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install Outline (Docker Compose)

Upstream's Docker instructions keep configuration in a docker.env file based on the repository's .env.sample, with URL, DATABASE_URL, REDIS_URL and SECRET_KEY as the minimum. Create the directory:

mkdir -p ~/outline && cd ~/outline

Write docker.env once, generating the secrets upstream asks for with openssl rand -hex 32. The guard keeps a re-run from replacing SECRET_KEY — upstream warns that losing it makes all encrypted data in the database unreadable:

if [ ! -f docker.env ]; then
  DB_PASS=$(openssl rand -hex 16)
  cat > docker.env <<EOF
NODE_ENV=production
URL=https://docs.example.com
PORT=3000
SECRET_KEY=$(openssl rand -hex 32)
UTILS_SECRET=$(openssl rand -hex 32)
DATABASE_URL=postgres://outline:$DB_PASS@postgres:5432/outline
PGSSLMODE=disable
REDIS_URL=redis://redis:6379
FILE_STORAGE=local
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
FORCE_HTTPS=false
POSTGRES_USER=outline
POSTGRES_PASSWORD=$DB_PASS
POSTGRES_DB=outline
EOF
  chmod 600 docker.env
fi

A few of those lines deserve a word:

  • URL must be the public HTTPS address. Replace docs.example.com with your hostname before going further.
  • PGSSLMODE=disable is upstream's own advice when the database runs on the same machine as the app.
  • FORCE_HTTPS=false: upstream allows it when TLS is terminated in front of Outline, which is exactly what Caddy does here.
  • The POSTGRES_* lines are read by the database container, which shares the same env file.

Now the compose file. It follows upstream's example — pinned image, as upstream recommends, and postgres:18 — minus the bundled https-portal container (Caddy replaces it) and with Outline published on loopback only:

cat > docker-compose.yml <<'YAML'
services:
  outline:
    image: docker.getoutline.com/outlinewiki/outline:1.10.1
    env_file: ./docker.env
    restart: unless-stopped
    ports:
      # Loopback only: Caddy is the only way in from outside.
      - "127.0.0.1:3000:3000"
    volumes:
      - storage-data:/var/lib/outline/data
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  redis:
    image: redis:7
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 30s
      retries: 3

  postgres:
    image: postgres:18
    env_file: ./docker.env
    restart: unless-stopped
    volumes:
      - database-data:/var/lib/postgresql
    healthcheck:
      test: ["CMD", "pg_isready", "-d", "outline", "-U", "outline"]
      interval: 10s
      timeout: 20s
      retries: 5

volumes:
  storage-data:
  database-data:
YAML

Start it. Outline runs its database migrations automatically on start, then answers on its health endpoint, /_health:

docker compose up -d
for i in $(seq 1 60); do
  code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/_health)
  [ "$code" = "200" ] && break
  sleep 5
done
docker compose ps
echo "Outline health: HTTP $code"
[ "$code" = "200" ]

A healthy server is only half the job: until you add a sign-in provider, the login page has no way in.

Add a sign-in provider

Pick at least one. The environment variables all go into docker.env, then docker compose up -d recreates the container with them.

OIDC is the most flexible — it works with a self-hosted identity provider such as Keycloak, Authelia, Pocket ID or Zitadel. Create a client in your provider with the redirect URI https://docs.example.com/auth/oidc.callback, then add:

OIDC_ISSUER_URL=https://id.example.com/...
OIDC_CLIENT_ID=outline
OIDC_CLIENT_SECRET=...
OIDC_DISPLAY_NAME=Company SSO

With OIDC_ISSUER_URL, Outline reads the provider's /.well-known/openid-configuration and fills in the rest. Providers without discovery need OIDC_AUTH_URI, OIDC_TOKEN_URI and OIDC_USERINFO_URI instead. Upstream also recommends setting either OIDC_LOGOUT_URI or OIDC_DISABLE_REDIRECT, or users will struggle to log out.

Google, Microsoft Entra, Slack, Discord and GitLab each have their own variables in .env.sample and a page in the hosting docs.

Email magic links need an SMTP provider (SMTP_SERVICE, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM_EMAIL, or the host/port variables for other servers). SMTP is worth configuring anyway: invitations and notifications use it too.

Apply the change:

cd ~/outline && docker compose up -d

The first person to sign in creates the workspace and becomes its admin. From Settings → Authentication you can then restrict sign-in to your email domain and choose which methods are allowed. Passkeys are supported from v1.2.0 as an additional method.

HTTPS + domain

Point an A record for docs.example.com at the server, wait for it to resolve, then terminate TLS with Automatic HTTPS with Caddy:

docs.example.com {
    reverse_proxy 127.0.0.1:3000
}

Upstream's SSL notes say a reverse proxy must forward WebSockets for Outline to work — collaborative editing depends on them. Caddy forwards them without extra configuration.

Backups

Upstream recommends daily database backups kept for a month, and a backup before every upgrade because releases run migrations that are not always backwards compatible. Dump PostgreSQL from its container:

cd ~/outline
docker compose exec -T postgres pg_dump -U outline outline > outline-db-$(date +%F).sql
test -s outline-db-$(date +%F).sql && ls -lh outline-db-*.sql

Attachments live in the storage-data volume (named outline_storage-data here — check with docker volume ls):

cd ~/outline
docker run --rm -v outline_storage-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/outline-files-$(date +%F).tar.gz -C /data .
ls -lh outline-files-*.tar.gz

And keep docker.env somewhere safe and encrypted, such as a password manager — without SECRET_KEY, the database backup is only partly usable. Copy all of it off the server. For a human-readable second copy, Settings → Export produces Markdown, HTML or JSON.

Upgrades

Take a database backup, change the image tag to the new release, then:

cd ~/outline
docker compose pull
docker compose up -d

Migrations run automatically when the container starts. Pinning the tag, as upstream recommends, means you choose when this happens.

Troubleshooting

The login page has no sign-in buttons. No provider is configured, or its variables are misspelled. Check docker compose logs outline — upstream notes that missing variables are reported at startup.

OIDC sign-in fails with a redirect error. The redirect URI registered with the provider must be exactly https://<your URL>/auth/oidc.callback, and URL in docker.env must match the address in the browser.

Documents open but nobody sees each other's edits. WebSockets aren't reaching Outline — look for a CDN or second proxy in front of Caddy.

Database connection errors on first start. PostgreSQL only applies POSTGRES_PASSWORD when its volume is first created. If you edited the password afterwards, DATABASE_URL no longer matches the stored one.

Verification + next steps

You're done when https://docs.example.com loads with a valid certificate, you can sign in through your provider and a colleague can too, sign-in is limited to your domain, and a database dump, a files archive and your docker.env exist off the server.

Next: set up SMTP for invitations, schedule the backups with cron, and connect the integrations your team uses. For host picks, see Best VPS for Self-Hosting.

Next steps

How to self-host Outline →More self-hosted wiki & docs tools →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.