Outline is an open-source knowledge base for teams, with a fast Markdown editor, real-time collaboration and full-text search. In this tutorial you will run Outline, PostgreSQL and Redis with Docker Compose on Ubuntu 24.04, store uploads on the local disk, sign in through an OpenID Connect (OIDC) provider, and publish the wiki over HTTPS behind Nginx. You will finish with a scheduled database backup.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS with at least 2 GB of RAM (4 GB recommended) and 20 GB of free disk, for example a CubePath VPS.
- A non-root user with
sudoprivileges. - A domain name, referred to as
your_domain(for examplewiki.example.com), with a DNS A record pointing toyour_server_ip. - An OpenID Connect provider where you can register an application, such as Authentik, Keycloak, Authelia, Zitadel or Google Workspace. Outline has no built-in username and password login, so the first user must come from an identity provider.
Step 1 - Installing Docker Engine and Docker Compose
Outline is distributed as a container image, so install Docker Engine and the Compose plugin from Docker's official repository. First add the repository key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
Add the repository and install the packages:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] 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 docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Check that both components respond:
sudo docker --version
sudo docker compose version
Docker version 28.x.x, build xxxxxxx
Docker Compose version v2.x.x
This guide runs Docker with sudo. Adding your user to the docker group also works, but that group is equivalent to root access.
Step 2 - Registering Outline with your OIDC provider
In your identity provider, create a new OAuth2/OpenID Connect application (a "confidential" client) with these settings:
- Redirect URI:
https://your_domain/auth/oidc.callback - Scopes:
openid,profile,email
Note the client ID, the client secret, and the three endpoint URLs the provider shows: authorization, token and userinfo. Most providers list them on the application page or in their discovery document at https://your_idp/.well-known/openid-configuration.
Step 3 - Creating the Outline configuration
Create a directory for the deployment and move into it:
sudo mkdir -p /opt/outline
cd /opt/outline
Outline needs two random secrets and PostgreSQL needs a password. Generate them now and keep the output at hand:
openssl rand -hex 32
openssl rand -hex 32
openssl rand -hex 16
The first value is SECRET_KEY, the second is UTILS_SECRET, and the third is the database password, referred to as your_db_password. A hex password avoids characters that would need escaping inside the database URL.
Create the environment file that the Outline container reads:
sudo nano /opt/outline/docker.env
Paste the following, replacing every placeholder:
NODE_ENV=production
URL=https://your_domain
PORT=3000
FORCE_HTTPS=true
SECRET_KEY=your_secret_key
UTILS_SECRET=your_utils_secret
DATABASE_URL=postgres://outline:your_db_password@postgres:5432/outline
PGSSLMODE=disable
REDIS_URL=redis://redis:6379
FILE_STORAGE=local
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE=262144000
OIDC_CLIENT_ID=your_client_id
OIDC_CLIENT_SECRET=your_client_secret
OIDC_AUTH_URI=https://your_idp/authorize
OIDC_TOKEN_URI=https://your_idp/token
OIDC_USERINFO_URI=https://your_idp/userinfo
OIDC_USERNAME_CLAIM=preferred_username
OIDC_DISPLAY_NAME=Company SSO
OIDC_SCOPES=openid profile email
URL must match the public address exactly, including https://, or sign-in redirects will fail. FILE_STORAGE=local keeps uploads in a Docker volume so you don't need an S3 bucket. OIDC_DISPLAY_NAME is the label shown on the login button.
The file contains secrets, so restrict it to root:
sudo chmod 600 /opt/outline/docker.env
Step 4 - Writing the Docker Compose file
Create the Compose file:
sudo nano /opt/outline/compose.yml
Add the three services. Use the same your_db_password value as in DATABASE_URL:
services:
outline:
image: docker.getoutline.com/outlinewiki/outline:latest
restart: unless-stopped
env_file: ./docker.env
ports:
- "127.0.0.1:3000:3000"
volumes:
- storage-data:/var/lib/outline/data
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
redis:
image: redis:7
restart: unless-stopped
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: outline
POSTGRES_PASSWORD: your_db_password
POSTGRES_DB: outline
volumes:
- database-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "outline", "-d", "outline"]
interval: 10s
timeout: 5s
retries: 5
volumes:
storage-data:
database-data:
Only Outline publishes a port, and only on 127.0.0.1. Ports published by Docker bypass UFW rules, so binding to localhost is what keeps the application reachable only through Nginx. PostgreSQL and Redis stay on the internal Compose network.
Protect the file, since it also contains the password:
sudo chmod 600 /opt/outline/compose.yml
Step 5 - Starting the stack
Pull the images and start the containers in the background:
cd /opt/outline
sudo docker compose up -d
On first start Outline runs its database migrations, which takes up to a minute. Check the state of the services:
sudo docker compose ps
All three services should show Up, and postgres should report (healthy). Follow the Outline log to confirm that migrations finished without errors:
sudo docker compose logs -f outline
Press Ctrl+C to stop following the log. Then check that Outline answers locally:
curl -I http://127.0.0.1:3000
Any HTTP response confirms the application is listening. Because FORCE_HTTPS=true, a redirect to https:// is expected at this stage.
Step 6 - Configuring Nginx as a reverse proxy
Install Nginx and allow SSH, HTTP and HTTPS through the firewall:
sudo apt install nginx
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Create a server block for the wiki:
sudo nano /etc/nginx/sites-available/outline
Outline uses WebSockets for real-time collaboration, so the proxy must pass the Upgrade headers:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name your_domain;
client_max_body_size 250M;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $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;
}
}
Enable the site, test the syntax and reload Nginx:
sudo ln -s /etc/nginx/sites-available/outline /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Step 7 - Enabling HTTPS with Let's Encrypt
Install Certbot with its Nginx plugin and request a certificate. Certbot adds the TLS directives and an HTTP to HTTPS redirect to the server block for you:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain
The Ubuntu package installs a systemd timer that renews certificates automatically. Confirm renewal works with a dry run:
sudo certbot renew --dry-run
Step 8 - Signing in and creating the workspace
Open https://your_domain in your browser. You will see a button with the name you set in OIDC_DISPLAY_NAME. Click it, authenticate with your provider, and Outline creates the workspace with you as its first administrator.
Everyone who can authenticate against your OIDC application can join the workspace, so restrict access on the provider side (group or policy bindings), or adjust the allowed domains under Settings > Security in Outline.
NoteInvitations and notification emails need SMTP. Add
SMTP_HOST,SMTP_PORT,SMTP_USERNAME,SMTP_PASSWORD,SMTP_FROM_EMAILandSMTP_SECUREtodocker.env, then runsudo docker compose up -dto recreate the container.
Step 9 - Backing up the database and uploads
Outline keeps documents in PostgreSQL and attachments in the storage-data volume. Create a backup script:
sudo nano /usr/local/bin/outline-backup
#!/usr/bin/env bash
set -euo pipefail
backup_dir="/var/backups/outline"
stamp="$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir"
cd /opt/outline
docker compose exec -T postgres pg_dump -U outline -d outline \
| gzip > "$backup_dir/outline-db-$stamp.sql.gz"
docker run --rm -v outline_storage-data:/data:ro -v "$backup_dir":/backup \
alpine tar -czf "/backup/outline-files-$stamp.tar.gz" -C /data .
find "$backup_dir" -type f -mtime +14 -delete
Compose prefixes volume names with the project name, which is the directory name (outline), so the uploads volume is outline_storage-data. Make the script executable and run it once:
sudo chmod 750 /usr/local/bin/outline-backup
sudo /usr/local/bin/outline-backup
ls -lh /var/backups/outline
Schedule it every night at 02:30 with a cron file:
echo '30 2 * * * root /usr/local/bin/outline-backup' | sudo tee /etc/cron.d/outline-backup
Copy the backup directory to another machine or object storage; a backup on the same disk does not protect against losing the server.
Updating Outline
Pull the new image and recreate the container. Migrations run automatically on start:
cd /opt/outline
sudo /usr/local/bin/outline-backup
sudo docker compose pull
sudo docker compose up -d
Read the release notes on GitHub before jumping several versions, and pin a specific tag instead of latest if you prefer controlled upgrades.
Troubleshooting
- The OIDC login returns a redirect URI error: the redirect URI registered in the provider must be exactly
https://your_domain/auth/oidc.callback, andURLindocker.envmust use the same host andhttps://. - Outline restarts in a loop with a database error: the password in
DATABASE_URLdoes not matchPOSTGRES_PASSWORD. PostgreSQL only applies that variable when the volume is first created, so if you changed it later, runsudo docker compose down -v(this deletes all data) on a fresh install, or change the password inside PostgreSQL. - Real-time editing does not sync or shows "connection lost": the Nginx server block is missing the
UpgradeandConnectionheaders. - Uploads fail: check
client_max_body_sizein Nginx andFILE_STORAGE_UPLOAD_MAX_SIZEindocker.env, then inspectsudo docker compose logs outline.
Conclusion
Outline is now running on Ubuntu 24.04 with PostgreSQL, Redis and local file storage, served over HTTPS and protected by your identity provider, with nightly backups. As next steps, configure SMTP so users receive invitations and notifications, import existing Markdown or Confluence content from Settings > Import, and move uploads to S3-compatible storage if the wiki will hold many large attachments.
