Skip to main content

Outline

A self-hosted team wiki and knowledge base.

Outline is an open-source wiki and documentation platform with a rich Markdown editor, nested document structure, full-text search, and team collaboration features. Self-hosting avoids per-seat subscription costs and keeps internal documentation on your own infrastructure.

Alternatives considered

Cloud Hosted

ToolOpen SourceFree TierMonthly Cost
NotionNoLimitedFrom $10/user
ConfluenceNoLimitedFrom $6/user

Self Hosted

ToolOpen SourceFull FeaturesNotes
BookStackYesYesSimpler wiki with book/chapter structure

Installation

Architecture

  • Deployment: Single outline deployment in the outline namespace
  • Images: outlinewiki/outline:1.6.1, valkey/valkey:9.0.3-alpine (both digest-pinned)
  • Cache: Valkey StatefulSet for session and cache
  • Database: CNPG PostgreSQL cluster with Longhorn-encrypted PVCs
  • Storage: Longhorn PVC (outline-data) for file attachments
  • Networking: ClusterIP service on port 3000, HTTPRoute via internal gateway

Security

  • Runs as runAsUser: 1001, runAsNonRoot: true, readOnlyRootFilesystem: true, allowPrivilegeEscalation: false, capabilities dropped
  • Longhorn PVCs encrypted at rest

Updates

Managed by Renovate. Both images are digest-pinned.

Data Management

  • Database: CNPG PostgreSQL cluster (Longhorn-encrypted PVCs)
  • PVC: outline-data (Longhorn-encrypted, k8up.io/backup: "true") for file attachments
  • Backups: k8up Schedule backs up CNPG Longhorn PVCs to Hetzner S3. CNPG annotated with k8up.io/backupcommand: pg_dump.

User Management

Full OIDC configured — OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_AUTH_URI, OIDC_TOKEN_URI, OIDC_USERINFO_URI, OIDC_LOGOUT_URI, OIDC_USERNAME_CLAIM, and OIDC_SCOPES all set via SOPS-encrypted secret.

Configuration Management

  • OIDC credentials and all app secrets from SOPS-encrypted secret
  • Database URL injected from CNPG-generated secret

Administration

Usage

Create and organize documents in a nested tree structure. Use Markdown with rich embeds (images, code blocks, tables). Share documents with the team or make them public. OIDC SSO means users authenticate with their existing identity provider credentials.

Cluster-specific deviations from the above live in the per-cluster README — see k8s/apps/talos/outline/README.md.

Cluster Deployment

Outline — Talos cluster

Cluster-specific notes only. General product info, "why we use it", and alternatives live in docusaurus/docs/apps/outline.mdx.

Deviations from defaults

Defaults live in docusaurus/docs/apps/outline.mdx — document anything this cluster does differently here, with a one-line reason.

Kubernetes Metadata
  • Image: outlinewiki/outline:1.8.1@sha256:e224dcbe34670bdae8835c32d5abc692d3560dfa262b72fb7232f4d87185aebd
  • Image: valkey/valkey:9.1.0-alpine@sha256:a35428eba9043cc0b79dbe54100f0c92784f2de00ad09b01182bfb1c5c83d1bd
Rendered manifests (kustomize build)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kustomize.toolkit.fluxcd.io/force: enabled
labels:
app: outline
name: outline
namespace: outline
spec:
replicas: 1
selector:
matchLabels:
app: outline
ingress: public
strategy:
rollingUpdate: null
type: Recreate
template:
metadata:
labels:
app: outline
ingress: public
spec:
containers:
- env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
key: uri
name: cnpg-app
envFrom:
- secretRef:
name: outline
image: outlinewiki/outline:1.8.1@sha256:e224dcbe34670bdae8835c32d5abc692d3560dfa262b72fb7232f4d87185aebd
livenessProbe:
failureThreshold: 5
httpGet:
path: /
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
name: outline
ports:
- containerPort: 3000
name: web
protocol: TCP
readinessProbe:
failureThreshold: 5
httpGet:
path: /
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: /var/lib/outline/data
name: outline-data
- mountPath: /tmp
name: empty-dir
securityContext:
fsGroup: 1001
fsGroupChangePolicy: OnRootMismatch
runAsGroup: 1001
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
volumes:
- emptyDir: {}
name: empty-dir
- name: outline-data
persistentVolumeClaim:
claimName: outline-data
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: valkey
namespace: outline
spec:
replicas: 1
selector:
matchLabels:
app: valkey
serviceName: valkey
template:
metadata:
labels:
app: valkey
spec:
containers:
- args:
- valkey-server
image: valkey/valkey:9.1.0-alpine@sha256:a35428eba9043cc0b79dbe54100f0c92784f2de00ad09b01182bfb1c5c83d1bd
livenessProbe:
initialDelaySeconds: 10
periodSeconds: 10
tcpSocket:
port: 6379
name: valkey
ports:
- containerPort: 6379
name: client
readinessProbe:
initialDelaySeconds: 3
periodSeconds: 5
tcpSocket:
port: 6379
resources:
limits:
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
volumeMounts:
- mountPath: /conf
name: conf
- mountPath: /data
name: data
securityContext:
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 999
seccompProfile:
type: RuntimeDefault
volumes:
- emptyDir: {}
name: conf
- emptyDir: {}
name: data