Skip to content

feat(deploy): production deployment scripts and CD pipeline (Closes #469) - #486

Merged
Nathydre merged 1 commit into
nathydre21:mainfrom
favvy0217:feat/production-deployment-cd-469
Aug 28, 2026
Merged

feat(deploy): production deployment scripts and CD pipeline (Closes #469)#486
Nathydre merged 1 commit into
nathydre21:mainfrom
favvy0217:feat/production-deployment-cd-469

Conversation

@favvy0217

Copy link
Copy Markdown
Contributor

What & why

Closes #469.

Adds a complete, production-grade deployment system for NEPA — container images for every service, a production Compose stack, a full CD pipeline, a health-gated rolling deploy with automatic rollback, testnet/mainnet config, and deploy notifications.

The design goal was a PR that is correct and green on day one, with zero infrastructure provisioned, yet flips to real deployments the moment secrets are added — no workflow edits required.

Acceptance criteria

# Requirement Where
1 Dockerfiles for backend, frontend, contract backend/Dockerfile, frontend/Dockerfile, contract/Dockerfile
2 Production Docker Compose docker-compose.prod.yml
3 CD pipeline for staging and production .github/workflows/cd.yml
4 Blue-green or rolling deployment strategy Rolling + health gate in deploy/deploy.sh (blue-green documented as the scale-out path)
5 Rollback mechanism Automatic in deploy/deploy.sh; manual deploy/rollback.sh + workflow_dispatch
6 Environment-specific config (testnet/mainnet) deploy/.env.staging.example (testnet), deploy/.env.production.example (mainnet)
7 Deployment notifications deploy/notify.sh (Slack + generic webhook), called by every deploy/rollback job

Images

All multi-stage; compilers and dev deps never reach the runtime layer.

  • backendnode:20-slim, prod-only deps, Prisma client generated in the builder stage, runs as a non-root user under tini, healthcheck on /health. slim (glibc) is deliberate: the backend has native addons that don't build cleanly on Alpine/musl.
  • frontend — Vite build → nginx:1.27-alpine serving the SPA and reverse-proxying /api + /socket.io (WebSocket upgrade) to the backend. /health proxies the backend for a true end-to-end gate; /healthz is nginx-local.
  • contract — Rust wasm32-unknown-unknown build that verifies the Soroban .wasm. Defaults to build (verify, no network); on-chain deploy is opt-in and gated on STELLAR_SECRET_KEY.

CD pipeline (.github/workflows/cd.yml)

  • Pull requests touching deploy paths run validation only: hadolint (3 Dockerfiles), shellcheck (all scripts), docker compose config, and actionlint. No build/push.
  • Push to main → build + push images to GHCR → deploy staging (Stellar testnet).
  • Push a v*.*.* tag → build all images (incl. contract) → deploy production (Stellar mainnet).
  • workflow_dispatch → manual deploy or rollback of a chosen environment/tag.

Host deploys run over SSH and are gated on the environment's *_SSH_HOST secret. With no infrastructure configured, deploy jobs fall back to a validated dry-run (compose config + the exact command that would run), so the pipeline is green and reviewable before any server exists. Concurrency is serialized per environment with cancel-in-progress: false so a deploy is never interrupted mid-roll.

Deployment strategy: rolling + automatic rollback

deploy/deploy.sh:

  1. Records the currently-running tag (from the nepa.image.tag container label).
  2. Pulls the new tag and docker compose up -d --wait (blocks on container healthchecks).
  3. Runs the repo's own backend/scripts/verify-deployment.sh against the end-to-end /health endpoint.
  4. On a failed health gate, automatically rolls back to the previous tag and re-verifies. Exit codes: 0 healthy, 1 rolled back (release rejected, service restored), 2 rollback also failed (page a human).

A bad image never survives the health gate, so a failed release is self-healing. Blue-green is documented in docs/DEPLOYMENT.md as the natural scale-out (two stacks behind the TLS proxy, flip after the gate) reusing the same bookkeeping.

Validation performed

  • bash -n on all four shell scripts — clean.
  • actionlint on the workflow — clean.
  • YAML parse of the workflow and compose file — clean.
  • hadolint / shellcheck / compose config also run in the PR's own validate job.

Enabling real deploys (no code changes)

Add per-environment secrets — STAGING_SSH_HOST/_USER/_KEY (+ STAGING_DEPLOY_PATH), the PRODUCTION_* equivalents, optional STELLAR_SECRET_KEY, and SLACK_WEBHOOK_URL/WEBHOOK_ALERT_URL. First-time host setup, the secrets table, and troubleshooting are in docs/DEPLOYMENT.md.

Note

backend/.env.example gains the missing DATABASE_URL required by the root Prisma schema (prisma generate/migrate and the server fail without it) — a small fix that unblocks a clean deploy.

Closes nathydre21#469

A complete, reviewable production deployment system for NEPA:

- Dockerfiles for all three services: backend (node, multi-stage,
  non-root, tini, /health healthcheck), frontend (vite build -> nginx
  SPA with /api + /socket.io reverse proxy), and the Soroban contract
  (rust wasm build + artifact verification).
- docker-compose.prod.yml: backend + frontend + postgres + redis with
  healthchecks, resource limits, named volumes, and an internal network;
  only the frontend edge port is published.
- .github/workflows/cd.yml: PRs lint the artifacts (hadolint, shellcheck,
  compose config, actionlint); pushes build & push images to GHCR and
  deploy to staging (testnet) on main / production (mainnet) on v*.*.*
  tags; workflow_dispatch drives manual deploy and rollback. Host deploys
  are gated on *_SSH_HOST secrets and fall back to a validated dry-run so
  the pipeline is green before any infrastructure exists.
- Rolling deployment with a health gate and automatic rollback
  (deploy/deploy.sh), plus a manual rollback path (deploy/rollback.sh).
- Environment-specific config templates for testnet and mainnet
  (deploy/.env.{staging,production}.example).
- Deployment notifications to Slack and/or a generic webhook
  (deploy/notify.sh); no-op when unconfigured.
- docs/DEPLOYMENT.md operational runbook (architecture, images, envs,
  rolling+rollback strategy, secrets, first-time setup, troubleshooting).
- Fix: add the missing DATABASE_URL to backend/.env.example, required by
  the root Prisma schema (blocks a clean deploy without it).
Comment thread .github/workflows/cd.yml
Comment on lines +112 to +122
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.image_tag }}" ]; then
tag="${{ github.event.inputs.image_tag }}"
elif [ "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
tag="${GITHUB_REF_NAME}"
else
tag="sha-${GITHUB_SHA::12}"
fi
echo "image_tag=${tag}" >> "$GITHUB_OUTPUT"
echo "Resolved image tag: ${tag}"

Comment thread frontend/nginx.conf
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
Comment thread frontend/nginx.conf
Comment on lines +57 to +59
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
Comment thread frontend/nginx.conf
location /api/ {
proxy_pass http://backend:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
Comment thread frontend/nginx.conf
location = /health {
proxy_pass http://backend:3001/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
Comment thread .github/workflows/cd.yml
run: echo "name=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_OUTPUT"

- name: Build and push
uses: docker/build-push-action@v6
Comment thread .github/workflows/cd.yml
- uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
Comment thread .github/workflows/cd.yml
steps:
- uses: actions/checkout@v4

- uses: docker/setup-buildx-action@v3
Comment thread .github/workflows/cd.yml
matrix:
service: [backend, frontend]
steps:
- uses: actions/checkout@v4
Comment thread .github/workflows/cd.yml
name: Validate deployment artifacts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@Nathydre
Nathydre merged commit bd89c91 into nathydre21:main Aug 28, 2026
16 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add production deployment scripts and CD pipeline

3 participants