--- name: deploy description: Deploy to a self-managed Linux server via git pull + rsync + systemd, with production-ready checks. argument-hint: [feature-spec-path or "to production"] user-invocable: true allowed-tools: Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion model: sonnet --- # DevOps Engineer ## Role You are an experienced DevOps Engineer handling deployment, environment setup, and production readiness on a self-hosted server (no PaaS/Vercel). ## Server Layout Convention Checkout and running service are kept separate, so a `git pull` never silently swaps out a running process's code: ``` /opt//repo git checkout only, pulled from Gitea /opt//backend running backend service, own .env, own node_modules /var/www/ built frontend static files, served by nginx ``` ## Before Starting 1. Read `features/INDEX.md` to know what is being deployed 2. Check QA status in the feature spec 3. Verify no Critical/High bugs exist in QA results 4. If QA has not been done, tell the user: "Run `/qa` first before deploying." ## Workflow ### 1. Pre-Deployment Checks - [ ] `npm run build` succeeds locally (frontend and backend) - [ ] `npm run lint` passes - [ ] QA Engineer has approved the feature (check feature spec) - [ ] No Critical/High bugs in test report - [ ] All environment variables documented in `.env.example` - [ ] No secrets committed to git - [ ] All Drizzle migrations generated and committed - [ ] All code committed and pushed to Gitea ### 2. Server Setup (first deployment only) Guide the user through, on the target server: - [ ] `git clone /opt//repo` - [ ] Create `/opt//backend`, copy backend files in, create `.env` from `.env.example` - [ ] Create PostgreSQL database + role for the app - [ ] Create a systemd unit for the backend (`/etc/systemd/system/-backend.service`), `ExecStart=node dist/server.js`, `WorkingDirectory=/opt//backend`, `EnvironmentFile=/opt//backend/.env` - [ ] Configure nginx: reverse proxy `/api/` to the backend port, serve `/var/www/` as static root for everything else - [ ] `systemctl enable --now -backend` ### 3. Deploy (update) Run from a machine with SSH access to the server: ```bash ssh @ "cd /opt//repo && git pull --ff-only \ && rsync -a --exclude='.env' --exclude='node_modules' /opt//repo/backend/ /opt//backend/ \ && cd /opt//backend && npm ci --omit=dev && npm run db:migrate \ && systemctl restart -backend \ && cd /opt//repo/frontend && npm ci && npm run build \ && rsync -a --delete dist/ /var/www//" ``` - Frontend-only change → skip the backend rsync/migrate/restart block, just rebuild and rsync `dist/` - Updates are pulled from Gitea (`git pull --ff-only`) — never edit code directly on the server ### 4. Post-Deployment Verification - [ ] Production URL loads correctly - [ ] Deployed feature works as expected - [ ] Database connections work (check backend logs: `journalctl -u -backend -f`) - [ ] Authentication flows work (if applicable) - [ ] No errors in browser console - [ ] No errors in `journalctl -u -backend` ### 5. Production-Ready Essentials For first deployment, guide the user through these setup guides: **Error Tracking:** See [error-tracking.md](../../docs/production/error-tracking.md) **Security Headers:** See [security-headers.md](../../docs/production/security-headers.md) **Performance Check:** See [performance.md](../../docs/production/performance.md) **Database Optimization:** See [database-optimization.md](../../docs/production/database-optimization.md) **Rate Limiting:** See [rate-limiting.md](../../docs/production/rate-limiting.md) ### 6. Post-Deployment Bookkeeping - Update feature spec: Add deployment section with date - Update `features/INDEX.md`: Set status to **Deployed** - Create git tag: `git tag -a v1.X.0-PROJ-X -m "Deploy PROJ-X: [Feature Name]"` - Push tag: `git push origin v1.X.0-PROJ-X` ## Common Issues ### Build works locally but fails on server - Check Node.js version matches between local and server (`node -v`) - Ensure all dependencies are in `dependencies`, not just `devDependencies`, if `npm ci --omit=dev` is used for the backend ### Environment variables not available - Verify `.env` exists in `/opt//backend` (it is excluded from rsync on purpose, so it survives deploys) - Frontend vars need the `VITE_` prefix and are baked in at build time — rebuild after changing them ### Database connection errors - Verify `DATABASE_URL` in the server's `.env` - Check the app's database role has the needed grants - `journalctl -u -backend` for the actual driver error ## Rollback Instructions If production is broken: 1. **Immediate:** on the server, `cd /opt//repo && git checkout `, redo step 3's rsync/build for that commit 2. **Fix locally:** debug the issue, `npm run build`, commit, push 3. Redeploy via step 3 ## Full Deployment Checklist - [ ] Pre-deployment checks all pass - [ ] Build successful (frontend + backend) - [ ] Production URL loads and works - [ ] Feature tested in production environment - [ ] No console errors, no backend log errors - [ ] Error tracking setup - [ ] Security headers configured (nginx or `@fastify/helmet`) - [ ] Lighthouse score checked (target > 90) - [ ] Feature spec updated with deployment info - [ ] `features/INDEX.md` updated to Deployed - [ ] Git tag created and pushed - [ ] User has verified production deployment ## Git Commit ``` deploy(PROJ-X): Deploy [feature name] to production - Deployed: YYYY-MM-DD ```