Self-host
Run duodoc on your own servers. One docker-compose file, one Postgres container, one named volume for data. Your docs sit on your disk; the only outbound calls are to Anthropic (BYOK per user) and Resend (your key) for magic-link emails.
When to self-host
- Your team can't have sensitive HTML drafts sitting in a shared SaaS DB.
- You want stable URLs under your own domain (
docs.yourco.com/d/<slug>). - You want to keep duodoc alive even if the hosted version goes away. The Dockerfile + compose are part of the open-source checkout — no vendor dependency beyond Postgres / Resend / Anthropic.
Quickstart (one machine)
# 1. Clone + set up env git clone https://github.com/aagarwal-gtr/duodoc.git cd duodoc cp .env.example .env # 2. Edit .env — at minimum: # SESSION_SECRET=$(openssl rand -hex 32) # RESEND_API_KEY=re_... # RESEND_FROM_EMAIL=hello@yourdomain.com (must be Resend-verified) # APP_URL=https://docs.yourco.com (or http://localhost:3000) # 3. Build + run docker compose up -d # 4. Open http://localhost:3000 (or your APP_URL behind a proxy)
On startup the duodoc container waits for Postgres to accept connections, applies pending migrations idempotently, then serves on port 3000.
Environment variables
All of these are read by the duodoc container (see .env.example and docker-compose.yml):
| Variable | Purpose |
|---|---|
| SESSION_SECRET | 32-byte hex. iron-session cookie sealing + magic-link HMAC. |
| RESEND_API_KEY | Resend BYO key for outbound email. |
| RESEND_FROM_EMAIL | Verified sender on Resend. |
| APP_URL | Public URL — magic links resolve here. |
| DATABASE_URL | Set by compose; points at bundled postgres. |
| DUODOC_BACKEND | Stay on 'neon' (default) for the docker stack. |
| POSTGRES_PASSWORD | Optional — rotate before prod. |
| PORT | Bound port (default 3000). |
TLS + reverse proxy
The duodoc container doesn't terminate TLS — terminate it upstream with caddy, nginx, or traefik. Sample caddy config:
docs.yourco.com {
reverse_proxy localhost:3000
}Then set APP_URL=https://docs.yourco.com in .env so magic links resolve to the right host.
Upgrades
git pull docker compose build duodoc docker compose up -d
Schema migrations run automatically on container start. The duodoc_pgdata named volume persists data across upgrades; don't delete it unless you mean to.
Backups
Standard Postgres backup story. Easiest: docker compose exec postgres pg_dump -U duodoc duodoc > backup.sql. For automated nightly dumps, mount a cron or use the volume path on the host directly.
Trade-offs vs hosted
- Yours. Your data, your domain, your retention policy.
- Yours to maintain.Upgrades, backups, TLS certs, Resend domain verification — all on you. There's a real ops surface.
- No telemetry.We don't collect anything from self-hosted instances.