Deployment
Docker Compose
The full stack runs with a single command:
docker compose up -dThis starts:
doto-postgres— PostgreSQL 16 with a named volume for persistencedoto-server— The API server plus the built web frontend (waits for Postgres to be healthy)
The server runs database migrations automatically on startup.
Environment variables
Copy .env.example to .env and configure:
# Required
DATABASE_URL=postgresql://doto:doto_dev@postgres:5432/doto_dev
PORT=3000
NODE_ENV=production
# Optional — AI classification
AI_PROVIDER=openai # openai or anthropic
AI_MODEL=gpt-4o-mini # optional, auto-selects if omitted
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_BASE_URL=... # optional, override the OpenAI endpoint
ANTHROPIC_BASE_URL=... # optional, override the Anthropic endpointIn Docker Compose, DATABASE_URL uses the service name postgres as the host. For local development outside Docker, use 127.0.0.1.
Build the image
docker compose buildThe Dockerfile uses a two-stage build:
Stage 1 — builder (oven/bun:1.3.12)
- Install all dependencies (including devDependencies)
- Build the React frontend from
packages/web - Run
prisma generate - Compile the server TypeScript with
tsc
Stage 2 — production (oven/bun:1.3.12)
- Install production dependencies only
- Copy compiled server
dist/,prisma/,prisma.config.ts, and built web assets from builder - Run
prisma generateagain (ensures ESM-compatible client) - Set
WEB_DIST_PATHso the server can serve the frontend - Set entrypoint
Startup sequence
packages/server/scripts/entrypoint.sh:
bun run prisma migrate deploy # Apply any pending migrations
bun /app/packages/server/dist/index.js # Start the serverHealth check
Docker Compose polls the health endpoint every 10 seconds:
GET http://localhost:3000/healthThe server is considered healthy when it returns 200. The health endpoint also checks database connectivity — it returns 503 if the database is unreachable.
Frontend routing
The production deployment is same-origin:
- API requests are served from
http://localhost:3000/api/v1/* - The login UI and other SPA routes are served from
http://localhost:3000/* - Google OAuth callback redirects back to
/auth/callbackon the same origin unlessFRONTEND_URLis explicitly set
Data persistence
PostgreSQL data is stored in a named Docker volume doto-pg-data. It survives container restarts and docker compose down.
To wipe all data:
docker compose down -vLogs
docker compose logs server # API server logs
docker compose logs postgres # Database logs
docker compose logs -f # Follow all logs