Template
Security was one section among seven in the QA Engineer. That is structurally weak: the agent that wants to recommend "production ready: YES" also judged security, and as a subsection it competes for attention with acceptance criteria, regression and test authoring. The new agent: - has no Write/Edit tools, so "never fix what you find" is enforced by the tool list rather than by instruction — it cannot launder a finding into a patch - reports PASS / FAIL / UNVERIFIED, and UNVERIFIED must reach the spec; an audit that omits what it could not check reads as all-clear - audits against .claude/rules/security.md, the same standard the developer agents are held to, so every mandatory item gets a verdict - holds a veto: BLOCK stops /deploy regardless of functional test results /qa now runs both agents in parallel and keeps the verdicts separate. The QA Engineer is explicitly forbidden from speaking for security.
142 lines
4.7 KiB
Markdown
142 lines
4.7 KiB
Markdown
---
|
|
name: deploy
|
|
description: Deploy the Docker Compose stack to a server, with pre-flight checks and rollback.
|
|
argument-hint: "PROJ-X or 'production'"
|
|
user-invocable: true
|
|
---
|
|
|
|
# Deploy
|
|
|
|
Self-hosted Docker Compose. There is no Vercel, no managed platform. The stack runs
|
|
one app container; TLS and routing are handled by a reverse proxy already present
|
|
on the host.
|
|
|
|
## 1. Gate
|
|
|
|
1. Read `features/INDEX.md` — what is being shipped
|
|
2. Read the feature spec's QA section
|
|
|
|
**No QA section:** → "Für PROJ-X gibt es noch kein QA-Ergebnis. Führe erst `/qa` aus." → Stop.
|
|
**Critical or High bugs open:** → name them and stop. Those get fixed first.
|
|
**Security verdict is BLOCK, or missing entirely:** → stop. A feature without a
|
|
Security Auditor verdict has not been security-reviewed, no matter how green the
|
|
functional tests are.
|
|
**UNVERIFIED security items:** → list them and ask the user explicitly whether to
|
|
ship with that surface unchecked. Do not decide this silently on their behalf.
|
|
|
|
## 2. Pre-flight
|
|
|
|
Run and show the results — do not proceed on a failure:
|
|
|
|
```bash
|
|
npm run lint
|
|
npm run typecheck
|
|
npm test
|
|
npm run build
|
|
```
|
|
|
|
Then check:
|
|
- [ ] Every env var used in code is in `.env.example` with a dummy value
|
|
- [ ] No secrets in the diff (`git diff --cached`)
|
|
- [ ] Pending migrations are committed
|
|
- [ ] Everything committed and pushed to the remote the server pulls from
|
|
|
|
**Port audit — blocking.** Only the app service may publish a port, and only on
|
|
loopback:
|
|
|
|
```bash
|
|
docker compose config | grep -B8 "published:"
|
|
```
|
|
|
|
Any service other than `app` with a published port, or any binding to `0.0.0.0`,
|
|
stops the deploy. A published database port puts the data on the public internet and
|
|
bypasses ufw, because Docker writes its own iptables rules. Fix `compose.yaml`
|
|
before continuing.
|
|
|
|
## 3. First deployment only
|
|
|
|
Walk the user through this once. It happens on the server, over SSH.
|
|
|
|
**Prerequisites the user provides manually:** a server with Docker and the Compose
|
|
plugin, a reverse proxy (nginx, Caddy, Traefik) terminating TLS, and a DNS record
|
|
pointing at it.
|
|
|
|
```bash
|
|
git clone <repo-url> /srv/<app-name> && cd /srv/<app-name>
|
|
cp .env.example .env
|
|
```
|
|
|
|
Then edit `.env`:
|
|
- `NEXT_PUBLIC_APP_URL` — the real public URL. **If auth is used, this must match
|
|
the domain exactly**: the WebAuthn relying-party ID comes from this hostname, and
|
|
a mismatch makes every passkey fail without a useful error.
|
|
- `APP_PORT` — the port the reverse proxy forwards to
|
|
- `APP_BIND` — leave at `127.0.0.1` so the container is not exposed directly
|
|
- `BETTER_AUTH_SECRET` — `openssl rand -base64 32`, unique per environment
|
|
|
|
```bash
|
|
docker compose up -d --build
|
|
curl -fsS http://127.0.0.1:${APP_PORT:-3000}/api/health
|
|
```
|
|
|
|
Point the reverse proxy at `127.0.0.1:$APP_PORT` and make sure it forwards
|
|
`X-Forwarded-Proto` and `X-Forwarded-Host`, otherwise redirects and WebAuthn origin
|
|
checks break.
|
|
|
|
Passkeys need HTTPS in production — confirm TLS works before testing auth.
|
|
|
|
## 4. Subsequent deployments
|
|
|
|
On the server:
|
|
|
|
```bash
|
|
./scripts/update.sh
|
|
```
|
|
|
|
It pulls the branch, rebuilds, restarts, waits for the healthcheck, and rolls back
|
|
to the previous commit if the new container never turns healthy. `--force` rebuilds
|
|
without a new commit; `--branch <name>` deploys a different branch.
|
|
|
|
If a data layer exists, migrations run before the new container serves traffic —
|
|
see `docs/DEPLOYMENT.md`.
|
|
|
|
## 5. Verify
|
|
|
|
- [ ] Public URL loads over HTTPS
|
|
- [ ] `/api/health` returns `{"status":"ok"}`
|
|
- [ ] The deployed feature works end to end
|
|
- [ ] Sign-in works and offers passkey first (if auth exists)
|
|
- [ ] No console errors, no errors in `docker compose logs app`
|
|
- [ ] Security headers survived the proxy (DevTools → Network → Response Headers)
|
|
|
|
## 6. Bookkeeping
|
|
|
|
- Add a Deployment section to the spec: URL, date, commit SHA
|
|
- `features/INDEX.md` → **Deployed** (write, then re-read to verify)
|
|
- Tag: `git tag -a v1.X.0-PROJ-X -m "Deploy PROJ-X: [name]"` and push it
|
|
|
|
## Rollback
|
|
|
|
`scripts/update.sh` rolls back automatically on an unhealthy container. Manually:
|
|
|
|
```bash
|
|
git reset --hard <previous-sha>
|
|
docker compose up -d --build
|
|
```
|
|
|
|
## Common failures
|
|
|
|
| Symptom | Cause |
|
|
|---------|-------|
|
|
| Build works locally, fails in Docker | A dependency sits in devDependencies but is needed at build time |
|
|
| Env var undefined at runtime | Not in `.env` on the server, or missing `NEXT_PUBLIC_` for client use |
|
|
| Container healthy, proxy returns 502 | Proxy points at the wrong port, or `APP_BIND` is not reachable from it |
|
|
| Passkeys fail after deploy | `NEXT_PUBLIC_APP_URL` does not match the real domain, or TLS is missing |
|
|
| Redirect loop | Proxy not forwarding `X-Forwarded-Proto` |
|
|
|
|
## Commit
|
|
|
|
```
|
|
deploy(PROJ-X): Deploy [feature name] to production
|
|
```
|