

JKH-{ENV}-FND-{TYPE}-{INSTANCE}acrjkhjkfprd. This is the only resource that deviates from the standard convention.
TUNNEL_TOKEN = <JKH-PRD-FND-TUNNEL-CSR token>VITE_SUPABASE_URL = https://<ref>.supabase.co
VITE_SUPABASE_ANON_KEY = <anon key>
VITE_ENTRA_CLIENT_ID = <client ID — keells.onmicrosoft.com tenant>
VITE_ENTRA_TENANT_ID = <tenant ID — keells.onmicrosoft.com>
VITE_ENTRA_REDIRECT_URI = https://csr.johnkeellsfoundation.com/auth/callback
VITE_LINKEDIN_CLIENT_ID = <LinkedIn OAuth client ID>
VITE_N8N_WEBHOOK_BASE_URL = https://n8n.johnkeellsfoundation.com
VITE_N8N_WEBHOOK_SECRET = <webhook auth token>TUNNEL_TOKEN = <JKH-PRD-FND-TUNNEL-N8N token>
N8N_ENCRYPTION_KEY = <random 32-char string>
DB_POSTGRESDB_PASSWORD = <Supabase DB password>
SUPABASE_SERVICE_KEY = <Supabase service role key — never in frontend>N8N_HOST = n8n.johnkeellsfoundation.com
WEBHOOK_URL = https://n8n.johnkeellsfoundation.com
N8N_PROTOCOL = https
N8N_PORT = 5678
DB_TYPE = postgresdb
DB_POSTGRESDB_HOST = db.JKHFOUNDATION.supabase.co
DB_POSTGRESDB_PORT = 5432
DB_POSTGRESDB_DATABASE = postgres
DB_POSTGRESDB_SCHEMA = n8n_data
DB_POSTGRESDB_USER = postgres
EXECUTIONS_DATA_PRUNE = true
EXECUTIONS_DATA_MAX_AGE = 336https://JKHFOUNDATION.supabase.co/auth/v1/callbackSite URL : https://csr.johnkeellsfoundation.com
Redirect URLs : https://csr.johnkeellsfoundation.com/auth/callback
https://csr.johnkeellsfoundation.com/**Site URL : https://csr.johnkeellsfoundation.com
Redirect URLs : https://csr.johnkeellsfoundation.com/auth/callback
https://csr.johnkeellsfoundation.com/**//git push → main
│
▼
GitHub Actions
├── npm ci
├── npm run build (Vite — VITE_ vars injected as build args)
├── docker build (two-stage — builder + serve)
├── docker push → acrjkhjkfprd.azurecr.io/jkh-csr-app:<sha>
└── az containerapp update → ca-jkh-csr-prd (rolling redeploy)# Stage 1 — Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_ANON_KEY
ARG VITE_ENTRA_CLIENT_ID
ARG VITE_ENTRA_TENANT_ID
ARG VITE_ENTRA_REDIRECT_URI
ARG VITE_LINKEDIN_CLIENT_ID
ARG VITE_N8N_WEBHOOK_BASE_URL
ARG VITE_N8N_WEBHOOK_SECRET
RUN npm run build
# Stage 2 — Serve
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY server.js ./
COPY --from=builder /app/dist ./dist
EXPOSE 8080
CMD ["node", "server.js"]name: Build and Deploy
on:
push:
branches: [main]
env:
ACR_NAME: acrjkhjkfprd
IMAGE_NAME: jkh-csr-app
CONTAINER_APP: ca-jkh-csr-prd
RESOURCE_GROUP: JKH-PRD-FND-RG-001
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to Azure
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Log in to ACR
run: az acr login --name $ACR_NAME
- name: Build and push image
run: |
docker build \
--build-arg VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL }} \
--build-arg VITE_SUPABASE_ANON_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY }} \
--build-arg VITE_ENTRA_CLIENT_ID=${{ secrets.VITE_ENTRA_CLIENT_ID }} \
--build-arg VITE_ENTRA_TENANT_ID=${{ secrets.VITE_ENTRA_TENANT_ID }} \
--build-arg VITE_ENTRA_REDIRECT_URI=${{ secrets.VITE_ENTRA_REDIRECT_URI }} \
--build-arg VITE_LINKEDIN_CLIENT_ID=${{ secrets.VITE_LINKEDIN_CLIENT_ID }} \
--build-arg VITE_N8N_WEBHOOK_BASE_URL=${{ secrets.VITE_N8N_WEBHOOK_BASE_URL }} \
--build-arg VITE_N8N_WEBHOOK_SECRET=${{ secrets.VITE_N8N_WEBHOOK_SECRET }} \
-t $ACR_NAME.azurecr.io/$IMAGE_NAME:${{ github.sha }} \
-t $ACR_NAME.azurecr.io/$IMAGE_NAME:latest \
.
docker push $ACR_NAME.azurecr.io/$IMAGE_NAME:${{ github.sha }}
docker push $ACR_NAME.azurecr.io/$IMAGE_NAME:latest
- name: Deploy to Container Apps
run: |
az containerapp update \
--name $CONTAINER_APP \
--resource-group $RESOURCE_GROUP \
--image $ACR_NAME.azurecr.io/$IMAGE_NAME:${{ github.sha }}Scale metric : http_requests (concurrent)
Target per replica: 150
Min replicas : 2
Max replicas : 10
Scale-out cooldown: 30 seconds
Scale-in cooldown : 120 secondsThis document supersedes Architecture Design Document v2.0 (March 2026). All changes are recorded in the Revision History above. The architecture described herein reflects the final agreed deployment model incorporating the pre-deployment architecture review, capacity sizing analysis, and Entra ID app registration amendments (March 2026).