Files
edr-platform/DEPLOYMENT.md
2026-07-02 22:42:15 +03:00

8.3 KiB

Deployment Runbook

This document explains how deployments work for the EDR platform using Docker, GitHub Actions, and self-hosted runners.

Overview

  • Monorepo contains 6 deployable services:
    • freight-api
    • freight-portal
    • freight-backoffice
    • passenger-api
    • passenger-portal
    • passenger-backoffice
  • Deployments run through one workflow: .github/workflows/deploy.yml
  • Each service is built/deployed independently in parallel (matrix jobs).
  • Docker Compose project names are branch-aware to avoid environment collisions on the same host.

Prerequisites

  • Docker Engine with Compose plugin on the self-hosted runner.
  • GitHub self-hosted runner registered for this repository.
  • Repository secret configured:
    • NPM_TOKEN (for private @tria-plc/* package install during Docker build)
  • Server-side env files created for each branch/environment.

Server Environment Files

sync-env-from-server.sh reads env files from:

/home/<DEPLOY_USER>/environment/edr/<branch-slug>/<project>/

Where:

  • <DEPLOY_USER> defaults to tria (overridable by DEPLOY_USER)
  • <branch-slug> is derived from Git branch (lowercase, non-alphanumeric replaced with -)
  • <project> is edr-freight or edr-passenger

Required files per project

For edr-freight:

  • freight-api.env
  • freight-portal.env
  • freight-backoffice.env
  • optional: freight-web.build.env

For edr-passenger:

  • passenger-api.env
  • passenger-portal.env
  • passenger-backoffice.env
  • optional: passenger-web.build.env

Required env key

Each service env file must contain:

  • PORT=<number>

The sync script validates this and fails if missing.

Build env files (optional)

Used for additional build-time variables (example: Vite API URL for freight web), with export syntax:

export FREIGHT_VITE_API_URL=https://freight-api.example.com/api

These are injected into GITHUB_ENV during workflow execution.

Passenger web: NEXT_PUBLIC_API_URL does not need a separate build env file. Place it directly in the service runtime env file (passenger-portal.env / passenger-backoffice.env) and the sync script will forward it to the build automatically.

Docker Compose Port Mapping

docker-compose.yaml uses per-service env variables for host/container port mappings:

  • FREIGHT_API_PORT
  • PASSENGER_API_PORT
  • FREIGHT_PORTAL_PORT
  • FREIGHT_BACKOFFICE_PORT
  • PASSENGER_PORTAL_PORT
  • PASSENGER_BACKOFFICE_PORT

scripts/deploy/sync-env-from-server.sh extracts PORT from each synced .env and exports the corresponding *_PORT variable to GITHUB_ENV.

Passenger Web Docker Configuration

The passenger web apps (portal and backoffice) are deployed as Next.js applications using a dedicated Dockerfile:

  • Dockerfile: infrastructure/docker/Dockerfile.passenger-web
  • Apps: apps/edr-passenger-web/portal and apps/edr-passenger-web/backoffice

Key differences from freight-web

Aspect Freight Web Passenger Web
Framework Vite (SPA) Next.js (SSR/SSG)
Deployment Static export + nginx Node.js server
Dockerfile Dockerfile.web Dockerfile.passenger-web
Final port (container) 80 (nginx) driven by PORT in service .env
Build arg TURBO_FILTER APP_PACKAGE + APP_PATH + NEXT_PUBLIC_API_URL

Build arguments

The Dockerfile accepts the following build args:

  • APP_PACKAGE: Turbo package filter (e.g., @edr/passenger-portal)
  • APP_PATH: App directory path (e.g., apps/edr-passenger-web/portal)
  • NEXT_PUBLIC_API_URL: API URL visible to browser — sourced from NEXT_PUBLIC_API_URL in the service .env file

Port mapping

Both host and container ports are driven by PORT in the service env file. The sync script reads PORT, exports PASSENGER_PORTAL_PORT / PASSENGER_BACKOFFICE_PORT to GITHUB_ENV, and docker-compose.yaml uses those variables for both sides of the mapping:

${PASSENGER_PORTAL_PORT:-5174}:${PASSENGER_PORTAL_PORT:-5174}

This ensures docker ps shows 0.0.0.0:<port>-><port>/tcp with matching ports.

Runtime

The final image uses Next.js output: 'standalone' and runs:

node server.js

Next.js reads PORT from the runtime environment (supplied via env_file in docker-compose). The standalone output bundles only the required node_modules, producing a significantly smaller image than a full pnpm deploy.

Rollback Procedure

Each build is tagged with the short git SHA (${COMPOSE_PROJECT_NAME}-<service>:<sha8>).

Rollback a single service

# 1. Find the last known-good image tag
docker images | grep passenger-api

# 2. Re-tag it as the current image
docker tag edr-passenger-main-passenger-api:<previous-sha> edr-passenger-main-passenger-api:latest

# 3. Restart the container from the previous image
docker compose --project-name edr-passenger-main up -d passenger-api --force-recreate

Rollback via re-run

Alternatively, trigger a workflow_dispatch on the last known-good commit SHA from the GitHub Actions UI — this rebuilds and redeploys that exact commit.

Production Security Checklist

Before deploying to production, verify:

  • JWT_SECRET, JWT_ACCESS_TOKEN_SECRET, JWT_REFRESH_TOKEN_SECRET are set to random 32+ char strings (openssl rand -hex 32)
  • DATABASE_URL includes ?sslmode=require&connection_limit=10
  • WAAFI_INSECURE_TLS is false (app will refuse to start if true in production)
  • NODE_ENV=production is set
  • GITHUB_PACKAGE_TOKEN is a scoped read-only token, not a personal admin token
  • No .env files are committed to the repository (git status should show none)

Data Retention Policy

The TasksService runs a daily purge cron at 02:00 EAT that automatically deletes:

Table Retention
OtpCode 1 hour after expiry or verification
FaydaVerificationSession 1 hour after expiry or completion
AuditLog 365 days
PaymentWebhookEvent 90 days
GateValidationLog 180 days

No manual intervention is required. Monitor the TasksService log output for purge counts.

GitHub Actions Deployment Flow

Workflow file: .github/workflows/deploy.yml

1) prepare job

  • Checks out repository once.
  • Creates workspace artifact (workspace.tgz) and uploads it.

2) deploy matrix job (parallel)

For each service:

  • Downloads and extracts workspace artifact.
  • Syncs that service env file from server path.
  • Computes branch slug and sets:
    • COMPOSE_PROJECT_NAME=<project>-<branch-slug>
  • Creates .npmrc/.npmrc_temp from NPM_TOKEN.
  • For passenger-api and payment-api: builds and runs the migration image as a gated step before the app image.
  • Builds the service image and tags it with the short git SHA.
  • Runs docker compose up -d <service> --force-recreate.
  • For API services: polls GET /health/ready every 10s for up to 120s. Fails the job if the service does not become healthy.
  • Cleans .npmrc/.npmrc_temp.

Branch/Environment Isolation

Compose project name is generated as:

<project>-<branch-slug>

Examples:

  • edr-freight-main
  • edr-freight-staging
  • edr-passenger-dev

This prevents container/network/volume name collisions between branches.

Local Manual Deployment (Optional)

From repo root:

DOCKER_BUILDKIT=1 docker compose build <service>
docker compose up -d <service>

If private packages are required locally, create .npmrc:

cat <<EOF > .npmrc
@tria-plc:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=<YOUR_TOKEN>
always-auth=true
EOF

Passenger API Startup Behavior

Passenger container entrypoint runs on startup:

  1. npm run prisma:generate
  2. npm run prisma:migrate (deploy mode)
  3. npm run prisma:seed
  4. starts API process

Troubleshooting

Missing env file

Error:

  • Missing env file: ...

Fix:

  • Create the required file in the server env directory for that project/branch slug.

Missing PORT in env file

Error:

  • Missing required PORT in env file: ...

Fix:

  • Add PORT=<number> to that service env file.

Private package install fails

Check:

  • NPM_TOKEN exists in repo secrets.
  • Workflow created .npmrc successfully.

Prisma seed/migrate failures (passenger)

Check:

  • DATABASE_URL in passenger-api.env
  • DB reachability from runner host/container network
  • migration history consistency