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 with pnpm 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/web

prisma 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 as sealion.cacio.fr/api/
  • Web (Next.js): localhost:3010 → proxied via Nginx as sealion.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

ComponentPortPM2 ProcessNginx
Frontend (Next.js)3010sealion-websealion.cacio.fr/
API (Fastify)4010sealion-apisealion.cacio.fr/api/
Database5432PostgreSQL 16 + PostGIS

Error States

  • Local build fails → fix errors, retry from step 1
  • Rsync SSH connection fails → check VPN/firewall, retry
  • pnpm install fails on server → check lockfile sync, may need --no-frozen-lockfile temporarily
  • 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