Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Prepare Azure environments for new workloads—subscriptions, networking, identity, and landing zones
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/runtimes/nodejs.md
1# Node.js/Express Production Configuration for Azure23Configure Express/Node.js applications for production deployment on Azure Container Apps and App Service.45## Required Production Settings67### 1. Trust Proxy (CRITICAL)89Azure load balancers and reverse proxies sit in front of your app. Without trust proxy, you'll get wrong client IPs, HTTPS detection failures, and cookie issues.1011```javascript12const app = express();1314// REQUIRED for Azure - trust the Azure load balancer15app.set('trust proxy', 1); // Trust first proxy1617// Or trust all proxies (less secure but simpler)18app.set('trust proxy', true);19```2021### 2. Cookie Configuration2223Azure's infrastructure requires specific cookie settings:2425```javascript26app.use(session({27secret: process.env.SESSION_SECRET,28resave: false,29saveUninitialized: false,30cookie: {31secure: process.env.NODE_ENV === 'production', // HTTPS only in prod32sameSite: 'lax', // Required for Azure33httpOnly: true,34maxAge: 24 * 60 * 60 * 1000 // 24 hours35}36}));37```3839**Key settings:**40- `sameSite: 'lax'` — Required for cookies through Azure's proxy41- `secure: true` — Only in production (HTTPS)42- `httpOnly: true` — Prevent XSS attacks4344### 3. Health Check Endpoint4546Azure Container Apps and App Service check your app's health:4748```javascript49app.get('/health', (req, res) => {50res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });51});52```5354**Configure in Container Apps:**55```bash56az containerapp update \57--name APP \58--resource-group RG \59--health-probe-path /health \60--health-probe-interval 3061```6263### 4. Port Configuration6465Azure sets the port via environment variable:6667```javascript68const port = process.env.PORT || process.env.WEBSITES_PORT || 3000;69app.listen(port, '0.0.0.0', () => {70console.log(`Server running on port ${port}`);71});72```7374**Important:** Bind to `0.0.0.0`, not `localhost` or `127.0.0.1`.7576### 5. Environment Detection7778```javascript79const isProduction = process.env.NODE_ENV === 'production';80const isAzure = process.env.WEBSITE_SITE_NAME || process.env.CONTAINER_APP_NAME;8182if (isProduction || isAzure) {83app.set('trust proxy', 1);84}85```8687---8889## Complete Production Configuration9091```javascript92// app.js - Production-ready Express configuration for Azure93const express = require('express');94const session = require('express-session');9596const app = express();97const isProduction = process.env.NODE_ENV === 'production';9899// Trust Azure load balancer100if (isProduction) {101app.set('trust proxy', 1);102}103104// Security headers105app.use((req, res, next) => {106res.setHeader('X-Content-Type-Options', 'nosniff');107res.setHeader('X-Frame-Options', 'DENY');108next();109});110111// JSON parsing112app.use(express.json());113app.use(express.urlencoded({ extended: true }));114115// Session (if using)116app.use(session({117secret: process.env.SESSION_SECRET || 'dev-secret-change-in-prod',118resave: false,119saveUninitialized: false,120cookie: {121secure: isProduction,122sameSite: 'lax',123httpOnly: true,124maxAge: 24 * 60 * 60 * 1000125}126}));127128// Health check129app.get('/health', (req, res) => {130res.status(200).json({ status: 'ok' });131});132133// Your routes here134app.get('/', (req, res) => {135res.json({ message: 'Hello from Azure!' });136});137138// Error handler139app.use((err, req, res, next) => {140console.error(err.stack);141res.status(500).json({ error: isProduction ? 'Internal error' : err.message });142});143144// Start server145const port = process.env.PORT || 3000;146app.listen(port, '0.0.0.0', () => {147console.log(`Server running on port ${port}`);148});149```150151---152153## Dockerfile for Azure154155```dockerfile156FROM node:20-alpine157158WORKDIR /app159160# Install dependencies first (better caching)161COPY package*.json ./162RUN npm ci --only=production163164# Copy app165COPY . .166167# Set production environment168ENV NODE_ENV=production169170# Expose port (Azure uses PORT env var)171EXPOSE 3000172173# Health check174HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \175CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1176177# Start app178CMD ["node", "app.js"]179```180181---182183## Common Issues184185### Cookies Not Setting186187**Symptom:** Session lost between requests188189**Fix:**1901. Add `app.set('trust proxy', 1)`1912. Set `sameSite: 'lax'` in cookie config1923. Set `secure: true` only if using HTTPS193194### Wrong Client IP195196**Symptom:** `req.ip` returns Azure internal IP197198**Fix:** `app.set('trust proxy', 1);`199200### HTTPS Redirect Loop201202**Symptom:** Infinite redirects when forcing HTTPS203204**Fix:**205```javascript206const TRUSTED_HOST = process.env.APP_PUBLIC_HOSTNAME;207208app.use((req, res, next) => {209if (req.get('x-forwarded-proto') !== 'https' && process.env.NODE_ENV === 'production') {210if (!TRUSTED_HOST) return next();211return res.redirect(`https://${TRUSTED_HOST}${req.originalUrl}`);212}213next();214});215```216217### Health Check Failing218219**Symptom:** Container restarts repeatedly220221**Fix:**2221. Ensure `/health` endpoint returns 2002232. Check app starts within startup probe timeout2243. Verify port matches container configuration225226---227228## Environment Variables229230> ⚠️ **Important distinction**: `azd env set` vs Application Environment Variables231>232> **`azd env set`** sets variables for the **azd provisioning process**, NOT application runtime. These are used by azd and Bicep during deployment.233>234> **Application environment variables** must be configured via:235> 1. **Bicep templates** — Define in the resource's `env` property236> 2. **Azure CLI** — Use `az containerapp update --set-env-vars`237> 3. **azure.yaml** — Use the `env` section in service configuration238239**Azure CLI:**240```bash241az containerapp update \242--name APP \243--resource-group RG \244--set-env-vars \245NODE_ENV=production \246SESSION_SECRET=your-secret-here \247PORT=3000248```249250**azure.yaml:**251```yaml252services:253api:254host: containerapp255env:256NODE_ENV: production257PORT: "3000"258```259260**Bicep:**261```bicep262env: [263{ name: 'NODE_ENV', value: 'production' }264{ name: 'SESSION_SECRET', secretRef: 'session-secret' }265]266```267