Template
Toolchain fixes (all previously broken): - npm run lint: next lint was removed in Next 16 -> eslint with flat config - .eslintrc.json was ignored by ESLint 9 -> eslint.config.mjs - npm test / test:e2e exited 1 with no tests -> passWithNoTests - Vitest scanned tests/ and would have run Playwright specs -> scoped to src/ - Playwright targeted Mobile Safari while docs installed only chromium - reuseExistingServer silently tested a foreign server on :3000 -> E2E_PORT - 11 high-severity vulns -> npm audit fix - deny rule Read(**/.env*) also blocked .env.example -> precise patterns Stack changes: - Tailwind v3 -> v4 (CSS-first, no tailwind.config.ts, tw-animate-css) - shadcn regenerated for v4; 33 components -> 6 core, bundled radix-ui - Supabase removed entirely; data layer is now chosen per project - Vercel removed; Docker Compose self-hosting with scripts/update.sh - Better Auth + passkey plugin as the binding auth standard - ThemeProvider and Sonner mounted; dark mode and toasts now actually work Workflow system: - Sub-agents wired: /frontend, /backend, /qa delegate to .claude/agents - Checklists folded into their SKILL.md; one file per skill - Rules rewritten: DB-agnostic backend, passkey security, v4 frontend - Spec language settled as German, skill instructions English
125 lines
3.9 KiB
Markdown
125 lines
3.9 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.
|
|
|
|
## 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
|
|
|
|
## 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
|
|
```
|