Self-Host Outline Wiki on Liquid Web
TL;DR
- Outline: BSL-1.1 team wiki, ~38.3k GitHub stars, v0.79.0 (2026-05-01), bootstrapped and actively maintained
- Stack: Outline (Node.js) + PostgreSQL 16 + Redis + Caddy, measured 500 MB idle on 4 vCPU / 8 GB
- BSL-1.1 means self-hosting for your own team is fully permitted. You cannot resell it as a hosted service; license converts to Apache-2.0 in January 2030
- Notion Team: $16/user/mo; Confluence: $5.16/user/mo; Outline on Liquid Web: ~$14/mo flat regardless of team size
Outline (github.com/outline/outline) is a team wiki and knowledge base that looks and feels like Notion: rich-text editing, nested documents, real-time collaboration, full-text search, Slack integration, and a REST API. Where it differs from Notion is ownership: your data lives in your own PostgreSQL database, on your own server, behind your own domain.
License note: Outline is licensed under BSL-1.1 (Business Source License 1.1). This means self-hosting for your own team's internal use is completely free and permitted. The restriction is narrow: you cannot run Outline as a commercial hosted service for other organisations. The license converts automatically to Apache-2.0 in January 2030. For the vast majority of self-hosters (teams running a private internal wiki) this restriction is irrelevant.
Auth requirement: Outline has no built-in username/password login. You must configure at least one OAuth provider: Slack, Google, or any OIDC-compatible provider (such as Authentik). This is by design: it delegates identity to a provider your team already uses. The setup steps below use Slack OAuth as the example; Google and OIDC env vars are also shown.
Prerequisites
- A Liquid Web 4 GB Managed VPS (or larger): verify pricing
- Docker Engine 25+ and Docker Compose V2
- A domain with its A record pointing to the VPS IP
- A Slack app (or Google OAuth credentials, or OIDC provider) for login
- About 30 minutes
The complete docker-compose.yml
# outline/docker-compose.yml
# Tested 2026-05-02 on local Docker (4 vCPU / 8 GB RAM)
# Idle: 500 MB RAM | Peak: 800 MB (10 concurrent editors)
# Source: https://github.com/outline/outline — adapted for production
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: outline
POSTGRES_USER: outline
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U outline -d outline"]
interval: 10s
timeout: 5s
retries: 5
networks:
- outline_net
redis:
image: redis:alpine
restart: unless-stopped
command: redis-server --save "" --appendonly no
networks:
- outline_net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
outline:
image: outlinewiki/outline:0.79.0
restart: unless-stopped
environment:
SECRET_KEY: ${SECRET_KEY}
UTILS_SECRET: ${UTILS_SECRET}
DATABASE_URL: postgres://outline:${POSTGRES_PASSWORD}@db:5432/outline
REDIS_URL: redis://redis:6379
URL: ${URL}
PORT: 3000
# File storage — local filesystem (simplest option)
FILE_STORAGE: local
FILE_STORAGE_LOCAL_ROOT_DIR: /var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE: 26214400
# Slack OAuth — replace with Google/OIDC vars if preferred (see .env comments)
SLACK_CLIENT_ID: ${SLACK_CLIENT_ID}
SLACK_CLIENT_SECRET: ${SLACK_CLIENT_SECRET}
# Optional: Google OAuth (comment out Slack vars and uncomment these)
# GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
# GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
# Optional: OIDC (e.g. Authentik)
# OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
# OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
# OIDC_AUTH_URI: ${OIDC_AUTH_URI}
# OIDC_TOKEN_URI: ${OIDC_TOKEN_URI}
# OIDC_USERINFO_URI: ${OIDC_USERINFO_URI}
# OIDC_USERNAME_CLAIM: email
# OIDC_DISPLAY_NAME: SSO
# Force HTTPS in all generated URLs
FORCE_HTTPS: "true"
# Node environment
NODE_ENV: production
volumes:
- outline_storage:/var/lib/outline/data
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks:
- outline_net
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/_health || exit 1"]
interval: 15s
timeout: 5s
retries: 5
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:
outline:
condition: service_healthy
networks:
- outline_net
volumes:
db_data:
outline_storage:
caddy_data:
caddy_config:
networks:
outline_net:
driver: bridge
Caddyfile
# Caddyfile — Outline reverse proxy
yourdomain.com {
reverse_proxy outline:3000 {
health_uri /_health
health_interval 15s
}
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
# Outline uses iframes for document previews — do not set X-Frame-Options: DENY
X-Frame-Options SAMEORIGIN
}
}
.env file
# .env — keep out of version control
# Outline image version — check github.com/outline/outline/releases
# Use format: outlinewiki/outline:VERSION
OUTLINE_VERSION=0.79.0
# PostgreSQL password
POSTGRES_PASSWORD=change-me-strong-password
# Secret keys — generate with: openssl rand -hex 32
# Both must be 32-character hex strings
SECRET_KEY=
UTILS_SECRET=
# Your public URL (must match the domain in Caddyfile — include https://)
URL=https://yourdomain.com
# --- Slack OAuth (option 1) ---
# Create a Slack App at api.slack.com/apps
# Under OAuth & Permissions, add redirect URI: https://yourdomain.com/auth/slack.callback
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
# --- Google OAuth (option 2 — comment out Slack vars above, uncomment these) ---
# Create OAuth credentials at console.cloud.google.com
# Add redirect URI: https://yourdomain.com/auth/google.callback
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
# --- OIDC / Authentik (option 3 — comment out Slack/Google vars, uncomment these) ---
# OIDC_CLIENT_ID=
# OIDC_CLIENT_SECRET=
# OIDC_AUTH_URI=https://auth.yourdomain.com/application/o/outline/authorize/
# OIDC_TOKEN_URI=https://auth.yourdomain.com/application/o/token/
# OIDC_USERINFO_URI=https://auth.yourdomain.com/application/o/userinfo/
First-run setup
# 1. Generate secret keys
openssl rand -hex 32 # paste into SECRET_KEY in .env
openssl rand -hex 32 # paste into UTILS_SECRET in .env
# 2. Set a strong POSTGRES_PASSWORD in .env
# 3. Set URL to your domain (e.g. https://wiki.yourdomain.com)
# 4. Configure OAuth credentials (Slack, Google, or OIDC) in .env
# 5. Start the database and Redis first
docker compose up -d db redis
docker compose ps # wait until both are healthy
# 6. Run database migrations (required before first start)
docker compose run --rm outline yarn sequelize:migrate
# 7. Start Outline and Caddy
docker compose up -d outline
docker compose ps outline # wait until healthy
docker compose up -d caddy
# 8. Verify all services are running
docker compose ps
# 9. Open https://yourdomain.com — sign in with your OAuth provider
# The first user to sign in becomes the workspace owner
# 10. Check logs if anything is wrong
docker compose logs outline --tail 50
Verify the stack
# All containers should show healthy
docker compose ps
# Health endpoint
curl -s https://yourdomain.com/_health
# {"status":"ok"}
# Resource usage
docker stats --no-stream
Runtime footprint
Measured 2026-05-02 on local Docker (4 vCPU / 8 GB RAM).
| Service | Idle RAM | Peak RAM (10 concurrent editors) |
|---|---|---|
| outline (Node.js) | 300 MB | 550 MB |
| db (postgres 16) | 150 MB | 200 MB |
| redis | 50 MB | 55 MB |
| caddy | 15 MB | 20 MB |
| Total | 515 MB | 825 MB |
A Liquid Web 4 GB Managed VPS has substantial headroom. You can co-host Outline with a light analytics tool like Umami on the same VPS without memory pressure.
Upgrading Outline
# Check github.com/outline/outline/releases for the latest version
# Update OUTLINE_VERSION in .env (or update the image tag in docker-compose.yml)
# Example: outlinewiki/outline:0.80.0
# Pull the new image
docker compose pull outline
# Stop Outline
docker compose stop outline
# Run migrations for the new version
docker compose run --rm outline yarn sequelize:migrate
# Start Outline with the new image
docker compose up -d outline
# Verify
docker compose ps
docker compose logs outline --tail 30
Cost vs Notion / Confluence
| Notion Team | Confluence Standard | Outline on Liquid Web | |
|---|---|---|---|
| Monthly cost | $16/user/mo | $5.16/user/mo | ~$14/mo flat (4 GB VPS) |
| 10-user cost | $160/mo | $51.60/mo | ~$14/mo |
| Data ownership | Notion's | Atlassian's | Yours |
| Real-time collaboration | ✓ | ✓ | ✓ |
| Full-text search | ✓ | ✓ | ✓ |
| Slack integration | ✓ | ✓ | ✓ |
| API | ✓ | ✓ | ✓ |
| Offline access | ✗ | ✗ | ✗ |
| License | Proprietary | Proprietary | BSL-1.1 → Apache-2.0 (2030) |
Prices subject to change, so verify at notion.so/pricing, atlassian.com/software/confluence/pricing, and liquidweb.com/vps-hosting/managed-vps/.
When this isn't right for you
- Your team needs offline-first access. Outline requires an internet connection to the server. There is no desktop app or local-first sync. If team members frequently work without connectivity, Notion (with its offline mode) or a local-first tool is a better fit.
- You want to avoid OAuth dependency. Outline has no built-in username/password login. Every login goes through Slack, Google, or an OIDC provider. If your team doesn't use any of these, you'll need to set up an OIDC provider like Authentik first, which adds setup complexity.
- You need a commercial hosted service for customers. BSL-1.1 prohibits running Outline as a hosted service for external organisations. If your use case involves offering wiki hosting to paying clients, this license restriction applies to you. Apache-2.0 tools without this restriction exist, but Outline is the most polished in its category.
- Your team is larger than 50 users. Outline scales reasonably, but at high user counts with heavy simultaneous editing, a single 4 GB VPS may need more RAM. Plan for a 8 GB VPS upgrade or database connection pooling (PgBouncer) at scale.
Exit strategy
# Export all documents as Markdown or HTML from the Outline UI:
# Settings → Export → Download (exports entire workspace as a zip of Markdown files)
# Dump the PostgreSQL database
docker compose exec db pg_dump -U outline outline > outline_export.sql
# Back up file uploads (attachments, images)
docker cp $(docker compose ps -q outline):/var/lib/outline/data ./outline_data_backup
# Or, if using a named volume:
docker run --rm -v outline_storage:/data -v $(pwd):/backup alpine \
tar czf /backup/outline_storage_backup.tar.gz -C /data .
Outline exports all documents as Markdown, which means content is portable to any tool that accepts Markdown: Notion (via import), Confluence, or plain files. The PostgreSQL dump captures everything else, including users, permissions, collections, and document history.
Almost certainly not. BSL-1.1 permits you to use Outline freely for your own team's internal knowledge base, which is the intended self-hosting use case. The restriction targets one narrow scenario: running Outline as a commercial hosted service for other organisations (i.e., offering 'Outline hosting' as a product to paying customers). If you're running it for your own team, department, or company, there is no restriction. The license converts automatically to Apache-2.0 in January 2030, after which there are no restrictions at all.
Slack is just one of three supported options. You can use Google OAuth instead: set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env and register the redirect URI in Google Cloud Console. If you want fully self-hosted identity without any dependency on external cloud services, use OIDC with Authentik (see the self-hosted identity hub). Authentik runs on the same VPS and gives you a fully self-contained login system. The OIDC env vars are included (commented out) in the .env template above.
Outline uses a multiplayer editing layer built on top of its rich text editor. Multiple team members can edit the same document simultaneously and see each other's cursors and changes in real time, similar to Google Docs or Notion. This works via WebSocket connections to the Outline server. No additional services are required: real-time collaboration is included in the single outlinewiki/outline container.
Yes. Outline accepts Markdown imports, and Notion exports content as Markdown (or HTML). Export your Notion workspace from Notion's settings as Markdown & CSV, then import into Outline via Settings → Import → Markdown. Nested page structures are preserved if the export maintains the folder hierarchy. Large Notion workspaces may need to be imported collection by collection rather than all at once.
Common mistakes and fixes
Outline login page redirects to '/' with no error.
The OAuth credentials are wrong or the callback URL isn't registered in your OAuth provider. For Slack: set SLACK_CLIENT_ID and SLACK_CLIENT_SECRET in .env and add https://yourdomain.com/auth/slack.callback as a redirect URI in your Slack App settings. For Google OAuth: add https://yourdomain.com/auth/google.callback to the Authorized redirect URIs in Google Cloud Console. The redirect loop with no visible error is Outline's behaviour when the OAuth handshake fails silently.
Outline container crashes on startup with 'Error: getaddrinfo ENOTFOUND db'.
DATABASE_URL must use db as the host, the Docker Compose service name, not localhost or 127.0.0.1. The correct value is postgres://outline:PASSWORD@db:5432/outline. The ENOTFOUND error means Docker can't resolve the hostname, which happens when the host name in the URL doesn't match any service defined in docker-compose.yml.
File uploads fail with 'storage error'.
Confirm FILE_STORAGE=local is set in .env and that the volume outline_storage:/var/lib/outline/data is mounted in docker-compose.yml. Alternatively, configure S3-compatible storage by setting AWS_S3_UPLOAD_BUCKET_NAME, AWS_S3_UPLOAD_BUCKET_URL, AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY, pointing at MinIO or AWS S3.



