How to Self-Host Outline Wiki on a VPS: Team Knowledge Base with Notion-Like Editing

How to Self-Host Outline Wiki on a VPS: Team Knowledge Base with Notion-Like Editing

Outline is an open-source team wiki and knowledge base with a Notion-like block editor, real-time collaborative editing, Slack and Google SSO, full-text search, nested document hierarchies, and a clean reading experience. Outline Cloud costs $10/user/month. Self-hosting on a VPS eliminates per-seat fees entirely — making it cost-effective for teams of any size.

What Outline Provides

  • Block editor: Rich text, code blocks, embeds, tables, callouts — Notion-like experience
  • Collections: Organize documents into nested hierarchies with team permissions
  • Real-time collaboration: Multiple people edit simultaneously, see each other’s cursors
  • Full-text search: Instant search across all documents and their content
  • SSO integration: Slack, Google, GitHub, OIDC, SAML — employees log in with existing accounts
  • Markdown import/export: Migrate from or to any other documentation tool
  • API: REST API for integrating with other tools and automations

Architecture

Outline (Node.js) → PostgreSQL (document storage)
                  → Redis (real-time collaboration, sessions)
                  → S3 / local storage (file attachments, images)

Step 1: Docker Compose Setup

mkdir -p /opt/outline && cd /opt/outline
nano docker-compose.yml
version: '3.8'

services:
  outline:
    image: outlinewiki/outline:latest
    container_name: outline
    restart: always
    ports:
      - "127.0.0.1:3000:3000"
    env_file: .env
    depends_on:
      outline-postgres:
        condition: service_healthy
      outline-redis:
        condition: service_healthy
    volumes:
      - outline_data:/var/lib/outline/data

  outline-postgres:
    image: postgres:16-alpine
    container_name: outline-postgres
    restart: always
    environment:
      POSTGRES_DB: outline
      POSTGRES_USER: outline
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - outline_db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U outline"]
      interval: 10s
      timeout: 5s
      retries: 5

  outline-redis:
    image: redis:7-alpine
    container_name: outline-redis
    restart: always
    command: redis-server --requirepass ${REDIS_PASSWORD}
    healthcheck:
      test: ["CMD", "redis-cli", "--pass", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  outline_data:
  outline_db:
nano .env
# Security — generate with: openssl rand -hex 32
SECRET_KEY=your_64_char_hex_secret_key_here
UTILS_SECRET=your_second_64_char_hex_key_here

# Database
DATABASE_URL=postgres://outline:${POSTGRES_PASSWORD}@outline-postgres:5432/outline
POSTGRES_PASSWORD=StrongOutlineDbPassword!

# Redis
REDIS_URL=redis://:${REDIS_PASSWORD}@outline-redis:6379
REDIS_PASSWORD=StrongRedisPassword!

# Application URL
URL=https://wiki.yourdomain.com
PORT=3000

# File storage — local (or configure S3 below)
FILE_STORAGE=local
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE=26214400

# Authentication — at least one provider required
# Option A: Slack OAuth
SLACK_CLIENT_ID=your_slack_client_id
SLACK_CLIENT_SECRET=your_slack_client_secret

# Option B: Google OAuth (uncomment to use)
# GOOGLE_CLIENT_ID=your_google_client_id
# GOOGLE_CLIENT_SECRET=your_google_client_secret

# Option C: Generic OIDC (Keycloak, etc.)
# OIDC_CLIENT_ID=outline
# OIDC_CLIENT_SECRET=your_secret
# OIDC_AUTH_URI=https://auth.yourdomain.com/realms/myrealm/protocol/openid-connect/auth
# OIDC_TOKEN_URI=https://auth.yourdomain.com/realms/myrealm/protocol/openid-connect/token
# OIDC_USERINFO_URI=https://auth.yourdomain.com/realms/myrealm/protocol/openid-connect/userinfo
# OIDC_USERNAME_CLAIM=email
# OIDC_DISPLAY_NAME=Keycloak
# OIDC_SCOPES=openid profile email

# Email (optional — for invites and notifications)
SMTP_HOST=smtp.mailgun.org
SMTP_PORT=587
SMTP_USERNAME=postmaster@mg.yourdomain.com
SMTP_PASSWORD=your_smtp_password
SMTP_FROM_EMAIL=wiki@yourdomain.com
SMTP_REPLY_EMAIL=noreply@yourdomain.com

# Node environment
NODE_ENV=production
PGSSLMODE=disable
chmod 600 .env
docker compose up -d
docker compose logs -f outline   # Wait for "Listening on port 3000"

Step 2: Nginx Reverse Proxy

sudo nano /etc/nginx/sites-available/outline
server {
    listen 80;
    server_name wiki.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name wiki.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/wiki.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/wiki.yourdomain.com/privkey.pem;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
    }
}
sudo ln -s /etc/nginx/sites-available/outline /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d wiki.yourdomain.com

Step 3: Configure Slack OAuth (Recommended)

  1. Go to api.slack.com/apps → Create New App → From scratch
  2. OAuth & Permissions → Redirect URLs: https://wiki.yourdomain.com/auth/slack.callback
  3. Enable Bot Token Scopes: users:read, users:read.email
  4. Install to Workspace
  5. Copy Client ID and Client Secret to your .env file

Step 4: Configure S3 File Storage (Production)

For production with multiple users uploading files, S3-compatible storage is more reliable than local storage:

# Add to .env (replace local storage config):
FILE_STORAGE=s3
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=us-east-1
AWS_S3_UPLOAD_BUCKET_NAME=outline-files-yourdomain
AWS_S3_UPLOAD_BUCKET_URL=https://s3.amazonaws.com
AWS_S3_UPLOAD_MAX_SIZE=26214400
AWS_S3_FORCE_PATH_STYLE=false

# For Cloudflare R2 (free egress):
AWS_ACCESS_KEY_ID=your_r2_key
AWS_SECRET_ACCESS_KEY=your_r2_secret
AWS_REGION=auto
AWS_S3_UPLOAD_BUCKET_NAME=outline-files
AWS_S3_UPLOAD_BUCKET_URL=https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com
AWS_S3_FORCE_PATH_STYLE=true
<code">docker compose restart outline

Step 5: Database Migrations and Updates

<code"># Run database migrations after first start or after updating
docker exec outline node ./build/server/index.js db:migrate

# Update Outline
cd /opt/outline
docker compose pull
docker compose up -d

Importing Existing Documentation

Outline imports from several formats:

  • Notion: Export from Notion as Markdown & CSV → Import in Outline Settings
  • Confluence: Export as HTML → use the Confluence importer plugin
  • Markdown files: Settings → Import → Upload a .zip of .md files
  • Any tool: Export as Markdown and import

Getting Started

Outline needs 1–2 GB RAM for a small team (under 50 users). Ubuntu VPS plans at VPS.DO with NVMe storage handle Outline’s PostgreSQL full-text search and Redis real-time collaboration efficiently. The 2 vCPU / 2 GB RAM plan comfortably runs Outline for teams of 10–50 people alongside other services.

Conclusion

Self-hosted Outline delivers a Notion-quality team wiki experience — block editor, real-time collaboration, full-text search, and SSO — at VPS infrastructure cost instead of $10/user/month. For a 20-person team, that’s $200/month saved against Outline Cloud. The Docker Compose deployment is straightforward, and Outline’s active development means new features ship regularly without additional licensing cost.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!