Docker Setup¶
Doable's Docker setup uses Compose v2 with a multi-stage Dockerfile. Everything you need is in docker/.
Three deployment modes (one script)¶
docker/setup.sh handles secrets, nginx, SSL, builds, and firewall in one shot. Pick a mode:
| Mode | Command | Use when |
|---|---|---|
| Localhost | ./docker/setup.sh |
Trying it out on your laptop |
| Private network / LAN | HOST=192.168.1.50 ./docker/setup.sh |
Air-gapped server, Tailscale, home lab |
| Public domain + Let's Encrypt | DOMAIN=app.example.com ./docker/setup.sh |
Real production |
| Public domain, behind another proxy | DOMAIN=app.example.com ./docker/setup.sh --skip-ssl |
Cloudflare proxy, ingress controller, etc. |
What it does in each mode:
- Generates
docker/.envwith randomJWT_SECRET,ENCRYPTION_KEY,INTERNAL_SECRET. - Sets
NEXT_PUBLIC_*URLs to match your domain/IP. - Installs nginx if missing.
- Obtains Let's Encrypt cert (domain mode) or generates a self-signed cert (IP/localhost mode).
- Writes nginx config from
nginx.conf.template. - Configures UFW: ports 22, 80, 443 only.
- Builds and starts all containers.
Compose services¶
Defined in docker/docker-compose.yml:
| Service | Image | Port (host) | Notes |
|---|---|---|---|
postgres |
pgvector/pgvector:pg16 |
127.0.0.1:5432 |
Volume: postgres_data |
migrate |
built locally, target migrate |
— | Runs once, exits 0 |
api |
built locally, target api |
127.0.0.1:4000 |
Hono REST API |
ws |
built locally, target ws |
127.0.0.1:4001 |
WebSocket / Yjs |
web |
built locally, target web |
127.0.0.1:3000 |
Next.js |
redis |
redis:7-alpine |
127.0.0.1:6379 |
Profile redis (opt-in) |
All host ports bind to 127.0.0.1 — never to 0.0.0.0. The only public-facing process is the host's nginx (installed by setup.sh).
Manual Compose usage¶
If you'd rather run Compose directly without setup.sh:
cp docker/.env.example docker/.env
# Edit docker/.env — set the three secrets:
# JWT_SECRET, ENCRYPTION_KEY, INTERNAL_SECRET (each: openssl rand -hex 32)
docker compose -f docker/docker-compose.yml up --build
Then put your own reverse proxy (nginx, Caddy, Traefik) in front of the three services — or access them directly on 127.0.0.1:3000, :4000, :4001 for development.
With Redis (multi-instance scaling)¶
echo "REDIS_URL=redis://redis:6379" >> docker/.env
docker compose -f docker/docker-compose.yml --profile redis up --build
The @doable/shared/kv-store.js abstraction auto-switches from in-memory to Redis when REDIS_URL is set.
Image structure (docker/Dockerfile)¶
The Dockerfile is multi-stage. Targets:
base— Node 22 alpine + pnpm.deps— installs prod + dev deps for the workspace.build— runspnpm buildfor all packages.migrate— minimal image that runspnpm db:migrateand exits.api,ws,web— slim runtime images, one per service. Each runs as the unprivilegednodeuser.
This keeps individual service images small (~150–300 MB) while sharing all build steps.
Volumes¶
| Volume | Mounted in | Purpose |
|---|---|---|
postgres_data |
postgres |
Database files |
api_projects |
api |
User projects (filesystem-backed) |
api_thumbnails |
api |
Generated preview screenshots |
ws_projects |
ws |
Shared with API for collab access |
redis_data |
redis |
Redis persistence (when enabled) |
Back these up — see Backups.
Common operations¶
# Tail logs
docker compose -f docker/docker-compose.yml logs -f
docker compose -f docker/docker-compose.yml logs -f api
# Restart a service
docker compose -f docker/docker-compose.yml restart api
# Run migrations on demand
docker compose -f docker/docker-compose.yml run --rm migrate
# Open psql against the running database
docker compose -f docker/docker-compose.yml exec postgres \
psql -U doable -d doable
# Stop everything (data preserved)
docker compose -f docker/docker-compose.yml down
# Stop and DELETE all data
docker compose -f docker/docker-compose.yml down -v
Updating¶
cd doable
git pull
docker compose -f docker/docker-compose.yml up --build -d
docker compose -f docker/docker-compose.yml run --rm migrate
Why nginx in front?¶
- Single TLS termination point.
- Routes
/,/api/,/auth/,/wsto the right backend. - Lets you mix Doable with other apps on the same host.
- Required by browsers for HTTPS-only features.
The nginx template is at docker/nginx.conf.template. Edit and re-run setup.sh to regenerate.
→ Prefer non-Docker? See Script-based Setup. Want to install everything manually? See Manual Setup.