# 15 — Build, Deployment & Ops Two supported deployment paths from day one: **bare binary** (user supplies Postgres/Meilisearch/SMTP via env) and **reference docker-compose**. One artifact serves both: a single Go binary with the SPA embedded. ## Build pipeline - [ ] `make build`: `cd web && pnpm install && pnpm build` → `go build -o solopm ./cmd/solopm` with `//go:embed web/dist` (embed directive lives in a package that fails the build if `web/dist` is missing) - [ ] Version stamped via `-ldflags "-X main.version=$(git describe)"`; shown in `/healthz` and a footer - [ ] `make dev`: Vite dev server (proxying `/api`) + `go run` with a file-watcher (air or watchexec) - [ ] CI recipe (GitHub Actions or equivalent): lint (golangci-lint, eslint), `go test ./...`, frontend build, binary artifact - [ ] Release: multi-arch binaries (linux/amd64, linux/arm64, darwin/arm64) + a container image (distroless or alpine, non-root user, `UPLOAD_DIR` volume) ## Runtime - [ ] CLI subcommands on the same binary: `solopm serve` (default), `solopm migrate up|down`, `solopm reindex`, `solopm cull [--dry-run]` - [ ] `serve` runs pending migrations at startup by default (`--no-migrate` to disable) - [ ] First-run bootstrap: empty users table (beyond seeds) → registration page advertises that the first account becomes admin ([05-auth.md](05-auth.md)) - [ ] `/healthz`: 200 when DB reachable; reports meilisearch/smtp as ok/absent (absent is not unhealthy) - [ ] Full env var reference lives in [01-architecture.md](01-architecture.md); ship `.env.example` with every variable, commented ## docker-compose (reference) - [ ] `docker-compose.yml` services: - [ ] `app` — the SoloPM image; ports `4080`; env wired to the two services; volumes: `uploads:/data/uploads`; depends_on healthy postgres + meilisearch - [ ] `postgres` — postgres:16-alpine; volume `pgdata`; healthcheck `pg_isready` - [ ] `meilisearch` — getmeili/meilisearch; volume `meilidata`; `MEILI_MASTER_KEY` from env - [ ] (dev profile) `mailhog` — SMTP capture for local testing - [ ] `docker compose up` from a fresh clone + `.env` copied from `.env.example` reaches the login page with zero other steps - [ ] Compose file pins image versions; README documents upgrade = pull new app image, migrations run on start ## Reverse proxy & SSE notes - [ ] Document (README/ops doc): disable proxy buffering for `/api/v1/stream` (nginx `proxy_buffering off`, `X-Accel-Buffering: no` is already sent); read timeout ≥ 60s; `client_max_body_size` ≥ `MAX_UPLOAD_MB` - [ ] Cookie `Secure` requires https at the proxy; set `BASE_URL` accordingly ## Backup & restore - [ ] Documented procedure: `pg_dump` + tar of `UPLOAD_DIR` = complete backup (Meilisearch is derived — rebuild with `solopm reindex`) - [ ] Restore drill documented and tested once: restore dump, restore uploads, start app, reindex ## Operational acceptance - [ ] Fresh `docker compose up` → register → create project → create issue → board drag works, all on first try following only the README - [ ] Bare-binary path verified against external Postgres + Meilisearch using only `.env.example` guidance - [ ] Binary restarts cleanly under systemd example unit (provided in ops doc)