6.7 KiB
06 — Deployment
Purpose: Condensed infrastructure reference for LLMs working on deployment, CI/CD, or ops tasks. For full setup details (VPS provisioning, Forgejo configuration, backup scripts), see the human-readable DEPLOYMENT.md. Last updated: 2026-05-15 Depends on: 00-project-overview.md
Infrastructure Overview
Internet
↓
Caddy (Docker container, ports 80/443)
├── lilastudy.com → web container (nginx:alpine, static files)
├── api.lilastudy.com → api container (Express, port 3000)
└── git.lilastudy.com → forgejo container (git + registry, port 3000)
SSH (port 2222) → forgejo container (git push/pull)
VPS: Hetzner, Debian 13, ARM64 (aarch64), 4GB RAM
Domain: lilastudy.com, wildcard *.lilastudy.com configured
Only Caddy faces the internet. All other services communicate over internal Docker network.
Docker Compose Stack
Services on shared lila-network:
| Service | Image | Ports (internal) | Notes |
|---|---|---|---|
| caddy | caddy:alpine | 80, 443 | Only container with published ports |
| api | git.lilastudy.com/forgejo-lila/lila-api:latest |
3000 | Multi-stage Dockerfile, runs migrations on startup |
| web | git.lilastudy.com/forgejo-lila/lila-web:latest |
80 | nginx:alpine, SPA fallback via try_files |
| database | postgres:16 | 5432 | Named volume lila-db for persistence |
| forgejo | forgejo:... | 3000, 2222 | Git + container registry, SSH on 2222 |
No ports exposed on internal services. Only Caddy (80/443) and Forgejo SSH (2222) are public.
Build & Deploy Flow
Dev laptop: git push to main
↓
Forgejo Actions triggers (runner on VPS)
↓
Build API image (target: runner)
Build Web image (target: production, VITE_API_URL baked in)
↓
Push both to git.lilastudy.com registry
↓
SSH into VPS, docker compose pull, restart containers
↓
API container runs migrations on startup (migrate.js before server.js)
↓
App updated (~2–5 min total)
Cross-compilation: Images built natively on ARM64 VPS (no QEMU). Dev laptop used for initial pushes before CI/CD was set up.
Environment-Driven Config
Same code runs in dev and production. Environment variables control behavior:
| Variable | Dev | Production |
|---|---|---|
DATABASE_URL |
postgres://...@localhost:5432/lila |
postgres://...@database:5432/lila |
BETTER_AUTH_URL |
http://localhost:3000 |
https://api.lilastudy.com |
CORS_ORIGIN |
http://localhost:5173 |
https://lilastudy.com |
COOKIE_DOMAIN |
undefined | .lilastudy.com |
VITE_API_URL |
http://localhost:3000 |
https://api.lilastudy.com (baked at build time) |
Note: VITE_API_URL is baked into the frontend at Docker build time via --build-arg. It cannot be changed at runtime.
Database
Migrations
Drizzle migrations run automatically on API container startup. The Dockerfile entrypoint:
CMD ["node", "dist/src/migrate.js", "&&", "node", "dist/src/server.js"]
Deploy order enforced automatically: migrations before server starts.
Backups
- Daily cron job at 3:00 AM:
pg_dump→ compressed SQL →~/backups/ - 7-day retention on VPS
- Dev laptop auto-syncs new backups on login via
rsync - Offsite storage: Planned (Hetzner Object Storage or S3-compatible)
Seeding
Idempotent (onConflictDoNothing). Safe to re-run for adding new languages without affecting existing data or user tables.
Auth & OAuth
Better Auth embedded in Express API. No separate auth service.
Social providers:
- Google OAuth — consent screen in testing mode (100 user cap). Must publish before reaching 80 users.
- GitHub OAuth — configured for both dev and production redirect URIs
Cross-subdomain cookies: COOKIE_DOMAIN=.lilastudy.com (leading dot) makes auth cookie valid across all subdomains.
Known Issues & Limitations
| Issue | Impact | Status |
|---|---|---|
| lila-web has no healthcheck | Vite dev server has no health endpoint; depends_on uses API healthcheck as proxy |
Acceptable for dev |
| Valkey memory overcommit warning | Harmless in dev. Fix before production: vm.overcommit_memory = 1 |
Documented |
| No centralized monitoring/logging | No uptime alerts or log aggregation on VPS | Planned (BACKLOG.md) |
| Backups only on VPS + dev laptop | No offsite protection against VPS failure | Planned (BACKLOG.md) |
| Google OAuth in testing mode | 100 user cap | Must publish before 80 users |
Key Files
| File | Purpose |
|---|---|
docker-compose.yml (root) |
Local dev stack |
docker-compose.yml (VPS) |
Production stack |
apps/api/Dockerfile |
Multi-stage: deps → dev → builder → runner |
apps/web/Dockerfile |
Multi-stage: deps → dev → builder → production (nginx) |
apps/web/nginx.conf |
SPA fallback routing |
Caddyfile |
Reverse proxy routing, automatic HTTPS |
.forgejo/workflows/deploy.yml |
CI/CD pipeline |
apps/api/src/migrate.ts |
Drizzle migration runner |