Docker Standalone Build & Azure App Service CI/CD
The HiTech Insurance Web Application is packaged as an optimized, security-hardened Linux container and deployed to Azure App Service via a zero-downtime, blue-green deployment pipeline orchestrated with GitHub Actions.
1. Multi-Stage Dockerfile Architecture
The build process is codified in Dockerfile using a 3-stage pattern to minimize image size and eliminate build toolchains from the production runtime image:
# Stage 1: Dependency Caching
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Stage 2: Next.js Standalone Compilation
FROM node:22-alpine AS builder
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY . .
# Build-time non-secret placeholders to satisfy Zod validation in src/lib/env.ts
ENV NEXT_PUBLIC_APP_URL=https://hitechinsurance.ai
ENV NEXT_PUBLIC_APP_ENV=production
ENV NEXT_PUBLIC_RECAPTCHA_SITE_KEY=6LfDgaQtAAAAACyjOzVYR-527YM2pOrHI3NlwIDr
ENV DATABASE_URL=mongodb://placeholder:27017/placeholder
ENV NEXTAUTH_SECRET=placeholder-build-time-secret
ENV NEXTAUTH_URL=https://build-placeholder.example.com
RUN npm run build
# Stage 3: Minimal Production Runner
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
# Security: Create non-root unprivileged service account
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs
# Copy only standalone server bundle, static assets, and public directory
COPY /app/public ./public
COPY /app/.next/standalone ./
COPY /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
Standalone Output & Bundle Reduction
By enabling output: "standalone" in next.config.ts, Next.js uses AST tracing to package only the exact subset of node_modules required by the production server. This drops the container footprint from over 1.5 GB down to ~118 MB.
GeoIP Tracing Safeguard
The geoip-lite native binary requires its physical binary data files (*.dat) on disk at runtime. To prevent Next.js standalone file tracing from dropping these assets, next.config.ts explicitly includes them:
serverExternalPackages: ["geoip-lite"],
outputFileTracingIncludes: {
"/*": ["./node_modules/geoip-lite/data/**"],
},
2. Infrastructure & Azure Resource Topology
The application runs on Microsoft Azure under the following topology:
| Azure Resource | Resource Name | Tier / SKU | Configuration & Role |
|---|---|---|---|
| Resource Group | Hightech-Webapp | South Central US | Common lifecycle boundary for compute, network, and registry. |
| App Service Plan | ASP-Hightech-Webapp | Linux P1v3 | Dedicated production compute with autoscaling capabilities. |
| Web App | hitech-webapp | Linux Container | Primary web application host. |
| Deployment Slots | production, staging | - | Staging slot used for zero-downtime blue-green validation. |
| Container Registry | hitechwebapp.azurecr.io | Basic / Standard | Private container registry housing tagged production images. |
| Key Vault | hitech-prod-kv | Standard | Injects secrets into App Service Configuration at startup. |
3. Continuous Deployment Workflow (deploy.yml)
The production deployment is fully automated via GitHub Actions (.github/workflows/deploy.yml):
Step 1: OpenID Connect (OIDC) Federated Authentication
The workflow eliminates long-lived static Azure credentials by exchanging GitHub's ephemeral OIDC token for an Azure access token:
- name: Log in to Azure (OIDC)
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
Step 2: Container Image Build & Push
The runner builds the Docker image and tags it with the immutable commit SHA:
az acr login --name "$ACR_NAME"
IMAGE="$ACR_NAME.azurecr.io/$IMAGE_NAME:${{ github.sha }}"
docker build -t "$IMAGE" .
docker push "$IMAGE"
Step 3: Staging Slot Deployment & Container Restart
The new container image is assigned exclusively to the staging deployment slot:
az webapp config container set \
--resource-group "$RESOURCE_GROUP" \
--name "$APP_NAME" \
--slot "$STAGING_SLOT" \
--container-image-name "$IMAGE"
az webapp restart \
--resource-group "$RESOURCE_GROUP" \
--name "$APP_NAME" \
--slot "$STAGING_SLOT"
Step 4: Staging Slot Automated Smoke Test
The pipeline queries the staging hostname's health check probe. If the health probe does not return HTTP 200 OK, the action fails immediately:
sleep 30
HOSTNAME=$(az webapp deployment slot list \
--resource-group "$RESOURCE_GROUP" \
--name "$APP_NAME" \
--query "[?name=='$STAGING_SLOT'].defaultHostName" -o tsv)
curl -f "https://$HOSTNAME/api/health"
Step 5: Zero-Downtime Blue-Green Slot Swap
Once the health check passes, Azure routes incoming production HTTP traffic to the verified container by swapping virtual IP mappings:
az webapp deployment slot swap \
--resource-group "$RESOURCE_GROUP" \
--name "$APP_NAME" \
--slot "$STAGING_SLOT" \
--target-slot production
4. Rollback & Disaster Recovery Runbook
If an unexpected regression escapes into production following a slot swap:
- Instantaneous Slot Swap Reversion:
Run an immediate reverse slot swap via the Azure CLI or Azure Portal. Because the previous healthy container remains active in the
stagingslot, traffic is reverted in under 5 seconds:az webapp deployment slot swap \--resource-group Hightech-Webapp \--name hitech-webapp \--slot staging \--target-slot production - Container Image Rollback: To redeploy a known stable historical release, navigate to Actions → Deploy in GitHub, click Run workflow, and specify the stable Git commit SHA or branch.