What Outline requires
Outline is a Node.js application that keeps documents in PostgreSQL, uses Redis alongside it, and stores uploads on local disk or in an S3 bucket. The project ships an Alpine-based Docker image on a monthly release cadence and calls Docker the recommended, supported install method.
| Item | Documented requirement |
|---|---|
| Operating system | Any Unix that runs the dependencies; Ubuntu and Debian reported in production. Windows Server is not supported |
| Database | PostgreSQL v14 or newer |
| Cache and queue | Redis v4 or newer; one instance is enough for a self-hosted setup |
| Memory | 512 MB minimum; 1 GB or more recommended depending on users |
| CPU | No figure; scales with how many users are online and editing |
| Network | A dedicated domain or subdomain, HTTPS, websockets through any proxy |
| Identity | A compatible authentication provider |
Real-time editing runs as one collaboration process inside the container. The scaling guide calls that sufficient for teams under roughly 1,000 active users, with the exact ceiling depending on the hardware. The docs also warn that the platform is engineered for horizontal scale and expects dev-ops experience to run in production.
Authentication comes before installation
Outline has no password login: people sign in through a provider or an emailed link, and may add a passkey once they are in. Pick the provider before you start: a self-hosted install supports one team, and the docs note that once you have authenticated, no further team can be created.
| Provider | Variables | Condition |
|---|---|---|
| Any OIDC server (Keycloak, authentik, GitLab and others) | OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_ISSUER_URL for auto-discovery | All editions |
| GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Google Workspace accounts only | |
| Microsoft Entra ID | Client id and secret from an app registration | Registered for accounts in your own directory only |
| Slack, Discord | SLACK_* or DISCORD_* client id and secret | Slack requires an HTTPS redirect URL; Discord from v0.78.0 |
| Email magic link | SMTP_* settings | Enabled once SMTP works and the option is on under Settings → Authentication |
| Passkeys | None | Not a first sign-in. v1.2.0 and later; after signing in another way, each user registers a key under Settings → Account → Passkeys; older workspaces switch them on under Settings → Workspace → Authentication |
| SAML (Okta, OneLogin) | SAML_SSO_ENDPOINT, SAML_CERT | Paid Business and Enterprise editions only |
If you already run an identity server, OIDC is the cleanest route: see self-hosted Keycloak or authentik. After the first login, add your domains under Settings → Security so nobody else's accounts can join.
Firewall and secrets
- Install Docker Engine with the Compose plugin from Docker's own package repository.
- Before enabling ufw, check its config file
/etc/default/ufw: withIPV6=yesthere, ufw writes every rule for IPv6 too. Rules:ufw allow 80/tcpfor certificate issuance,ufw allow OpenSSH, and for now 443 only from your own address, e.g.ufw allow from 203.0.113.10 to any port 443 proto tcp. Runufw --force enable(no y/n prompt to swallow a pasted line) and checkufw status verbose. - As root, create the install directory and the
.envfile. Each secret comes fromopenssl randinside a command substitution, so no value is typed, printed or stored in shell history.
# as root (sudo -i)
mkdir -p /opt/outline/data && cd /opt/outline
chown 1001 data # the uid Outline writes uploads as
umask 077
pg=$(openssl rand -hex 24)
{
printf 'OUTLINE_TAG=\n'
printf 'URL=https://wiki.example.com\n'
printf 'PORT=3000\n'
printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 32)"
printf 'UTILS_SECRET=%s\n' "$(openssl rand -hex 32)"
printf 'POSTGRES_PASSWORD=%s\n' "$pg"
printf 'DATABASE_URL=postgres://outline:%s@postgres:5432/outline\n' "$pg"
printf 'PGSSLMODE=disable\n'
printf 'REDIS_URL=redis://redis:6379\n'
printf 'FILE_STORAGE=local\n'
printf 'FORCE_HTTPS=true\n'
} > .env
unset pg
chmod 600 .env
ls -l .env # -rw------- root root
SECRET_KEY must be a hex-encoded 32-byte key, which is what openssl rand -hex 32 produces; a badly formatted key produces the "Invalid key length" error listed on the troubleshooting page. UTILS_SECRET guards the scheduled-job endpoints. PGSSLMODE=disable is acceptable, per the sample config, when the database sits on the same machine. Edit the file with an editor to fill OUTLINE_TAG with the newest version from the releases page and to add provider and SMTP values; never pass them as echo arguments.
The Compose file
# /opt/outline/docker-compose.yml
services:
outline:
image: docker.getoutline.com/outlinewiki/outline:${OUTLINE_TAG:?set OUTLINE_TAG in .env}
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_healthy
restart: unless-stopped
redis:
image: redis:7
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
restart: unless-stopped
postgres:
image: postgres:18
environment:
POSTGRES_USER: outline
POSTGRES_DB: outline
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- database-data:/var/lib/postgresql
healthcheck:
test: ["CMD", "pg_isready", "-U", "outline", "-d", "outline"]
interval: 10s
restart: unless-stopped
volumes:
database-data:
Bring the stack up from /opt/outline (docker compose up -d), then follow docker compose logs -f outline; missing variables are listed at startup. Pinning a version instead of latest is the project's own advice, so upgrades happen when you choose.
Docker writes its own iptables rules for published ports and they are evaluated before ufw's, so a port published as plain 3000:3000 is reachable from outside whatever ufw says. The 127.0.0.1 prefix keeps it on loopback; confirm with ss -tlnp | grep 3000. Redis and PostgreSQL publish nothing at all.
TLS and the websocket proxy
Outline should always be served over HTTPS, and the proxy must forward websockets or documents load but cannot be edited. This nginx site follows the project's example, plus proxy_http_version 1.1, which nginx needs for the upgrade, and a body limit so uploads larger than nginx's 1 MB default get through.
# /etc/nginx/sites-available/outline
server {
listen 80;
listen [::]:80;
server_name wiki.example.com;
client_max_body_size 300m;
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-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
}
Enable the site, install certbot with its nginx plugin, and certbot --nginx -d wiki.example.com adds the 443 listener and certificate. With TLS ending at nginx, keep FORCE_HTTPS=true. Register the provider's callback, sign in yourself first, set the allowed domains, and only then ufw allow 443/tcp for everyone.
Uploads: local disk or S3
- Local (
FILE_STORAGE=local): files land in/var/lib/outline/datainside the container, so they must be mapped to the host or they vanish with it. The bind mount above, owned by uid 1001, is the documented way to do that. SetFILE_STORAGE_UPLOAD_MAX_SIZEin bytes and keep nginx's limit above it. - S3 (
FILE_STORAGE=s3plus theAWS_*variables): needsBlockPublicAclsoff at bucket level, a CORS policy naming your URL, and an IAM policy covering object get, put, delete, the two ACL actions and bucket listing. Community-tested: Amazon S3, MinIO, DigitalOcean; Cloudflare R2 and Backblaze are listed as not working. - A MinIO server works, but put it on its own domain: on Outline's domain the browser forwards Outline's authorization header to it and uploads fail.
Scheduled jobs, backups and upgrades
- Cron. Trash cleanup, reminders and popularity scores depend on
/api/cron.hourlyand/api/cron.dailybeing called with?token=set to UTILS_SECRET. Keep the token off process lists: a root-only script,/opt/outline/cron.sh, that reads just the two values it needs,URL=$(sed -n 's/^URL=//p' /opt/outline/.env)andUTILS_SECRET=$(sed -n 's/^UTILS_SECRET=//p' /opt/outline/.env), and pipesprintf 'url = "%s/api/cron.%s?token=%s"\n' "$URL" "$JOB" "$UTILS_SECRET"intocurl -fsS -K -;/etc/cron.dthen runsJOB=hourly bash /opt/outline/cron.shevery hour and the same withJOB=dailyonce a day. The query string still lands in nginx's access log, so keep that log root-readable. Neversourcethe.env: it is a Docker env file, not shell syntax, and a value such as a display name with spaces breaks bash. - Database. The docs recommend daily backups at minimum, kept for a month. Create the target once with
mkdir -m 700 /var/backups/outline, then from/opt/outlinerundocker compose exec -T postgres pg_dump -U outline -Fc outline > /var/backups/outline/db-$(date +%F).dump;-Tmeans no TTY, so it works from cron. Written directly into a crontab line, each%must be escaped as\%. - Files and secrets. Archive
/opt/outline/dataand.envinto a root-only directory, then copy off the box. Without SECRET_KEY, every encrypted value in the database is unreadable. - Upgrades. Back up, change
OUTLINE_TAG,docker compose pull,docker compose up -d. Migrations run automatically on start (--no-migratedisables that) and are not always backwards compatible, so a rollback means restoring the dump.
Settings → Export also produces HTML, Markdown or JSON copies of the content, a useful second format but not a substitute for the database dump.
Licence terms
The community edition is under the Business Source License 1.1, not an open-source licence. Its Additional Use Grant reads: "You may make use of the Licensed Work, provided that you may not use the Licensed Work for a Document Service." The same file defines a Document Service as a commercial offering through which outside parties, meaning anyone other than your own employees and contractors, get to use Outline by setting up teams and documents they control.
An internal wiki for your company is inside the grant. Selling Outline workspaces to customers is not, and the hosting docs say such use automatically terminates your rights. Each version converts to Apache 2.0 on its Change Date or four years after its first public release, whichever comes first.
Which machine to run it on
The documented floor is small, so pick the box for the disk that holds uploads and dumps, and for recovery. A single-socket server from the dedicated server range has its own out-of-band console (iLO, iDRAC or a Supermicro BMC), which matters the day a ufw rule locks out SSH. Moonshot cartridges, one small server per chassis slot, also fit the documented floor, but apart from the m510 and m710x they offer power control only. Every line gives root, an unmetered port and free Linux images you reinstall from the control panel; a racked machine is handed over in about 30 minutes once payment is approved.
Wrong fit: a team that wants plain email-and-password accounts with no identity provider (BookStack has local logins), or anyone planning to sell Outline to other organisations as a hosted service.
Questions
Can Outline run without SSO or an identity provider?
Not with passwords: Outline has no native email-and-password login. The nearest option is the email magic link, which needs working SMTP and the email method enabled under Settings → Authentication. Users then receive a single-use, time-limited sign-in link.
Which PostgreSQL and Redis versions does it need?
The requirements page sets PostgreSQL v14 and Redis v4 as the floor. The Compose example on the Docker page uses the postgres:18 image. One Redis instance is the recommended setup for self-hosting.
What happens if SECRET_KEY is lost?
Encrypted data in the database becomes unreadable: access tokens, sessions and integrations are destroyed, though unencrypted content can be recovered with a reset script. Back up .env after every change to an encrypted store.
Do I have to use the https-portal container from the docs?
No. The docs offer it for automatic Let's Encrypt certificates, but any reverse proxy works if it forwards websockets. A host nginx in front of 127.0.0.1:3000 keeps the app port off the public interface.
Can the community edition use SAML?
No. SAML, including Okta and OneLogin setups, is listed for the licensed Business and Enterprise editions. On the community edition use OIDC, which most SAML-capable identity servers also speak.