How deployment works
The platform moved off Robusta’s old deploy system onto a new, self-contained rail: you push to
GitHub, GitHub Actions builds a complete deploy revision, and AWS CodeDeploy rolls it out —
blue/green in production and UAT, in-place in staging. The backend and CRM production apps cut over
to this rail on 2026-07-20 (blue/green, live and verified). This page is the single “how it ships”
picture; the granular, step-by-step operator procedures live in the runbooks under
docs/handoff-vault/ (see Operational runbooks below).
The new rail, end-to-end
Section titled “The new rail, end-to-end”flowchart LR DEV(Developer):::person -->|push / merge PR| GH[GitHub repo<br/>OrascomDevelopment]:::src GH -->|branch → environment| GHA[GitHub Actions<br/>reusable laravel-deploy.yml]:::ci GHA -->|build self-contained<br/>revision .zip| S3[(S3<br/>orascom-deploy-bucket)]:::store GHA -->|create deployment| CD[AWS CodeDeploy]:::deploy S3 --> CD CD -->|blue/green prod+uat<br/>in-place staging| ASG[EC2 Auto Scaling<br/>Graviton t4g · behind ALB]:::compute classDef person fill:#e7eef6,stroke:#2563A8,color:#0b1b2b; classDef src fill:#e7eef6,stroke:#2563A8,color:#0b1b2b; classDef ci fill:#efe7f7,stroke:#7A4FB5,color:#0b1b2b; classDef deploy fill:#dff0f0,stroke:#0C7C84,color:#0b1b2b; classDef store fill:#eceff1,stroke:#5B6B7A,color:#0b1b2b; classDef compute fill:#f7efdc,stroke:#B7791F,color:#0b1b2b;
- Push / merge a pull request to an environment branch (
develop→ staging,uat→ UAT,main→ production) in the GitHub orgOrascomDevelopment. - GitHub Actions builds the revision. A reusable workflow (
portals-workflows/laravel-deploy.yml) installs dependencies and packages a self-contained deploy artifact — the built code plus the CodeDeployappspec.ymland lifecycle hooks — then uploads it to S3 (orascom-deploy-bucket). This is the key change from the old rail: the revision carries the code, so a box never has togit pullto get it. - CodeDeploy deploys the revision. On the box, lifecycle hooks render the environment file from AWS Secrets Manager (see Where config comes from), run database migrations under a fleet-safe lock, and clear caches — then health-check before any traffic moves.
Per-app × environment
Section titled “Per-app × environment”Each app has three environment branches. The rail and cutover status as of 2026-07-21:
| App | Source repo (GitHub) | Staging · develop |
UAT · uat |
Production · main |
|---|---|---|---|---|
| Backend | Orascom-Portals-Backend |
Actions → CodeDeploy in-place | Actions → CodeDeploy blue/green | Actions → CodeDeploy blue/green live 2026-07-20 |
| Backend workers (queues/schedule) | (same repo) | in-place, supervisor | in-place, supervisor | ODH-Prod-Supervisor / prod in-place live 2026-07-20 |
CRM (crms-middleware) |
Orascom-Portals-CRM |
Actions → CodeDeploy in-place | Actions → CodeDeploy in-place | Actions → CodeDeploy blue/green live 2026-07-20 |
| Frontend (4 React apps) | Orascom-Portals-Frontend |
AWS Amplify autoBuild | AWS Amplify autoBuild | AWS Amplify autoBuild live |
| PDF service | Orascom-Portals-pdf-service |
not on this rail yet | Actions → CodeDeploy in-place (supervisor) | Actions → CodeDeploy in-place live 2026-07-21 |
- Backend production = CodeDeploy app
Orascom-Prod-app/ grouporascom-backend-deploy-group(blue/green), with queue workers deployed in-place toODH-Prod-Supervisor/prod. - CRM production =
Orascom-Prod-app/orascom-crm-deploy-group(blue/green). The CRM has no workers — its queue runs synchronously (QUEUE=sync). - Frontend is AWS Amplify, sourced from GitHub
Orascom-Portals-FrontendwithautoBuild=true— one Amplify app for production (main) and one for the staging/UAT branches. Amplify builds and deploys on push; it is not on the CodeDeploy rail. (The one-time GitLab “Fresh copy” commits in July were the migration onto GitHub, not an ongoing GitLab dependency.)
Automated traffic shift + alarm rollback
Section titled “Automated traffic shift + alarm rollback”Blue/green and rollback
Section titled “Blue/green and rollback”flowchart LR
REV[New revision<br/>from S3]:::store --> GREEN[Green fleet<br/>new EC2 ASG]:::compute
GREEN -->|health-check + migrate| READY{Green healthy<br/>behind ALB?}:::deploy
READY -->|auto-continue| SHIFT[ALB shifts traffic<br/>blue → green]:::ok
READY -->|deploy fails / 5xx / unhealthy alarm| RB[Auto-rollback<br/>blue keeps serving]:::bad
SHIFT --> TERM[Blue terminated<br/>after 10-min window]:::compute
classDef store fill:#eceff1,stroke:#5B6B7A,color:#0b1b2b;
classDef compute fill:#f7efdc,stroke:#B7791F,color:#0b1b2b;
classDef deploy fill:#dff0f0,stroke:#0C7C84,color:#0b1b2b;
classDef ok fill:#dff0e6,stroke:#2f855a,color:#0b1b2b;
classDef bad fill:#f7e0e0,stroke:#c53030,color:#0b1b2b;- Blue/green (production + UAT): CodeDeploy stands up a new Auto Scaling group (“green”) running the new revision alongside the live one (“blue”), shifts traffic at the ALB, and keeps blue briefly so rollback is a traffic flip, not a rebuild.
- In-place (staging): the revision is deployed onto the existing boxes. Faster, cheaper, and fine for a non-production environment.
- Auto-rollback on failure is enabled. If a deployment fails (a hook errors, health checks don’t pass), CodeDeploy automatically rolls back and blue keeps serving — no manual intervention needed.
- Database migrations run inside a deploy hook under a fleet-safe lock (only one box in the group runs them), and are migrate-only — additive/expand-contract by discipline, so blue and green can run against the same schema during the shift.
Where config comes from
Section titled “Where config comes from”Deploy notifications
Section titled “Deploy notifications”The mixed state, told honestly
Section titled “The mixed state, told honestly”The cutover is substantially done. As of 2026-07-21 most of the transition is complete; here is the honest status of each moving part:
- PDF service — on its own rail (uat + prod). live 2026-07-21
The PDF service now auto-deploys via its own GitHub Actions workflow in
Orascom-Portals-pdf-service: a push touatormainruns OIDC → adocker buildx --platform linux/arm64cross-build (QEMU) → push to Amazon ECR (backend-pdf-service) → a tiny CodeDeploy revision → CodeDeploy appODH-Pdf, in-place on the supervisor box. The deploy hookpdf_run.shis fail-safe, build-before-swap: it pulls the image, renders the env from Secrets Manager, asserts the pdfAPI_TOKENequals the backendPDF_SERVICE_TOKEN, runs the new container on a temp port, runs a real Chromium render smoke test, and only then swaps it onto:3001with--restart=always— so a bad image leaves the old container running (empirically proven in UAT), and--restart=alwaysmeans the container now survives a reboot (it was--restart=nobefore). It touches only the Docker container, never the supervisordpdf-service-queueworker, and posts a Teams card. Still honest: staging/developPDF is not on this rail yet — it runs on the combined ODH-Staging box; onlyuat+mainare wired. Remaining follow-up: the pdf↔backend shared-token rotation (F-F) — rotateportals/<env>/pdf.API_TOKENandbackend.PDF_SERVICE_TOKENtogether at a redeploy. - GitLab is fully retired. done 2026-07-21 Production and UAT
never touched GitLab; on 2026-07-21 the last holdout — the staging box’s
crm-mock,pdf-serviceandcrms-middlewaregit remotes — were re-pointed to GitHub (OrascomDevelopment, read-only deploy keys). All Portals repos now live in GitHub and GitLab has zero runtime role. - Prod gate removed — deploys are automated. done 2026-07-21
The manual
continue-deploymentgate was removed on all production groups (and the UAT backend group); traffic now shifts automatically once green is healthy, with CloudWatch-alarm and deploy-failure auto-rollback as the safety net. - Old CodePipelines kept as rollback. The previous prod pipelines are disconnected (webhooks deregistered, deploy transitions disabled) but not deleted, so they remain a manual rollback path until the new rail has proven itself over several deploys.
The unchanged infrastructure underneath
Section titled “The unchanged infrastructure underneath”The deploy rail changed; the compute, data, and network topology did not. It still runs in AWS
account 907815021625, region eu-central-1 (Frankfurt):
- Compute: EC2 Auto Scaling groups (Graviton t4g) for backend and CRM, behind a shared
Application Load Balancer (
orascom-prod-ecommerce-lb) with AWS WAF on the prod ALB. - Data: RDS MySQL, Multi-AZ (
orascom-prod-rds); a dedicated Redis EC2 instance for cache/queues. - Storage / CDN: S3 for media, served via CloudFront. Deploy revisions live in a separate
bucket,
orascom-deploy-bucket. - Config: AWS Secrets Manager (
portals/<env>/<app>), rendered to.envon-box at deploy time.
See the Architecture page for the full component and production-topology diagrams.
Operational runbooks
Section titled “Operational runbooks”The blow-by-blow operator procedures (rebake steps, gate validation commands, exact deployment-group
and instance identifiers, rollback levers) live in the repo, not on this site. They are in
docs/handoff-vault/:
runbook-prod-cutover.md— the production cutover procedure (backend + CRM).runbook-uat-backend-bluegreen.md— the UAT backend blue/green deploy.runbook-staging-deploy.md— the staging (in-place) deploy.deploy-logic-decoded.md— the old on-box pull logic, decoded (the behaviour the new rail replicates and improves on).
