Skip to content

Outline Knowledgebase Deployment

Objectives

Just give me the files

You can find the final configurations here:

Tools

  • Linux base system with docker - Using Debian with Docker
  • A way to interact with the system - Using VSCode via SSH
  • An Identity Provider to provide authentication (required for outline) - Using PocketID
  • A proxy with valid TLS certificates and valid domain - using caddy with let's encrypt http01
  • Supporting containers for outline - Using Postgres (database) and Redis (cache)

Process

  • Set up the Proxy
  • Set up Identity Provider
  • Set up Outline

Set up the Reverse Proxy

Info

You can skip this step if you already have a reverse proxy, like caddy or traefik

  • Make sure you have your domain setup and DNS (either from your router, domain provider, or both) pointed at your linux server.
  • Start by creating the docker network for your proxy to sit on

Info

this guide will be working with folders within /mnt/containers, and finishing with three compose stacks

docker network create reverseproxy-nw

  • create up a caddy folder, and within create a container-config folder and a container-data folder. In container-config place a text file called Caddyfile (note the caps). in the root caddy folder place a docker-compose.yaml file.
  • docker-compose.yaml
services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
      - 443:443/udp
    volumes:
      - ./container-config:/etc/caddy
      - ./container-data:/data
      - /etc/localtime:/etc/localtime:ro
    networks:
      - reverseproxy-nw

networks:
  reverseproxy-nw:
    external: true
  • Caddyfile
auth.<your-domain>.com {
    reverse_proxy http://pocketid
}

kb.<your-domain>.com {
    reverse_proxy http://outline:8080
}

  • Bring up with docker compose up -d and verify that you are getting the certificates for the two domains (the sites won't work yet for obvious reasons).

Info

If you want to use let's encrypt certificates but don't want to expose your domain publicly, you can either use DNS challenges (see Reverse Proxies with Caddy), or port forward port 80 only to your server (assuming you are not behind CGNAT). If you do not port forward port 443, port 80 will be used for cert verification but all other external requests will fail.

Set up Identity Provider (PocketID)

Info

You can skip this step if you already have an identity provider like keycloak

Outline does not ship with a built in authentication capability, and relies on an external identity provider (IdP). While this is counterintuitive at first glance, it is in fact best practice to rely on a dedicated identity provider for authentication using Single Sign-On (SSO).

In this circumstance we will set up PocketID as a simple but powerful authentication provider:

  • create a pocketid folder in the working directory and create a docker-compose.yaml file within that. Remember to change your domain
  • docker-compose.yaml
services:
  pocketid:
    image: ghcr.io/stonith404/pocket-id
    container_name: pocketid
    restart: unless-stopped
    # ports:
      # - 3000:80
    volumes:
      - "./container-data:/app/backend/data"
    environment:
      - PUBLIC_APP_URL=https://auth.<your-domain>.com
      - TRUST_PROXY=true
      - PUID=1000
      - PGID=1000
    networks:
      - reverseproxy-nw

networks:
  reverseproxy-nw:
    external: true
  • bring up pocketID with docker compose up -d and check that the site works.

Configure PocketID

PocketID is unique in that it only supports passkeys. Historically this would be problematic from a user experience perspective, but nowadays every major device platform (windows, macos, android, ios) supports passkeys natively. Neat! Let's get registered and create a client for outline.

  • navigate to https://auth.<your-domain>.com/login/setup to perform the first time setup and register your device.

Warning

make sure to change your details before registering your passkey, as changing the username will render the passkey invalid.

Info

you can register more devices by sending one-time links to log in on that device and register it.

  • navigate to OIDC Clients and create a client for outline. Set the Callback URLs to https://kb.<your-domain>.com/auth/oidc.callback. Copy down the client secret (or regenerate it when adding to outline's configuration).

  • (optional) set the session timeout to be higher. 60 min is the default and feels excessively small, I will typically set it either to 1 day or 1 week

Set up Outline

Alright, almost there.

  • Create an outline folder with a docker-compose.yaml file and a .env file.
  • docker-compose.yaml (it's a long one)
services:
  outline:
    container_name: outline
    image: docker.getoutline.com/outlinewiki/outline:latest
    restart: always
    # ports:
      # - "8080:8080"
    volumes:
      - ./container-data/outline:/var/lib/outline/data
      - /etc/localtime:/etc/localtime:ro
    environment:
      - PGSSLMODE=disable
      - FILE_STORAGE=local
      - FORCE_HTTPS=false
      - SECRET_KEY=${SECRET_KEY}
      - UTILS_SECRET=${UTILS_SECRET}
      - DATABASE_URL=postgres://outline:${POSTGRES_PASSWORD}@outline-db:5432/outline
      - REDIS_URL=redis://outline-redis:6379
      - URL=${WIKI_URL}
      - PORT=8080
      - OIDC_CLIENT_ID=${OIDC_CLIENT_ID}
      - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
      - OIDC_AUTH_URI=${OIDC_AUTH_URI}
      - OIDC_TOKEN_URI=${OIDC_TOKEN_URI}
      - OIDC_USERINFO_URI=${OIDC_USERINFO_URI}
      - OIDC_DISPLAY_NAME=${OIDC_DISPLAY_NAME}
      - OIDC_USERNAME_CLAIM=email
    depends_on:
      - postgres
      - redis
    networks:
      - outline-nw
      - reverseproxy-nw

  redis:
    image: redis
    container_name: outline-redis
    restart: always
    # ports:
    #   - "6379:6379"
    networks:
      - outline-nw

  postgres:
    image: postgres
    restart: always
    container_name: outline-db
    # ports:
      # - "5432:5432"
    volumes:
      - ./container-data/db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-d", "outline", "-U", "user"]
      interval: 30s
      timeout: 20s
      retries: 3
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_USER=outline
      - POSTGRES_DB=outline
    networks:
      - outline-nw

networks:
  outline-nw:
  reverseproxy-nw:
    external: true
  • .env
#secrets/passwords. Generated 3x using `openssl rand -hex 32`
SECRET_KEY=<generate a key>
UTILS_SECRET=<generate another key>
POSTGRES_PASSWORD=<generate a third key>

#domains
WIKI_URL=https://kb.<your-domain>.com

#oidc information
OIDC_CLIENT_ID=<from pocket id>
OIDC_CLIENT_SECRET=<from pocket id>
OIDC_AUTH_URI=https://auth.<your-domain>.com/authorize
OIDC_TOKEN_URI=https://auth.<your-domain>.com/api/oidc/token
OIDC_USERINFO_URI=https://auth.<your-domain>.com/api/oidc/userinfo
OIDC_DISPLAY_NAME=Pocket ID
  • Create the container-data/outline directory and change the owner to the container user (nodejs/1001). Reduce permissions on the .env file
cd /mnt/containers/outline
mkdir -p container-data/outline
chown 1001:1001 container-data/outline
chmod 600 .env
  • Bring up the service (yes you can see secrets in this recording. This is a lab demo that gets wiped).

  • Test that pasting content (like images) works correctly

You're good to go!