How to Install Outline on Ubuntu 24.04 — Self-Hosted Team Wiki with Docker
A team wiki is one of those tools everyone agrees is valuable in principle and almost no one loves in practice. Confluence feels like wading through concrete, Notion quietly changes its pricing every 18 months, and Google Docs folders decay into an archaeological dig site within a year. Outline is the wiki that finally feels like writing in a modern editor: fast, clean, keyboard-driven, with real-time collaboration, full-text search, hierarchical collections, and a live API. It is also open source, and that means you can run it on your own VPS without paying per seat or wondering where your documents live.
This guide walks through a production-grade install of Outline on Ubuntu 24.04 using Docker Compose, from prepping the host through SSO, S3 file storage, Nginx with TLS, and the first admin login.
Recommended Plan: CloudCore Professional>
Outline plus Postgres plus Redis runs happily on our Professional VPS — 6 vCPU, 12 GB RAM, 100 GB NVMe, unmetered bandwidth at EUR 19.99/month. This gives your team years of growth runway without worrying about the wiki becoming the bottleneck.
Table of Contents
What is Outline?
Outline is an open source knowledge base and team wiki built by the team at getoutline.com. It ships as a single Node.js application backed by Postgres (for documents, users, and metadata), Redis (for websockets, caching, and background jobs), and an S3-compatible bucket (for uploads, avatars, and attachments). The editor is based on ProseMirror, which gives it one of the smoothest writing experiences of any wiki — slash-command blocks, real-time cursors, keyboard shortcuts, embedded iframes, code blocks with syntax highlighting, and a clean Markdown-compatible document model.
Outline is organized around three concepts: workspaces (your company), collections (folders with their own permissions — Engineering, HR, Onboarding), and documents (pages that can nest into an arbitrary tree). On top of that it adds full-text search, comments, document sharing with public links, version history, a publish/draft state, and an import/export flow that understands Markdown, HTML, and Notion ZIP exports.
The project is distributed under the Business Source License (BSL), which for practical purposes means you can self-host it freely for your own team — you just cannot turn it into a competing commercial SaaS. Documentation lives at docs.getoutline.com and the source is on GitHub.
Why Self-Host Your Team Wiki?
The pitch for a hosted wiki is convenience. The pitch for self-hosting is ownership — and once you have been on the receiving end of a vendor price hike, a deprecated feature, or an outage on somebody else's infrastructure, ownership starts to look a lot more attractive.
- Data ownership. Every document, comment, attachment, and audit log lives on infrastructure you control. No third party can read it, mine it, train on it, or lose it. If your industry has data residency rules (GDPR, HIPAA, SOC 2, UK DPA), self-hosting in a known region is the simplest answer.
- Flat pricing, any team size. Notion charges around USD 10 per user per month. Confluence charges around USD 6 per user per month with a minimum. On a 30-person team those numbers are USD 180-300 per month, growing with every hire. A self-hosted Outline on a EUR 19.99 VPS costs the same whether the team is five people or five hundred.
- No vendor lock-in. Outline stores documents as Markdown and exposes every object through its API. Your content is never trapped in a proprietary binary blob.
- No surprise policy changes. The SaaS wiki you picked last year may have changed its free tier, its AI features, its sharing rules, or its data retention policy since. Your self-hosted install changes only when you choose to change it.
- Faster, always. The app runs next to your team, on hardware you chose. No shared multi-tenant contention, no mystery regional routing.
- Integrates cleanly with the rest of your stack. Run it on the same VPS as your monitoring, your CI runner, or your internal tools. Call it from any script via the API. Put it behind your existing identity provider.
Outline vs. Other Self-Hosted Wikis
Outline is not the only game in town. Briefly, where the alternatives fit:
- BookStack — PHP + MySQL, organized as Books/Chapters/Pages. Fantastic if you want a structured manual-style layout and an uncomplicated PHP stack.
- Wiki.js — Node.js + Postgres. Flexible, supports multiple editors (Markdown, WYSIWYG, Asciidoc), strong for technical teams that want raw Markdown storage.
- DokuWiki — file-based, no database. Lightweight, battle-tested, perfect for small teams or SOP libraries where simplicity beats features.
- Trilium — personal-first knowledge tree with a note graph, scripting, and Zettelkasten workflows. Best for individuals or very small teams.
Prerequisites
Before you start, you need:
- An Ubuntu 24.04 LTS VPS with root or sudo access
- A domain name (for this guide:
wiki.example.com) with an A record pointing to the server - At least 4 GB of RAM (12 GB recommended for production)
- At least 20 GB of free disk space
- An S3-compatible object storage bucket (AWS S3, Cloudflare R2, Backblaze B2, or MinIO)
- An identity provider to authenticate users (Slack, Google, Microsoft, or OIDC)
ssh root@your-server-ipStep 1: Prepare the Server
Update the package index and install the essentials you will need throughout this guide:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl ca-certificates gnupg lsb-release ufw opensslOpen the firewall for SSH, HTTP, and HTTPS only. Docker will manage its own internal network:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enableSet a timezone so timestamps in the wiki are consistent:
sudo timedatectl set-timezone Europe/BerlinStep 2: Install Docker Engine and Compose
Add Docker's official repository and install the Engine together with the Compose plugin:
sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpgecho "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release; echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Confirm:
docker --version
docker compose versionExpected output:
Docker version 27.3.1, build ...
Docker Compose version v2.29.7Step 3: Create the Project Layout
Give Outline its own directory under /opt:
sudo mkdir -p /opt/outline/{data,postgres,redis}
cd /opt/outlineThe data directory is a fallback for local file storage. The postgres and redis directories are bind-mounted into the database containers so data survives container rebuilds.
Step 4: Generate Secrets and Write the .env File
Outline requires two 32-byte hex secrets. Generate them:
openssl rand -hex 32
openssl rand -hex 32Each call prints a 64-character hex string — one becomes SECRET_KEY, the other UTILS_SECRET. Generate a strong Postgres password as well:
openssl rand -base64 24Create /opt/outline/.env:
sudo tee /opt/outline/.env > /dev/null <<'EOF'
--- core ---
NODE_ENV=production
SECRET_KEY=REPLACE_WITH_FIRST_OPENSSL_HEX
UTILS_SECRET=REPLACE_WITH_SECOND_OPENSSL_HEXPublic URL — must match the domain Nginx serves
URL=https://wiki.example.com
PORT=3000
FORCE_HTTPS=true--- database ---
POSTGRES_USER=outline
POSTGRES_PASSWORD=REPLACE_WITH_STRONG_PASSWORD
POSTGRES_DB=outline
DATABASE_URL=postgres://outline:REPLACE_WITH_STRONG_PASSWORD@postgres:5432/outline
PGSSLMODE=disable--- cache / pubsub ---
REDIS_URL=redis://redis:6379--- file storage (filled in Step 6) ---
FILE_STORAGE=s3
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
AWS_S3_UPLOAD_BUCKET_NAME=outline-uploads
AWS_S3_UPLOAD_BUCKET_URL=https://outline-uploads.s3.amazonaws.com
AWS_S3_FORCE_PATH_STYLE=false
AWS_S3_ACL=private--- auth (filled in Step 5) ---
Uncomment one block below
--- optional ---
DEFAULT_LANGUAGE=en_US
RATE_LIMITER_ENABLED=true
LOG_LEVEL=info
EOF
sudo chmod 600 /opt/outline/.envOpen the file with sudo nano /opt/outline/.env and paste the hex secrets and Postgres password into the right slots. DATABASE_URL must reference the same password you set in POSTGRES_PASSWORD.
SECRET_KEY encrypts tokens and session cookies. UTILS_SECRET is used for secondary crypto routines like email confirmation signing. Treat both as production secrets — rotate them and all active sessions invalidate.
Step 5: Configure SSO
Outline's self-hosted build requires SSO. Pick the provider that matches how your team already authenticates and uncomment the relevant block in .env.
Option A: Slack
Create a Slack app at . Under "OAuth & Permissions", add the redirect URL https://wiki.example.com/auth/slack.callback. Add the scopes identity.basic, identity.email, identity.team, and identity.avatar. Copy the Client ID and Client Secret.
SLACK_CLIENT_ID=1234567890.1234567890
SLACK_CLIENT_SECRET=abc123...Option B: Google Workspace
Open the Google Cloud Console, create an OAuth 2.0 Client (Web application). Add the authorized redirect URL https://wiki.example.com/auth/google.callback.
GOOGLE_CLIENT_ID=xxxxxxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxOption C: Microsoft Entra ID (Azure AD)
Register an application in Entra ID. Add https://wiki.example.com/auth/azure.callback as a redirect URI. Create a client secret.
AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
AZURE_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AZURE_RESOURCE_APP_ID=https://graph.microsoft.comOption D: Generic OIDC
For Authentik, Keycloak, Zitadel, Okta, or any OpenID Connect provider, register a client with redirect URL https://wiki.example.com/auth/oidc.callback.
OIDC_CLIENT_ID=outline
OIDC_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OIDC_AUTH_URI=https://id.example.com/application/o/authorize/
OIDC_TOKEN_URI=https://id.example.com/application/o/token/
OIDC_USERINFO_URI=https://id.example.com/application/o/userinfo/
OIDC_LOGOUT_URI=https://id.example.com/application/o/end-session/
OIDC_USERNAME_CLAIM=preferred_username
OIDC_DISPLAY_NAME=SSO
OIDC_SCOPES=openid profile emailYou only need one provider active. More than one is supported — users see each option on the sign-in screen.
Step 6: Configure S3 File Storage
Outline can keep uploads on local disk for quick tests (FILE_STORAGE=local), but for production you want them on object storage so they are easy to back up, replicate, and serve at scale.
Create a bucket on your provider of choice and generate an access key pair with read/write permission scoped to that bucket. Fill the AWS_* block in .env:
- AWS S3: set
AWS_REGIONto the bucket's region andAWS_S3_UPLOAD_BUCKET_URLtohttps://<bucket>.s3.<region>.amazonaws.com. - Cloudflare R2: set
AWS_S3_UPLOAD_BUCKET_URL=https://<account-id>.r2.cloudflarestorage.com/<bucket>andAWS_S3_FORCE_PATH_STYLE=true. - Backblaze B2: set the S3-compatible endpoint
https://s3.<region>.backblazeb2.comandAWS_S3_FORCE_PATH_STYLE=true. - MinIO (self-hosted): point
AWS_S3_UPLOAD_BUCKET_URLat your MinIO endpoint and useAWS_S3_FORCE_PATH_STYLE=true.
GET, PUT, and POST from https://wiki.example.com — Outline uploads directly from the browser to the bucket using pre-signed URLs.Example CORS document for AWS S3:
[
{
"AllowedOrigins": ["https://wiki.example.com"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]Step 7: Write docker-compose.yml
Create /opt/outline/docker-compose.yml:
sudo tee /opt/outline/docker-compose.yml > /dev/null <<'EOF' services: outline: image: outlinewiki/outline:latest container_name: outline restart: unless-stopped env_file: .env ports: - "127.0.0.1:3000:3000" volumes: - ./data:/var/lib/outline/data depends_on: postgres: condition: service_healthy redis: condition: service_startedpostgres: image: postgres:16-alpine container_name: outline-postgres restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - ./postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5
redis: image: redis:7-alpine container_name: outline-redis restart: unless-stopped volumes: - ./redis:/data command: ["redis-server", "--appendonly", "yes"] EOF
Key points:
- Outline binds to
127.0.0.1:3000only. External traffic arrives via Nginx with TLS, never directly. - Postgres and Redis stay on the internal Docker network — they are unreachable from the public internet.
depends_onwithcondition: service_healthymeans the Outline container waits for Postgres to accept connections before booting, avoiding first-run race conditions.- Volumes are bind-mounted into
/opt/outline/{postgres,redis,data}so they are easy to back up and survivedocker compose down.
Step 8: Start the Stack
Pull the images and launch:
cd /opt/outline
sudo docker compose pull
sudo docker compose up -dWatch the startup logs:
sudo docker compose logs -f outlineOn first launch Outline runs its schema migrations automatically. You will see lines like [database] 20240...migration completed and finally [server] Listening on http://0.0.0.0:3000. Press Ctrl+C to stop tailing the logs — the containers keep running in the background.
Check health:
curl -I http://127.0.0.1:3000A 302 redirect to /auth means the app is up.
Step 9: Nginx Reverse Proxy with TLS
Install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginxWrite the Outline site config:
sudo tee /etc/nginx/sites-available/outline > /dev/null <<'EOF' upstream outline_upstream { server 127.0.0.1:3000; keepalive 16; }server { listen 80; server_name wiki.example.com; return 301 https://$host$request_uri; }
server { listen 443 ssl http2; server_name wiki.example.com;
# Certbot will fill these in ssl_certificate /etc/letsencrypt/live/wiki.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/wiki.example.com/privkey.pem;
client_max_body_size 50m;
# Security headers add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; add_header Referrer-Policy strict-origin-when-cross-origin always; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
location / { proxy_pass http://outline_upstream; proxy_http_version 1.1; 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;
# Websockets for real-time editing and presence proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
proxy_read_timeout 600s; proxy_send_timeout 600s; } } EOF
sudo ln -s /etc/nginx/sites-available/outline /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx
Obtain a Let's Encrypt certificate:
sudo certbot --nginx -d wiki.example.com --redirect --agree-tos -m [email protected] -nCertbot installs a systemd timer that auto-renews the certificate. Confirm:
sudo systemctl list-timers | grep certbotVisit https://wiki.example.com — you should see the Outline sign-in page.
Step 10: First Admin Login
Click the button for whichever SSO provider you configured. Outline creates a new workspace on the first successful login and promotes that user to workspace admin automatically.
You are now looking at an empty Outline workspace. Before you hand it to the team:
If SSO fails on the callback, the single most common cause is a mismatch between URL in .env and the exact domain you typed into the provider's redirect URL field. They must be byte-for-byte identical including https://.
Building Out Your Wiki: Teams, Collections, Documents
Outline organizes content as a hierarchy:
- Workspace — your company. One per install.
- Groups — named sets of users (e.g. "Engineering", "Ops", "Leadership"). Used for permissioning.
- Collections — top-level folders, each with its own icon, color, and member/group permissions. Typical first set: Company, Engineering, Product, People Ops, Onboarding.
- Documents — pages. Documents nest into sub-documents to any depth.
Onboarding— public to all members, read-only for most. Contains day-one, week-one, month-one playbooks.Company— policies, values, meeting notes, OKRs.Engineering— RFCs, runbooks, post-mortems, service catalogue.Product— specs, roadmap, research.People Ops— restricted to HR group. Contains handbook, compensation bands, performance frameworks.
/ to open the block menu — headings, tables, code blocks, embeds, Mermaid diagrams, and info/warning/success callouts are all one keystroke away. Press Cmd/Ctrl+K from anywhere for the command palette — the fastest way to jump between documents.Collections support three permission levels per member or group: View, Comment, and Edit. Set them at the collection level and they cascade to all documents inside, with per-document overrides when you need them.
Using the Outline API
Outline exposes a REST API at /api/*. Generate a personal API token from Settings → API.
Example: list documents in a collection.
curl -X POST https://wiki.example.com/api/documents.list \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"collectionId": "COLLECTION_UUID", "limit": 25}'Create a document:
curl -X POST https://wiki.example.com/api/documents.create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Incident 2026-04-16 — API 502s",
"collectionId": "COLLECTION_UUID",
"text": "## Summary\n...",
"publish": true
}'Search across the workspace:
curl -X POST https://wiki.example.com/api/documents.search \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "postmortem database"}'The API is a natural way to wire Outline into the rest of your stack: auto-create a post-mortem template from an incident bot, sync deployment notes from CI, or pipe Zendesk macros into a public help collection. Full API reference lives at docs.getoutline.com/s/developers.
FAQ
Why self-host Outline instead of using Notion or Confluence?
Self-hosting Outline gives you full data ownership — every document, comment, and attachment lives on infrastructure you control. You avoid per-seat pricing (which scales linearly with headcount), vendor lock-in (Notion's export is Markdown, Confluence's is HTML, both with quirks), surprise policy and pricing changes, and the compliance complications of handing internal knowledge to a third party. Outline is open source under BSL, costs a flat VPS fee regardless of team size, and keeps your wiki on the same infrastructure as the rest of your internal tools.
Does Outline require SSO, or can I use email and password?
Outline requires an authentication provider. It supports Slack, Google Workspace, Microsoft Entra ID (Azure AD), and any standards-compliant OIDC provider such as Authentik, Keycloak, Zitadel, or Okta. There is no email plus password login in the self-hosted build — this is a deliberate design choice that keeps identity management centralized and avoids the operational weight of password resets, MFA enrollment, and account lockouts inside the wiki itself.
Can I run Outline without S3 object storage?
Yes. Set FILE_STORAGE=local and point FILE_STORAGE_LOCAL_ROOT_DIR at a mounted volume inside the container. For small teams this works fine, but for production you should use an S3-compatible bucket (AWS S3, Backblaze B2, Cloudflare R2, MinIO, or Wasabi) so your uploads survive container rebuilds, are trivial to back up, and can be served via CDN. Switching later is possible but involves a migration script.
How much RAM does Outline actually need?
A small team (under 50 users) runs comfortably on 4 GB of RAM. Outline itself sits around 500 MB, Postgres around 300 MB, and Redis under 100 MB at idle. For production deployments with active search indexing, real-time collaboration across many documents, and a growing document count, 12 GB gives you plenty of headroom for Postgres caches and future growth. Storage needs scale with attachments — budget ~100 MB per active user per year for a text-heavy wiki, more if your team uploads screenshots freely.
Does Outline have an API?
Yes. Outline exposes a full JSON-over-HTTP API at /api/* with an OpenAPI schema. You can create and update documents, move them between collections, manage collections and groups, run full-text searches, manage users, and subscribe to webhooks. Generate a personal API token from Settings → API and call endpoints with Authorization: Bearer <token>. The API is the supported way to integrate Outline with CI pipelines, incident tooling, and internal bots.
How do I back up an Outline install?
Back up three things. First, the Postgres database — run docker exec outline-postgres pg_dump -U outline outline | gzip > outline-$(date +%F).sql.gz from a nightly cron job. Second, the S3 bucket — enable versioning plus lifecycle rules, or run a nightly rclone sync to a second region. Third, your .env file — store an encrypted copy somewhere safe because without SECRET_KEY and UTILS_SECRET a restored database cannot decrypt its own session state. Outline also ships a built-in export under Settings → Export that produces a ZIP of Markdown files per workspace, useful as a belt-and-braces fallback.
Can I migrate from Notion or Confluence to Outline?
Yes. Outline imports Markdown and HTML from most wikis. For Notion, use its "Export as Markdown and CSV" option, ZIP the result, and drop it into Outline's Settings → Import flow — Outline understands Notion's folder structure and recreates the collection/document hierarchy. For Confluence, export each space to HTML and use the same importer; complex macros (Jira tickets, custom HTML) will need manual cleanup, but page hierarchy, text, images, and tables carry over cleanly. Expect to spend an afternoon tidying up after a mid-sized import.
Next Steps
Your Outline install is running, encrypted, backed by object storage, and fronted by Nginx with auto-renewing TLS. The remaining work is cultural more than technical — making the wiki the default place the team writes things down. A few concrete follow-ups:
- Set up nightly Postgres backups with a simple cron job dumping to an off-site bucket. A wiki without backups is a wiki waiting for an outage.
- Wire up Slack or Discord webhooks from Settings → Integrations so new document events land in the right channel. This single change is what turns a dormant wiki into one people read.
- Pre-seed the first week of content — onboarding checklist, team directory, one runbook, one post-mortem. An empty wiki is intimidating; a half-full one invites contributions.
- Compare with alternatives if you're still evaluating — the guides for BookStack, Wiki.js, DokuWiki, and Trilium cover the same ground with different trade-offs.
- Read the official docs at docs.getoutline.com for advanced topics like webhook integrations, custom emojis, workspace SAML, and the full API reference.
Spin Up a Wiki-Ready VPS in Minutes>
Our CloudCore Professional plan is sized exactly for self-hosted Outline: 6 vCPU, 12 GB RAM, 100 GB NVMe, unmetered bandwidth, EUR 19.99/month. Deploy Ubuntu 24.04 and you can follow this guide end to end in under 40 minutes.>
Order your Professional VPS and get your team wiki online tonight.