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 useIF 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.
| Scenario | CPU | RAM | Disk |
|---|---|---|---|
| Minimum (small team) | 1 vCPU | 1 GB | 10 GB |
| Recommended (app + Postgres on the same VM) | 2 vCPU | 2 GB | 20 GB+ |
| Large team / many attachments | 2–4 vCPU | 4 GB | as 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
| Variable | Required | Description |
|---|---|---|
SECRET_KEY | yes | Secret that signs the session JWT. ≥ 32 characters. |
DATABASE_URL | yes | Postgres connection: postgresql://user:password@host:5432/next_wiki. |
URL | yes | Public URL of the wiki (used in invite/email links). |
PORT | no | Port the app listens on. Default 8080. |
BOOTSTRAP_ADMIN_EMAIL | no | Email that becomes the first admin on first boot (via magic link). |
FILE_STORAGE_LOCAL_ROOT_DIR | no | Local storage root (markdown + icons). Default /app/data. |
REDIS_URL | no | Distributed cache. Empty = in-memory cache. E.g. redis://host:6379. |
SMTP_HOST … SMTP_SECURE | no | Email sending (magic link, invites). Without SMTP_HOST, email is a no-op. |
OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET | no | SSO via OIDC (Keycloak, etc). Empty = magic link only. |
IA_MODELO, LITELLM_KEY | no | Default model and key for the AI endpoint (OpenAI-compatible). |
OTEL_EXPORTER_OTLP_ENDPOINT | no | OpenTelemetry 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_URLto an existing database and just create the database:CREATE DATABASE next_wiki;. Migrations run on boot.
First access
- Open the configured
URL. - Enter the email set in
BOOTSTRAP_ADMIN_EMAIL— it receives the magic link (or, without SMTP, the link appears in the container logs). - 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
URLvariable 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_ENDPOINTto 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
| Symptom | Likely cause | What to do |
|---|---|---|
| Container won’t start and exits | SECRET_KEY missing/short | Set SECRET_KEY with ≥ 32 characters. |
password authentication failed / no connection | wrong DATABASE_URL or unreachable DB | Check host/port/credentials; from inside the container use host.docker.internal. |
| Magic link doesn’t arrive | SMTP not configured | Without SMTP_HOST, the link appears in the logs (docker compose logs app). |
| Login fails/redirects wrong behind a proxy | URL or X-Forwarded-* | Use URL with public https and make sure the proxy forwards the headers. |
| Content gone after updating | volume not persisted | /app/data must be in a named volume. |
| AI doesn’t respond | AI endpoint/key | Check 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.