Task d45cc3d2-fc99-4d8a-a9b0-7b3f55b53261 (Feature 17 - API Deployment and Identity).
Why
The API deployable had no packaging of its own: its runtime deps lived only in the integration's dev group, so there was no way to deploy the service without dragging the test venv along.
What
api/requirements.txt — the API deployable's OWN runtime manifest (fastapi, uvicorn, pyjwt[crypto], httpx; per the pyproject comment these must not live in the integration's groups).
Dockerfile (repo-root build context) — multi-stage python:3.12-slim, no toolchain left behind, non-root user, serves uvicorn --factory api.app:create_app; carries api/ and the bundled custom_components/nestquest/core/ (loaded by file location).
deploy/compose.yaml — SQLite file /var/lib/nestquest/nestquest.db on the NAMED VOLUME nestquest-db; port bound to localhost only (TLS/reverse proxy, Authentik, network separation are later tasks).
deploy/.env.example (committed, placeholder names with empty values) → real deploy/.env is git-ignored; .dockerignore keeps the build context minimal; .gitignore also ignores .env, *.db, WAL sidecars.
WRITE inside the running container via the bundled core: create_child(db, "Persistence Probe Child") → WROTE child id=1 name='Persistence Probe Child'.
docker compose down (container removed, volume kept) → up -d (new container healthy).
READ BACK via ChildrenDao.list_all() in the recreated container → READ BACK child id=1 name='Persistence Probe Child' active=True.
READ BACK over HTTP through the recreated API: GET /api/v1/panel/snapshot → child_id: 1, child_name: "Persistence Probe Child".
Tests
TZ=UTC uv run pytest -q → 1711 passed in 27.72s (baseline exact match).
Task d45cc3d2-fc99-4d8a-a9b0-7b3f55b53261 (Feature 17 - API Deployment and Identity).
## Why
The API deployable had no packaging of its own: its runtime deps lived only in the integration's dev group, so there was no way to deploy the service without dragging the test venv along.
## What
- `api/requirements.txt` — the API deployable's OWN runtime manifest (fastapi, uvicorn, pyjwt[crypto], httpx; per the pyproject comment these must not live in the integration's groups).
- `Dockerfile` (repo-root build context) — multi-stage `python:3.12-slim`, no toolchain left behind, non-root user, serves `uvicorn --factory api.app:create_app`; carries `api/` and the bundled `custom_components/nestquest/core/` (loaded by file location).
- `deploy/compose.yaml` — SQLite file `/var/lib/nestquest/nestquest.db` on the NAMED VOLUME `nestquest-db`; port bound to localhost only (TLS/reverse proxy, Authentik, network separation are later tasks).
- `deploy/.env.example` (committed, placeholder names with empty values) → real `deploy/.env` is git-ignored; `.dockerignore` keeps the build context minimal; `.gitignore` also ignores `.env`, `*.db`, WAL sidecars.
- `api/README.md` — short deploy scaffold doc.
## Done-condition proof (real Docker)
1. `docker build -t nestquest-api:latest .` → image built.
2. `docker compose -f deploy/compose.yaml up -d --build` → healthy; `curl /health` → `{"status":"ok"}`.
3. WRITE inside the running container via the bundled core: `create_child(db, "Persistence Probe Child")` → `WROTE child id=1 name='Persistence Probe Child'`.
4. `docker compose down` (container removed, volume kept) → `up -d` (new container healthy).
5. READ BACK via `ChildrenDao.list_all()` in the recreated container → `READ BACK child id=1 name='Persistence Probe Child' active=True`.
6. READ BACK over HTTP through the recreated API: `GET /api/v1/panel/snapshot` → `child_id: 1, child_name: "Persistence Probe Child"`.
## Tests
`TZ=UTC uv run pytest -q` → `1711 passed in 27.72s` (baseline exact match).
Secrets: nothing committed — `deploy/.env` ignored (`git check-ignore` → `.gitignore:13`), example carries placeholder names only.
The API deployable had no packaging of its own: its runtime deps lived
only in the integration's dev group, so there was no way to deploy the
service without dragging the test venv along. Package the API as a
container image (Dockerfile, multi-stage slim build, non-root) with the
deployable's own manifest api/requirements.txt, and run it via
deploy/compose.yaml with the SQLite file on the named volume
nestquest-db so recreating the container keeps household data.
Secrets are supplied by the environment (ignored deploy/.env, example
with placeholder names only). TLS/reverse proxy, Authentik wiring and
network separation are later Feature 17 tasks; the port is bound to
localhost until they land.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Task d45cc3d2-fc99-4d8a-a9b0-7b3f55b53261 (Feature 17 - API Deployment and Identity).
Why
The API deployable had no packaging of its own: its runtime deps lived only in the integration's dev group, so there was no way to deploy the service without dragging the test venv along.
What
api/requirements.txt— the API deployable's OWN runtime manifest (fastapi, uvicorn, pyjwt[crypto], httpx; per the pyproject comment these must not live in the integration's groups).Dockerfile(repo-root build context) — multi-stagepython:3.12-slim, no toolchain left behind, non-root user, servesuvicorn --factory api.app:create_app; carriesapi/and the bundledcustom_components/nestquest/core/(loaded by file location).deploy/compose.yaml— SQLite file/var/lib/nestquest/nestquest.dbon the NAMED VOLUMEnestquest-db; port bound to localhost only (TLS/reverse proxy, Authentik, network separation are later tasks).deploy/.env.example(committed, placeholder names with empty values) → realdeploy/.envis git-ignored;.dockerignorekeeps the build context minimal;.gitignorealso ignores.env,*.db, WAL sidecars.api/README.md— short deploy scaffold doc.Done-condition proof (real Docker)
docker build -t nestquest-api:latest .→ image built.docker compose -f deploy/compose.yaml up -d --build→ healthy;curl /health→{"status":"ok"}.create_child(db, "Persistence Probe Child")→WROTE child id=1 name='Persistence Probe Child'.docker compose down(container removed, volume kept) →up -d(new container healthy).ChildrenDao.list_all()in the recreated container →READ BACK child id=1 name='Persistence Probe Child' active=True.GET /api/v1/panel/snapshot→child_id: 1, child_name: "Persistence Probe Child".Tests
TZ=UTC uv run pytest -q→1711 passed in 27.72s(baseline exact match).Secrets: nothing committed —
deploy/.envignored (git check-ignore→.gitignore:13), example carries placeholder names only.Independent Claude Sonnet 5 review completed for this exact commit.