Appearance
Fooj CI/CD
Section: 17 — Fooj
Last Updated: 2026-05-30
Scope: Angular 18 SSR build, ACA deployment, pipeline structure
Overview
Fooj uses an Azure DevOps pipeline that builds an Angular 18 SSR (Server-Side Rendering) application, packages it as a Docker container, pushes it to ACR, and deploys it to Azure Container Apps.
Git Push → Build Angular SSR → Docker Build → Push to ACR → Deploy to ACAPipeline File
Devops/azure/pipelines/fooj/deploy-fooj.ymlTrigger Configuration
yaml
trigger:
branches:
include:
- main
- stage
- staging
paths:
include:
- FoojApp/**
pr:
branches:
include:
- main
parameters:
- name: environment
type: string
default: stg
values: [stg, prod]
- name: forceRedeploy
type: boolean
default: falsePipeline Stages
Stage 1 — Lint and Type Check
yaml
- stage: Validate
jobs:
- job: LintAndTypeCheck
pool:
vmImage: ubuntu-latest
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: npm ci
workingDirectory: FoojApp
displayName: Install dependencies
- script: npx ng lint --max-warnings=0
workingDirectory: FoojApp
displayName: ESLint
- script: npx tsc --noEmit
workingDirectory: FoojApp
displayName: TypeScript type checkStage 2 — Unit Tests
yaml
- stage: Test
dependsOn: Validate
jobs:
- job: UnitTests
steps:
- script: |
npm ci
npx ng test --watch=false --browsers=ChromeHeadless \
--code-coverage true
workingDirectory: FoojApp
displayName: Run unit tests
- task: PublishTestResults@2
inputs:
testResultsFormat: JUnit
testResultsFiles: FoojApp/test-results/*.xml
- task: PublishCodeCoverageResults@2
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: FoojApp/coverage/cobertura-coverage.xmlStage 3 — Angular SSR Build
Angular 18 SSR is built using @angular/ssr (successor to Angular Universal):
yaml
- stage: Build
dependsOn: Test
jobs:
- job: BuildSSR
steps:
- script: npm ci
workingDirectory: FoojApp
- script: |
npx ng build --configuration=${{ parameters.environment }}
workingDirectory: FoojApp
displayName: Build Angular SSR
- publish: FoojApp/dist
artifact: ssr-dist
displayName: Publish SSR dist artifactAngular 18 SSR output structure
FoojApp/dist/fooj/
├── browser/ ← Static assets (served by CDN or Node server)
├── server/ ← Node.js SSR server bundle
└── prerendered/ ← Statically pre-rendered routes (if configured)The Dockerfile copies both browser/ and server/ into the image. At runtime, the Node.js server in server/main.server.mjs serves SSR responses and proxies static assets.
Stage 4 — Docker Build and Push
yaml
- stage: DockerBuild
dependsOn: Build
jobs:
- job: BuildAndPush
steps:
- download: current
artifact: ssr-dist
- task: Docker@2
displayName: Build and push SSR image
inputs:
command: buildAndPush
containerRegistry: fooj-${{ parameters.environment }}-acr-connection
repository: fooj-ssr
dockerfile: FoojApp/Dockerfile
buildContext: .
tags: |
$(Build.BuildId)
latestDockerfile (Angular 18 SSR)
dockerfile
# Stage 1: Base node image
FROM node:20-alpine AS base
WORKDIR /app
# Stage 2: Build (pre-built artifact is copied in)
FROM base AS runner
COPY dist/fooj/browser ./browser
COPY dist/fooj/server ./server
COPY dist/fooj/prerendered ./prerendered 2>/dev/null || true
# Install only production dependencies for the server bundle
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
EXPOSE 4000
ENV PORT=4000
ENV NODE_ENV=production
CMD ["node", "server/main.server.mjs"]Why Alpine?
The Alpine base image keeps the Fooj SSR container under 200 MB. The Angular 18 server bundle has no native Node addons, so Alpine's musl libc is fully compatible.
Stage 5 — Deploy to ACA
yaml
- stage: Deploy
dependsOn: DockerBuild
jobs:
- deployment: DeployToACA
environment: fooj-${{ parameters.environment }}
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: Update ACA image
inputs:
azureSubscription: fooj-azure-connection
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
ACR="fooj${{ parameters.environment }}acr"
RG="fooj-${{ parameters.environment }}-containers-rg"
az containerapp update \
--name fooj-ssr \
--resource-group $RG \
--image "${ACR}.azurecr.io/fooj-ssr:$(Build.BuildId)" \
--revision-suffix "$(Build.BuildId)"Deployment uses ACA revision-based rolling update — new traffic is directed to the new revision only after its replicas pass health checks.
Stage 6 — Smoke Test
yaml
- stage: SmokeTest
dependsOn: Deploy
jobs:
- job: Smoke
steps:
- script: |
BASE_URL="https://stg.fooj.sa"
if [[ "${{ parameters.environment }}" == "prod" ]]; then
BASE_URL="https://fooj.sa"
fi
# Home page returns 200 with SSR content
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/")
if [ "$HTTP_CODE" != "200" ]; then
echo "Smoke test FAILED: $BASE_URL returned $HTTP_CODE"
exit 1
fi
# Health endpoint
curl -sf "$BASE_URL/health" | grep -q '"status":"Healthy"'
echo "Smoke tests PASSED"Environment-Specific Build Configurations
Angular's environment.ts files map to the --configuration flag:
--configuration | Environment file | API URL |
|---|---|---|
stg | environment.stg.ts | https://api.stg.fooj.sa |
prod | environment.prod.ts | https://api.fooj.sa |
typescript
// FoojApp/src/environments/environment.stg.ts
export const environment = {
production: false,
apiBaseUrl: 'https://api.stg.fooj.sa',
ssrEnabled: true,
};Rollback
If a deployment causes issues, roll back to the previous revision:
bash
# List revisions
az containerapp revision list \
--name fooj-ssr \
--resource-group fooj-prod-containers-rg \
--query "[].{name:name, active:properties.active, created:properties.createdTime}" \
-o table
# Activate the previous revision
az containerapp revision activate \
--revision fooj-ssr--<previous-build-id> \
--resource-group fooj-prod-containers-rg
# Deactivate the bad revision
az containerapp revision deactivate \
--revision fooj-ssr--<bad-build-id> \
--resource-group fooj-prod-containers-rgPre-Production Checklist
Before deploying to production:
- [ ] Staging smoke tests passing
- [ ] Production Key Vault secrets verified (
fooj-prod-kv) - [ ] ACR image verified in
foojprodacr - [ ] DNS records pointing to production ACA environment
- [ ] ACA custom domain certificate active
- [ ] Min replicas set to 2 in production ACA app
- [ ] Monitoring alerts enabled in Azure Monitor