What you are putting together
Outline is a knowledge base where several people can edit the same document at the same moment. Self-hosting it means running four parts that talk to each other:
- The Outline app, shipped by the project as a Docker image, released monthly. Docker is the install route they recommend and support.
- PostgreSQL, version 14 or later, which holds every document, user and setting.
- Redis, version 4 or later. A single instance is all a self-hosted wiki needs.
- A front door: a domain or subdomain of its own, an HTTPS certificate and a reverse proxy (a small web server that passes visitors through to the app).
It runs on Linux and other Unix systems, not on Windows Server. The project is frank that it is built like a cloud platform and that running it in production takes some dev-ops experience.
Will a modest server cope?
Yes. The requirements page asks for 512 MB of memory at the very least and suggests 1 GB or more as your user count grows. It gives no processor figure, only that load rises with how many people are online, typing and reading. Live co-editing runs as a single process, which the docs describe as fine for teams below about 1,000 active users, depending on the hardware.
So the things that really decide your server are the disk, because every image and attachment your team uploads lives there unless you use S3, and how quickly you can get back in when something breaks.
Choose how your team will log in
This is the step that surprises people: Outline has no built-in email-and-password accounts. People sign in through a service you connect, an emailed link or a passkey. Your choices on the free community edition:
- OpenID Connect (OIDC), which works with any compatible provider, including GitLab, Keycloak and authentik. If you would rather own that too, see our guide to running authentik yourself.
- Google, but only for Google Workspace organisations.
- Microsoft Entra ID, with an app registered for your own directory.
- Slack (its redirect address must be HTTPS) or Discord (version 0.78.0 onwards).
- An email magic link: people type their address and get a one-time, short-lived link. It switches on once you have set up outgoing mail (SMTP) and allowed email under Settings → Authentication.
- Passkeys, from version 1.2.0, as an extra way back in rather than a first login: once signed in by one of the methods above, each person adds their own under Settings → Account → Passkeys. Older workspaces switch them on first under Settings → Workspace → Authentication.
SAML logins, including Okta and OneLogin, are reserved for the paid Business and Enterprise editions. Also worth knowing: a self-hosted install supports a single team, and once you have authenticated no second team can be created, so make sure the first login is yours.
Up and running in an evening
- Install a current Ubuntu LTS, then Docker Engine and its Compose plugin from Docker's official repository.
- Become root with
sudo -iand run the block below. It first makes sure ufw covers IPv6 as well as IPv4, then turns the firewall on with HTTPS open only to your own IP address for now, and writes your secrets straight into a file only root can read. Each secret is produced byopenssl randas the file is written (the database password through a shell variable that is unset afterwards), so none of them appears on screen or in your shell history. - Open
/srv/outline/.envin an editor and fill inOUTLINE_TAGwith the latest version number from Outline's releases page, plus the client ID and secret for your sign-in provider. - Save the Compose file below as
/srv/outline/docker-compose.yml, then rundocker compose up -dfrom that folder and watchdocker compose logs -f outlineuntil it settles. Outline lists any setting it is missing when it starts.
# run as root
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw # rules cover IPv6 too
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow from 198.51.100.7 to any port 443 proto tcp # your IP, for now
ufw --force enable # no y/n prompt
mkdir -p /srv/outline/uploads && cd /srv/outline
chown 1001 uploads # Outline's container user
umask 077
dbpass=$(openssl rand -hex 24)
cat > .env <<EOF
OUTLINE_TAG=
URL=https://docs.example.com
PORT=3000
SECRET_KEY=$(openssl rand -hex 32)
UTILS_SECRET=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$dbpass
DATABASE_URL=postgres://outline:$dbpass@db:5432/outline
PGSSLMODE=disable
REDIS_URL=redis://redis:6379
FILE_STORAGE=local
FORCE_HTTPS=true
EOF
unset dbpass
chmod 600 .env
services:
db:
image: postgres:18
environment:
POSTGRES_DB: outline
POSTGRES_USER: outline
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql
healthcheck:
test: ["CMD", "pg_isready", "-d", "outline", "-U", "outline"]
interval: 10s
restart: unless-stopped
redis:
image: redis:7
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
restart: unless-stopped
outline:
image: docker.getoutline.com/outlinewiki/outline:${OUTLINE_TAG:?fill in OUTLINE_TAG}
env_file: .env
ports:
- "127.0.0.1:3000:3000" # loopback only; nginx faces the internet
volumes:
- ./uploads:/var/lib/outline/data
depends_on:
db: { condition: service_healthy }
redis: { condition: service_healthy }
restart: unless-stopped
volumes:
pgdata:
Why 127.0.0.1 matters: when Docker publishes a port, it adds firewall rules of its own that are checked ahead of ufw's. Publish port 3000 the usual way and the app answers the whole internet even though ufw looks closed. Tied to 127.0.0.1, only programs on the server itself can reach it. Check with ss -tlnp.
Put HTTPS in front
Outline's docs say it should always sit behind HTTPS, on the open internet and on internal networks alike. The proxy also has to pass websockets through; when it does not, the wiki opens but nobody can edit a page. Here is an nginx site based on the project's example, with two lines the docs leave out: proxy_http_version 1.1, because nginx will not upgrade a connection to a websocket without it, and an upload ceiling well above the 1 MB nginx allows by default.
server {
server_name docs.example.com;
listen 80;
listen [::]:80;
client_max_body_size 300m; # keep above FILE_STORAGE_UPLOAD_MAX_SIZE
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_redirect off;
}
}
- Save it under
/etc/nginx/sites-available/, link it intosites-enabled, and reload nginx. - Install certbot and its nginx plugin, then run
certbot --nginx -d docs.example.comto fetch a certificate and add the HTTPS listener. - In your sign-in provider, register the callback address its Outline guide gives, then log in yourself so the team is created under your account.
- Under Settings → Security, add your company's email domains, so only your people can join.
- Now open HTTPS to everyone with
ufw allow 443/tcp.
Where your uploads live
With FILE_STORAGE=local, Outline saves files inside its container at /var/lib/outline/data. Unless that folder is mapped to the server, as the uploads folder above is, the files disappear when the container is replaced. If you see permission errors, the folder must belong to user ID 1001.
Prefer object storage? Set FILE_STORAGE=s3 and the AWS_ settings. Community members have tested Amazon S3, DigitalOcean and MinIO successfully, while Cloudflare R2 and Backblaze are marked as not working. Your own MinIO server works well, as long as it has a domain of its own rather than sharing Outline's.
Looking after it
- Scheduled tasks. Outline relies on two addresses being visited, hourly and daily:
/api/cron.hourlyand/api/cron.daily, each with?token=and your UTILS_SECRET. A small script that pulls the two values it needs out of.envwithgrep '^URL=' /srv/outline/.env | cut -d= -f2-andgrep '^UTILS_SECRET=' /srv/outline/.env | cut -d= -f2-, then hands curl its address through standard input (curl -fsS -K -) means the token never shows up inpsoutput or in the crontab itself. Your nginx access log will still record it, so keep that log private. Do notsourcethe.envin a script: Docker reads it as plain KEY=value lines rather than shell code, and values with spaces in them break bash. - Database copies. Outline's advice is daily backups, kept for a month. Make a private folder once (
mkdir -m 700 /srv/backups), then a cron-friendly dump, run in/srv/outline:docker compose exec -T db pg_dump --format=custom --username=outline outline > /srv/backups/outline-$(date +%F).dump. Leave out-it, because cron has no terminal, and if you put the line straight into a crontab, write each%as\%. - The other two things to save. The
uploadsfolder and the.envfile. Without SECRET_KEY, whatever Outline encrypted inside its database can no longer be read, so keep a copy in a password manager or other encrypted store, and refresh it whenever you change the file. - Updating. Take a backup first, since every release brings automatic database changes that are not always reversible. Then change
OUTLINE_TAG, rundocker compose pullanddocker compose up -d. Migrations run on their own when the container starts.
For a readable copy on top, Settings → Export gives you your content as HTML, Markdown or JSON.
The licence, in plain words
Outline's community edition uses the Business Source License 1.1. Its Additional Use Grant lets you use Outline freely, with one exception: you may not use it for a "Document Service". The LICENSE file defines that, word for word, as "a commercial offering that allows third parties (other than your employees and contractors) to access the functionality of the Licensed Work by creating teams and documents controlled by such third parties."
In practice: running it for your own staff and contractors is fine. Selling or reselling Outline as a hosted service is a breach that, the hosting docs warn, ends your licence automatically. Each release becomes Apache 2.0 at its Change Date or on the fourth anniversary of its first public release, whichever is sooner.
The server that fits
For most teams, an everyday single-processor machine from our dedicated servers is the comfortable choice: plenty of headroom for the app, the database and Redis side by side. Rack servers and blades, plus two cartridge models (m510, m710x), also come with the out-of-band console: the screen and power button that still work when a firewall mistake has locked you out of SSH. The whole machine is yours, the port is unmetered, you can reinstall the operating system yourself whenever you want, with no ticket to open, and terms run month to month. Ready servers are handed over within 30 minutes of payment approval.
Two cases where this is the wrong plan: you want simple username-and-password accounts without connecting any sign-in service (our BookStack guide covers a wiki that has them), or you hope to sell Outline to other companies as a hosted service, which the licence forbids.
Questions people ask
- Is self-hosting Outline free?
- The community edition costs nothing to run for your own organisation under its BSL 1.1 licence. You pay for the server, and paid Business and Enterprise licences exist if you need extras such as SAML. Selling Outline to outside customers as a hosted service is not allowed.
- Can my team log in with just an email address?
- Yes, through magic links, once outgoing email works. Each person enters their address and receives a single-use link that expires after a short time. There are no stored passwords, which the docs point out is safer than a password.
- Why can people read documents but not edit them?
- Almost always the proxy is blocking websockets, which Outline needs for editing. Make sure your nginx site passes the Upgrade and Connection headers and uses HTTP/1.1 towards the app, as in the example above.
- How do I move to a newer Outline version?
- Back up the database, change the version in
.env, pull the new image and start it again. Database migrations run automatically at startup. Because they are not always backwards compatible, the backup is how you go back if something goes wrong.