Skip to main content

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 --from=deps /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 --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /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 ResourceResource NameTier / SKUConfiguration & Role
Resource GroupHightech-WebappSouth Central USCommon lifecycle boundary for compute, network, and registry.
App Service PlanASP-Hightech-WebappLinux P1v3Dedicated production compute with autoscaling capabilities.
Web Apphitech-webappLinux ContainerPrimary web application host.
Deployment Slotsproduction, staging-Staging slot used for zero-downtime blue-green validation.
Container Registryhitechwebapp.azurecr.ioBasic / StandardPrivate container registry housing tagged production images.
Key Vaulthitech-prod-kvStandardInjects 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:

  1. 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 staging slot, traffic is reverted in under 5 seconds:
    az webapp deployment slot swap \
    --resource-group Hightech-Webapp \
    --name hitech-webapp \
    --slot staging \
    --target-slot production
  2. 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.