Self-host (technical)

Run Next Wiki with Docker — environment variables, database, storage and AI.

Self-host with Docker

Next Wiki runs as a single image (the frontend is served by the .NET backend itself, on the same port). You only need Docker and an accessible PostgreSQL. Configuration is done through standardized environment variables (SECRET_KEY, DATABASE_URL, URL, REDIS_URL, SMTP_*…).

Prerequisites

  • Docker 24+ (and Docker Compose v2)
  • PostgreSQL 14+ — can be an existing database; Next Wiki uses its own database (e.g. next_wiki) and never drops it (migrations use IF NOT EXISTS).
  • An OpenAI-compatible AI endpoint (optional, but recommended): OpenAI, a litellm gateway or local Ollama.

Hardware requirements

The image is lean and the app reuses a “warm” connection pool with the database — it runs comfortably on small machines.

ScenarioCPURAMDisk
Minimum (small team)1 vCPU1 GB10 GB
Recommended (app + Postgres on the same VM)2 vCPU2 GB20 GB+
Large team / many attachments2–4 vCPU4 GBas the content grows

Keep backend and PostgreSQL in the same region/network. Each query pays the network latency to the database; with them together, queries stay in a few milliseconds.

Environment variables

VariableRequiredDescription
SECRET_KEYyesSecret that signs the session JWT. ≥ 32 characters.
DATABASE_URLyesPostgres connection: postgresql://user:password@host:5432/next_wiki.
URLyesPublic URL of the wiki (used in invite/email links).
PORTnoPort the app listens on. Default 8080.
BOOTSTRAP_ADMIN_EMAILnoEmail that becomes the first admin on first boot (via magic link).
FILE_STORAGE_LOCAL_ROOT_DIRnoLocal storage root (markdown + icons). Default /app/data.
REDIS_URLnoDistributed cache. Empty = in-memory cache. E.g. redis://host:6379.
SMTP_HOSTSMTP_SECUREnoEmail sending (magic link, invites). Without SMTP_HOST, email is a no-op.
OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRETnoSSO via OIDC (Keycloak, etc). Empty = magic link only.
IA_MODELO, LITELLM_KEYnoDefault model and key for the AI endpoint (OpenAI-compatible).
OTEL_EXPORTER_OTLP_ENDPOINTnoOpenTelemetry observability. Empty = disabled.

docker-compose.yml

services:
  app:
    image: nexttag/next-wiki:latest
    environment:
      SECRET_KEY: "replace-with-a-secret-longer-than-32-chars"
      DATABASE_URL: "postgresql://admin:password@host.docker.internal:5432/next_wiki"
      URL: "https://wiki.yourcompany.com"
      BOOTSTRAP_ADMIN_EMAIL: "you@yourcompany.com"
      # AI (optional) — OpenAI-compatible endpoint (litellm/OpenAI/Ollama)
      IA_MODELO: "gpt-4o-mini"
      LITELLM_KEY: "your-key"
    ports:
      - "5210:8080"                   # host:container
    volumes:
      - nextwiki_data:/app/data       # document markdown + icons
    extra_hosts:
      - "host.docker.internal:host-gateway"

volumes:
  nextwiki_data:

Bring it up with:

docker compose up -d

No Postgres of your own? Point DATABASE_URL to an existing database and just create the database: CREATE DATABASE next_wiki;. Migrations run on boot.

First access

  1. Open the configured URL.
  2. Enter the email set in BOOTSTRAP_ADMIN_EMAIL — it receives the magic link (or, without SMTP, the link appears in the container logs).
  3. Once in, you are already the admin of the workspace. Invite the team and organize the collections.

HTTPS and reverse proxy

The image serves HTTP on the internal port — in production, put it behind a TLS proxy. The app already honors the X-Forwarded-* headers, so the proxy just needs to forward them; links and session cookies are issued with the https scheme correctly.

Caddy (automatic TLS via Let’s Encrypt):

wiki.yourcompany.com {
    reverse_proxy localhost:5210
}

Cloudflare Tunnel (no open ports):

ingress:
  - hostname: wiki.yourcompany.com
    service: http://localhost:5210
  - service: http_status:404

Always set the URL variable with the public https address — it builds the invite/email links and validates the origin of requests.

Verify it’s up

# should return 200 (the root serves the app)
curl -I https://wiki.yourcompany.com

# follow the boot and migrations
docker compose logs -f app

For a Docker healthcheck, add to the app service:

    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/"]
      interval: 30s
      timeout: 5s
      retries: 3

Storage and data

The body of the documents is stored as markdown in FILE_STORAGE_LOCAL_ROOT_DIR (default /app/data), together with the icons. Keep that directory in a volume so you don’t lose content between updates. The metadata (users, permissions, tree) lives in PostgreSQL.

Production: scale and security

  • Rate limiting is built in — the app limits requests by default, no extra configuration.
  • Security headers and response compression are also applied automatically.
  • Real-time editing: collaboration uses WebSocket (SignalR). For one instance, it works with nothing extra. To run multiple instances behind a load balancer, set REDIS_URL — Redis becomes the backplane that synchronizes the connections across nodes (and also serves as a distributed cache).
  • Observability: set OTEL_EXPORTER_OTLP_ENDPOINT to send logs/traces (OpenTelemetry) to your collector (Grafana/Loki, etc).

Update

# 1. back up the database and the volume (see below)
docker compose pull
docker compose up -d        # migrations run automatically on boot

Backup

# Database (metadata)
pg_dump "postgresql://admin:password@localhost:5432/next_wiki" > next_wiki.sql

# Content (markdown + icons)
docker run --rm -v nextwiki_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/nextwiki_data.tgz -C /data .

Troubleshooting

SymptomLikely causeWhat to do
Container won’t start and exitsSECRET_KEY missing/shortSet SECRET_KEY with ≥ 32 characters.
password authentication failed / no connectionwrong DATABASE_URL or unreachable DBCheck host/port/credentials; from inside the container use host.docker.internal.
Magic link doesn’t arriveSMTP not configuredWithout SMTP_HOST, the link appears in the logs (docker compose logs app).
Login fails/redirects wrong behind a proxyURL or X-Forwarded-*Use URL with public https and make sure the proxy forwards the headers.
Content gone after updatingvolume not persisted/app/data must be in a named volume.
AI doesn’t respondAI endpoint/keyCheck IA_MODELO and the key; the endpoint must be OpenAI-compatible.

That’s it — an instance of your own, with your data under your control and the AI pointing to the provider you choose.