Skip to Content

Production Guide: Deploy Outline with Docker Compose, Caddy, PostgreSQL, and Redis on Ubuntu

A practical, production-focused runbook for self-hosted team knowledge with secure access, backups, and operational guardrails.

When product, support, and engineering teams scale, knowledge fragments quickly across chats, docs, tickets, and personal notes. Outline gives teams a clean, searchable internal knowledge base, but production deployments need more than a quick container launch. You need durable storage, reliable sessions, secure cookies, TLS, backup strategy, and a predictable upgrade workflow. This guide shows a production-ready deployment of Outline on Ubuntu using Docker Compose, Caddy, PostgreSQL, and Redis, with practical checks and incident-minded operational guidance.

Architecture and flow overview

This stack separates responsibilities so operations remain simple under load: Caddy terminates TLS and reverse-proxies traffic, Outline serves the app, PostgreSQL stores durable content and metadata, and Redis handles cache/session primitives. We isolate services on a private Docker network, expose only the reverse proxy, and persist all stateful services on named volumes. The result is an architecture that is easy to audit, back up, and recover.

  • Caddy: HTTPS and reverse proxy.
  • Outline: app/API for docs and search.
  • PostgreSQL: durable relational state.
  • Redis: cache/session queue primitives.

Prerequisites

  • Ubuntu 22.04/24.04 server (2 vCPU, 4 GB RAM minimum).
  • DNS record (for example wiki.example.com) pointing to the host.
  • Sudo access.
  • Firewall open on 80/443.
  • Docker Engine and Docker Compose plugin installed.
  • SMTP credentials for invites and password reset links.

Step 1: Prepare host and baseline security

Patch first, then configure firewall and application paths so operations stay repeatable. A predictable host baseline is what makes incident response fast and low-risk when troubleshooting login outages, certificate issues, or database restores.

sudo apt update && sudo apt -y upgrade
sudo apt -y install ca-certificates curl gnupg ufw
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo mkdir -p /opt/outline/{caddy,data}
sudo chown -R $USER:$USER /opt/outline

If the copy button does not work in your browser/editor, manually select the code block and copy it.

Step 2: Create production Docker Compose stack

Keep only Caddy public; all stateful services remain private on the internal network. This boundary lowers attack surface and also prevents accidental direct database exposure.

cat > /opt/outline/docker-compose.yml <<'YAML'
services:
  caddy:
    image: caddy:2.8
    container_name: outline-caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on: [outline]
    networks: [outline_net]
  outline:
    image: outlinewiki/outline:latest
    container_name: outline-app
    restart: unless-stopped
    env_file: .env
    depends_on: [postgres, redis]
    networks: [outline_net]
  postgres:
    image: postgres:15
    container_name: outline-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: outline
      POSTGRES_USER: outline
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data
    networks: [outline_net]
  redis:
    image: redis:7-alpine
    container_name: outline-redis
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    networks: [outline_net]
networks:
  outline_net:
    driver: bridge
volumes:
  pg_data:
  redis_data:
  caddy_data:
  caddy_config:
YAML

If the copy button does not work in your browser/editor, manually select the code block and copy it.

Step 3: Configure Caddy for HTTPS

Explicit proxy headers prevent protocol confusion and login/session edge cases. Keep logs enabled from day one so your team can reconstruct events during incidents without rebuilding observability later.

cat > /opt/outline/caddy/Caddyfile <<'CADDY'
wiki.yourdomain.com {
    encode zstd gzip
    reverse_proxy outline:3000 {
        header_up X-Forwarded-Proto {scheme}
        header_up X-Forwarded-For {remote_host}
        header_up Host {host}
    }
    log {
        output file /data/access.log
        format json
    }
}
CADDY

If the copy button does not work in your browser/editor, manually select the code block and copy it.

Step 4: Configuration and secret handling

Generate strong secrets and lock the env file. For mature teams, move to centralized secret management and inject at deploy time. Document ownership for each credential so incident responders know exactly who rotates which secret.

cd /opt/outline
POSTGRES_PASSWORD=$(openssl rand -base64 36 | tr -d '\n')
SECRET_KEY=$(openssl rand -hex 32)
UTILS_SECRET=$(openssl rand -hex 32)
cat > .env <<EOF
URL=https://wiki.yourdomain.com
PORT=3000
NODE_ENV=production
SECRET_KEY=$SECRET_KEY
UTILS_SECRET=$UTILS_SECRET
DATABASE_URL=postgres://outline:$POSTGRES_PASSWORD@postgres:5432/outline
REDIS_URL=redis://redis:6379
PGSSLMODE=disable
FORCE_HTTPS=true
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USERNAME=your_smtp_user
SMTP_PASSWORD=your_smtp_password
[email protected]
EOF
chmod 600 .env

If the copy button does not work in your browser/editor, manually select the code block and copy it.

Step 5: Launch and migrate

Bring up backing services first, then run migrations, then expose full stack. This sequence makes failures easier to isolate and reduces the chance of partially initialized application state.

cd /opt/outline
docker compose pull
docker compose up -d postgres redis
sleep 8
docker compose run --rm outline yarn db:migrate --env=production
docker compose up -d
docker compose ps

If the copy button does not work in your browser/editor, manually select the code block and copy it.

Verification

Run these checks before inviting users and declaring go-live. Test from a second browser session to validate first-time login and invite-link flows end to end.

  1. HTTPS valid and auto-renewing.
  2. Sign-in and invite email delivery works.
  3. Create/edit/search latency is acceptable.
  4. Restart test confirms persistence.
  5. Backups complete and are restorable.
curl -I https://wiki.yourdomain.com
docker compose ps
docker compose logs --tail=120 outline
docker exec outline-postgres pg_dump -U outline outline | head -n 30

If the copy button does not work in your browser/editor, manually select the code block and copy it.

Common issues and fixes

Redirect loop on login

Usually an incorrect URL value or missing X-Forwarded-Proto header.

Emails not sending

Validate SMTP credentials, sender policy, and outbound provider restrictions.

Migrations failing

Wait for PostgreSQL health and verify DATABASE_URL syntax exactly.

Slow response during peaks

Check host CPU steal/memory pressure and scale instance resources.

Upload errors

Increase proxy/body limits and verify reverse proxy timeout behavior.

Unexpected logout/session churn

Confirm Redis availability and stable app secret values across restarts.

Operational hardening notes

Production quality comes from operational discipline, not only deployment success. Add a recurring backup job, monitor certificate renewal, and instrument baseline metrics such as response latency, 5xx rate, and disk consumption for PostgreSQL and Redis. Create a lightweight on-call runbook describing what to check first for login failures, SMTP degradation, and storage pressure. Document exact restore procedures and run practice restores monthly in staging so you can verify both backup integrity and team readiness. Define explicit SLOs for availability and page load latency so teams can distinguish normal variance from true incident behavior.

For security posture, keep the host patched, use SSH keys only, disable password login, and restrict administrative access to trusted IP ranges where possible. If your compliance environment requires stronger controls, front the stack with an identity-aware proxy and central audit logging. As your organization grows, integrate SSO and enforce role boundaries for editors and admins. These practices reduce accidental misconfiguration risk and make audits easier. Also schedule quarterly dependency reviews so container base images, proxy versions, and cryptographic defaults remain current.

Finally, create a predictable release calendar. Bundle low-risk changes weekly and reserve larger version upgrades for approved maintenance windows. Capture pre-change health snapshots, run post-change smoke checks, and maintain rollback criteria that are simple enough to execute under pressure. This process turns upgrades from stressful events into routine operations and prevents documentation platforms from becoming hidden single points of failure.

Internal links and related guides

FAQ

Do I need Kubernetes for Outline?

No. Compose is often enough for small-to-mid teams if backups, monitoring, and upgrades are handled carefully.

What VM size should I start with?

2 vCPU/4 GB RAM is a practical start; move to 4 vCPU/8 GB as users and uploads grow.

Can I use managed PostgreSQL?

Yes, and it can improve durability/ops overhead if network access and TLS are configured correctly.

How often should backups run?

Nightly full backups are baseline; add higher frequency for stricter RPO requirements.

Should Redis be exposed publicly?

No. Keep Redis private on the Docker network and avoid public bind ports.

How do I upgrade safely?

Stage first, snapshot data, run migrations, verify critical flows, then promote to production.

What if JavaScript is stripped by the editor?

The manual-copy fallback text under each code block ensures users can still copy commands reliably.

Talk to us

Need help deploying a production-ready Outline knowledge platform, integrating SSO, or building secure backup and upgrade runbooks for your team? We can help with architecture, hardening, migration, and operational readiness.

Contact Us

Deploy Grafana with Docker Compose and Traefik on Ubuntu: Production-Ready Observability Guide
A step-by-step production guide to running Grafana 11 behind Traefik v3 with automatic TLS, Prometheus data source, and secure dashboard routing.