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 sudo privileges.
  • A domain name, referred to as your_domain (for example wiki.example.com), with a DNS A record pointing to your_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.

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, and URL in docker.env must use the same host and https://.
  • Outline restarts in a loop with a database error: the password in DATABASE_URL does not match POSTGRES_PASSWORD. PostgreSQL only applies that variable when the volume is first created, so if you changed it later, run sudo 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 Upgrade and Connection headers.
  • Uploads fail: check client_max_body_size in Nginx and FILE_STORAGE_UPLOAD_MAX_SIZE in docker.env, then inspect sudo 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.