Self-hosting a team wiki with Docker: a practical guide

Most teams reach for a hosted wiki because it's the path of least resistance: sign up, invite people, done. But a wiki is where your architecture decisions, incident write-ups, onboarding docs, and half your institutional memory end up. At some point it's worth asking whether you actually want all of that living on someone else's infrastructure, metered per head. This is a practical look at what it takes to run one yourself with Docker — the real moving parts, the commands, and the things that bite.

Why self-host

Three reasons come up over and over, and none of them are ideology.

Data ownership. Your wiki is a primary record. Self-hosting means the database is yours — you can query it, back it up, export it, and leave without negotiating an export format or waiting on a support ticket. Nobody changes the terms under you.

Data residency and compliance. If you have customers or regulators who care where data physically sits, "it's in our VPC in eu-central-1" is a sentence you can actually say. With a hosted SaaS you're bound to wherever they run, and that's frequently the thing that stalls a procurement review.

No per-seat tax. Per-user pricing punishes exactly the behavior you want — adding more people, inviting contractors, giving read access widely. Self-hosting moves you to an infrastructure cost: you pay for a box and some Postgres storage, and the marginal cost of the 200th reader is essentially zero.

The honest counterweight: you now own uptime, upgrades, and backups. That's the trade. The rest of this post is about making that trade small.

What a real self-host stack needs

A wiki that looks like one container is almost never one container. A realistic stack has three core pieces:

  1. The application — the server that renders pages, handles auth, and serves the editor.
  2. PostgreSQL — the actual system of record. Pages, revisions, permissions, search indexes. This is the part you must back up; everything else is replaceable.
  3. A reverse proxy — TLS termination, a stable public hostname, and a single ingress in front of the app. Caddy and Traefik both do automatic Let's Encrypt certs; nginx is fine if you'd rather manage certs yourself.

That's the irreducible core. Treat anything that isn't "app + database + proxy" as optional until proven otherwise.

The docker compose up path

The mechanics are the same regardless of which wiki you pick. You want a docker-compose.yml describing the three services, an .env file holding secrets, and a single command to bring it up. Conceptually:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${PG_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
  app:
    image: your-wiki:latest
    environment:
      DATABASE_URL: postgres://wiki:${PG_PASSWORD}@db:5432/wiki
      API_KEY_SECRET: ${API_KEY_SECRET}
      SHARE_SECRET: ${SHARE_SECRET}
    depends_on: [db]
  proxy:
    image: caddy:2
    ports: ["443:443"]
    # routes the public hostname to app:PORT, terminates TLS
volumes:
  pgdata:

Then:

cp .env.example .env   # fill in the secrets below
docker compose up -d
docker compose logs -f app

On first boot the app runs its database migrations against the empty Postgres and comes up. The good projects ship a first-run setup wizard — you hit the proxy's hostname in a browser and create the first admin account through a /setup page rather than juggling bootstrap credentials in the environment. Watch the logs the first time so you see the migration run complete before you start clicking around.

The secrets you must set — and keep stable

This is the part people get wrong, and it fails quietly. A self-hosted wiki typically needs a database password (often with no default, so compose refuses to start without it — the good kind of failure), an API/token signing secret, and a share/session secret.

The trap is that the signing secrets often do have a default (frequently empty), so the stack boots and looks healthy while issuing forgeable tokens. Set them to long random values:

openssl rand -hex 32

And then the rule that actually matters: once set, leave them alone. Rotating the API key secret invalidates every outstanding access token; rotating the share/session secret logs everyone out and breaks live share links. Keep .env in a secrets manager or at least diffed against .env.example after every change, and back it up separately from the database — restoring a database backup against a different set of secrets is its own bad afternoon.

The optional pieces

Beyond the core three, modern wikis bolt on extra services. Add these only when you want the feature they enable:

Start with app + DB + proxy, confirm it's solid, then add a sidecar when someone actually asks for the feature.

Backups

Your backup is the Postgres database plus the secrets file. Everything else rebuilds from images.

docker compose exec -T db pg_dump -U wiki wiki | gzip > wiki-$(date +%F).sql.gz

Run that on a schedule, ship the dumps off the box, and — the step everyone skips — test a restore into a throwaway stack at least once. A backup you've never restored is a hope, not a backup. If you store attachments on a volume rather than in the database, back that volume up too.

Honest gotchas

One concrete option: tela

If you want something to actually try, tela is one example of this shape. It's a markdown-native team wiki — Go and PostgreSQL on the backend, a React/Milkdown editor with live Yjs collaboration up front — and it self-hosts as a standalone Docker Compose stack: app, Postgres (with pgvector), and a Caddy proxy that publishes on :8780, with a /setup wizard on first boot. The secrets are exactly the ones discussed here — a Postgres password and two signing secrets that must stay stable.

It's open-source under AGPL-3.0, and the Community core is the whole product: the wiki, full-text plus semantic search, live collaboration, public spaces, orgs and roles, a built-in MCP server (39 tools, so coding agents are first-class), and Atlas, which auto-generates a cited, coverage-checked wiki from sources like git and Jira. There's a small Enterprise add-on for SSO, audit, SCIM, and governance if you need it, but nothing core is paywalled. You can run it entirely yourself, or start on the free cloud tier at telawiki.com and move to self-hosting later — the database is yours either way.

Whatever you choose, the playbook is the same: three core services, stable secrets, tested backups, and optional pieces added only when you need them. Do that and self-hosting stops being scary and starts being boring — which, for a wiki, is exactly what you want.