6.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-apifreight-portalfreight-backofficepassenger-apipassenger-portalpassenger-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 totria(overridable byDEPLOY_USER)<branch-slug>is derived from Git branch (lowercase, non-alphanumeric replaced with-)<project>isedr-freightoredr-passenger
Required files per project
For edr-freight:
freight-api.envfreight-portal.envfreight-backoffice.env- optional:
freight-web.build.env
For edr-passenger:
passenger-api.envpassenger-portal.envpassenger-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_URLdoes 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_PORTPASSENGER_API_PORTFREIGHT_PORTAL_PORTFREIGHT_BACKOFFICE_PORTPASSENGER_PORTAL_PORTPASSENGER_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/portalandapps/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 fromNEXT_PUBLIC_API_URLin the service.envfile
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 runs:
npx next start
Next.js reads PORT from the runtime environment (supplied via env_file in docker-compose) to determine which port to listen on.
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_tempfromNPM_TOKEN. - Runs:
docker compose --project-name "$COMPOSE_PROJECT_NAME" build <service>docker compose --project-name "$COMPOSE_PROJECT_NAME" up -d <service>
- Cleans
.npmrc/.npmrc_temp.
Branch/Environment Isolation
Compose project name is generated as:
<project>-<branch-slug>
Examples:
edr-freight-mainedr-freight-stagingedr-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:
npm run prisma:generatenpm run prisma:migrate(deploy mode)npm run prisma:seed- 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_TOKENexists in repo secrets.- Workflow created
.npmrcsuccessfully.
Prisma seed/migrate failures (passenger)
Check:
DATABASE_URLinpassenger-api.env- DB reachability from runner host/container network
- migration history consistency