Production Deployment
Summary
SeaLion has no git on the production server — deployments are done via rsync from the developer’s local machine to the Hetzner VPS (root@46.225.120.4, app path /opt/sealion). The build-sealion skill orchestrates the full process: local build, rsync, remote install + migrate + build, PM2 restart, health check, and Slack notification. The API .env on the server is never touched by rsync. NEXT_PUBLIC_API_URL must be set at build time (not runtime) as it is inlined into the Next.js client bundle.
State Diagram
stateDiagram-v2 state "Build local" as LocalBuild state "Erreur de build" as BuildFailed state "Transfert vers serveur" as Rsync state "Installation distante" as RemoteSetup state "Services redémarrés" as Restarted state "Vérification santé" as HealthCheck state "Déploiement en échec" as Failed state "Slack notifié" as SlackNotified [*] --> LocalBuild: npx turbo build (local) LocalBuild --> BuildFailed: errors → fix and retry LocalBuild --> Rsync: rsync to root@46.225.120.4:/opt/sealion Rsync --> RemoteSetup: SSH — pnpm install + prisma migrate deploy + prisma generate + web build RemoteSetup --> Restarted: pm2 restart sealion-api sealion-web Restarted --> HealthCheck: curl http://localhost:4010/health HealthCheck --> Failed: non-200 → check pm2 logs, diagnose, re-deploy HealthCheck --> SlackNotified: deploy-notify.sh with commit hash + message SlackNotified --> [*]
Steps
1. Local Build (Actor: admin)
npx turbo build — builds all packages in the monorepo. If the build fails, errors must be fixed and the build retried before proceeding. This step validates that the code compiles and Next.js pages render without errors.
Outcome: .next build artifacts and API dist ready locally
2. Rsync to Server (Actor: admin, tools: rsync)
rsync -avz \
--exclude='node_modules' --exclude='.next' --exclude='dist' \
--exclude='.superpowers' --exclude='.claude' \
--exclude='.env' --exclude='allowed-image-domains.json.prev' \
/Users/alexisdahan/project-sealion/ root@46.225.120.4:/opt/sealion/
Key exclusions:
node_modules: reinstalled on server withpnpm install..next/dist: rebuilt on server with correct env vars..env: server has production secrets, never overwritten.allowed-image-domains.json.prev: server-side tracking file for nightly rebuild.
Outcome: Source code synced to /opt/sealion on the VPS
3. Remote Install, Migrate, Build (Actor: system)
Single SSH call:
cd /opt/sealion
pnpm install --frozen-lockfile
cd apps/api
npx prisma migrate deploy # no-op if no pending migrations
npx prisma generate # regenerates client after schema changes
cd /opt/sealion
NEXT_PUBLIC_API_URL=https://sealion.cacio.fr/api npx turbo build --filter=@sealion/webprisma migrate deploy + prisma generate are always run — skipping risks deploying code that references new DB fields against an old Prisma client.
NEXT_PUBLIC_API_URL must be passed here, not just in PM2 env, because Next.js inlines it into the client bundle at build time.
Outcome: Dependencies installed, DB migrated, Prisma client regenerated, frontend built with production API URL
4. PM2 Restart and Health Check (Actor: system, tools: pm2)
pm2 restart sealion-api sealion-web && sleep 2 && curl -s http://localhost:4010/health
If health check returns non-200: inspect pm2 logs sealion-api --lines 30 --nostream, diagnose the issue, fix, and re-deploy.
Service ports:
- API (Fastify):
localhost:4010→ proxied via Nginx assealion.cacio.fr/api/ - Web (Next.js):
localhost:3010→ proxied via Nginx assealion.cacio.fr
Outcome: Both processes running with new code, health endpoint confirmed
5. Slack Notification (Actor: system, tools: slack)
# On developer's machine:
COMMIT_HASH=$(git rev-parse HEAD)
COMMIT_MSG=$(git log -1 --pretty=%s)
# On server:
export $(grep -E '^SLACK_(BOT_TOKEN|DEPLOYS_CHANNEL_ID)=' /opt/sealion/apps/api/.env | xargs)
bash /opt/sealion/scripts/server/deploy-notify.sh "$COMMIT_HASH" "$COMMIT_MSG"deploy-notify.sh posts to #deploys: "🚀 Déploiement en production — {date}\n{shortHash} — {commitMessage}".
Slack credentials sourced from server .env — only the two SLACK_* vars are exported to avoid issues with other env vars.
Outcome: Deployment recorded in #deploys with commit reference
Server Architecture
| Component | Port | PM2 Process | Nginx |
|---|---|---|---|
| Frontend (Next.js) | 3010 | sealion-web | sealion.cacio.fr/ |
| API (Fastify) | 4010 | sealion-api | sealion.cacio.fr/api/ |
| Database | 5432 | PostgreSQL 16 + PostGIS | — |
Error States
- Local build fails → fix errors, retry from step 1
- Rsync SSH connection fails → check VPN/firewall, retry
pnpm installfails on server → check lockfile sync, may need--no-frozen-lockfiletemporarily- Prisma migration fails → review migration, may need manual DB backup + remediation
- Next.js build fails on server → check server logs, usually an env var or dependency issue
- Health check fails after restart → inspect
pm2 logs, rollback by re-deploying previous commit
Related Processes
- nightly-image-rebuild — automated partial rebuild for new image domains (no manual deploy needed)