How to Deploy Outline on a VPS
Updated Sep 2026
verified on Ubuntu 26.04 · Sep 2026Self-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.
- 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)
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_URLis 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.
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:
URLmust be the public HTTPS address. Replacedocs.example.comwith your hostname before going further.PGSSLMODE=disableis 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.