Files
code-vorlage/.claude/skills/deploy/SKILL.md
T
Florian Wilde f432122abf feat: add a dedicated Security Auditor agent with veto power
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.
2026-08-09 01:22:05 +02:00

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
```