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):

VariablePurpose
SESSION_SECRET32-byte hex. iron-session cookie sealing + magic-link HMAC.
RESEND_API_KEYResend BYO key for outbound email.
RESEND_FROM_EMAILVerified sender on Resend.
APP_URLPublic URL — magic links resolve here.
DATABASE_URLSet by compose; points at bundled postgres.
DUODOC_BACKENDStay on 'neon' (default) for the docker stack.
POSTGRES_PASSWORDOptional — rotate before prod.
PORTBound 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.