πŸš€ Unit 15 β€’ Cloud Infrastructure & Production Engineering

Deployment & DevOps: CI/CD, Cloud Platforms & Reliability

The authoritative production engineering capstone: Modern hosting ecosystems (Vercel, Netlify, Render, Heroku, AWS), static vs. full-stack deployment topologies, Twelve-Factor environment configuration, GitHub Actions CI/CD automation pipelines, DNS architectures & SSL/TLS certificate handshakes, and observability with structured logging, health checks, and APM error monitoring.

Curriculum: Full-Stack Web Engineering
Unit: 15 of 15 (Graduation Milestone)
Track: DevOps & Site Reliability (SRE)
Status: Production Ready
Curriculum Balance
50% Theory
30% Code
20% Architecture
01

Modern Hosting Platforms & Architectural Topologies

🌐 Cloud Hosting Spectrum: From PaaS to IaaS

In modern web development, shipping software to users has shifted from managing bare-metal racks to automated cloud abstractions. Selecting a deployment target requires balancing operational overhead against infrastructure control and cost at scale:

  • Vercel: The gold standard for Next.js and frontend Jamstack apps. Features global Edge Network caching, automated Git branch previews, serverless functions, and zero-configuration framework detection.
  • Netlify: Jamstack pioneer featuring automated static asset distribution, serverless Lambda functions, split testing, and edge redirects with atomic Git-triggered rollbacks.
  • Render: Modern unified Cloud PaaS and Heroku alternative. Provides managed PostgreSQL, Redis, Docker web services, background workers, and static sites with automatic SSL and pull-request preview environments.
  • Heroku: The classic PaaS platform utilizing containerized Dynos, a declarative Procfile, managed add-ons, and slug compilation pipelines. Ideal for rapid prototyping and educational deploys.
  • AWS (Amazon Web Services): The foundational public cloud provider (IaaS). Provides raw compute (EC2), managed container orchestration (ECS/EKS), serverless execution (AWS Lambda), static asset storage (S3), and global CDN delivery (CloudFront).
Platform Primary Architecture Best Suited For Database & Persistence Configuration Style
Vercel Edge CDN + Serverless / Edge Functions Next.js, SvelteKit, React SPAs, JAMstack External (Neon, Supabase, PlanetScale) vercel.json or Git UI
Netlify Global CDN + AWS Lambda Edge functions Vite SPAs, Astro, Static Docs, Jamstack External / Serverless integrations netlify.toml or _redirects
Render Containerized Web Services & Background Workers Full-Stack Node.js, Express, Python, Go Managed Postgres & Redis render.yaml (Infrastructure as Code)
Heroku Isolated Linux Dyno Containers REST APIs, Worker queues, Full-Stack monoliths Heroku Postgres, Redis add-ons Procfile + Buildpacks
AWS IaaS / PaaS / Serverless (EC2, S3, CloudFront) Enterprise workloads, Custom VPC networking RDS (PostgreSQL, MySQL), DynamoDB Terraform, AWS CDK, CloudFormation
Visual Architecture: Workload Hosting Decision Tree
Application Source Code
β–Ό
Is it purely pre-built static HTML/JS/CSS assets?
↙ YES
NO (Requires Server Runtime) β†˜
Vercel / Netlify / AWS S3+CloudFront
Static Edge Distribution
Does it require long-running WebSockets or background workers?
↙ YES
NO (Ephemeral APIs) β†˜
Render / Heroku / AWS EC2
Persistent Container / Service
Vercel Serverless / AWS Lambda
On-Demand Invocation
02

Deploying Static Sites & Single Page Application (SPA) Routing

πŸ“¦ Build Pipelines & The SPA 404 Routing Dilemma

Static sites are built once at compile time into raw assets (HTML, JavaScript bundles, CSS, images). In client-side routed Single Page Applications (React Router, Vue Router), the browser intercepts navigation URLs like /dashboard or /profile/edit.

The Problem: When a user bookmarks or refreshes https://myapp.com/dashboard, the web server looks for a physical directory or file named dashboard.html. Because it does not exist on disk, the server returns a 404 Not Found error.

The Solution (SPA Rewrite Fallback): All requests that do not match a physical static file must be rewritten by the CDN to serve /index.html with a 200 OK status code, allowing the client-side JavaScript router to mount and render the correct view.

SPA Rewrite Rules: Netlify _redirects & vercel.json
# --- Option A: Netlify (_redirects file in public/ or dist/ directory) ---
# Catch-all rule: redirects every route to index.html with a 200 HTTP status
/*    /index.html   200

# Optional: Proxy API requests to backend server to avoid CORS issues
/api/*  https://api.devascentacademy.me/:splat  200

# --- Option B: Vercel (vercel.json in project root) ---
{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ],
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}
Client Refresh Flow
GET /dashboard HTTP/1.1 Host: myapp.devascentacademy.me β†’ CDN detects rule: matches '/dashboard' (no static file) β†’ Rewrites to /index.html (Status: 200 OK) β†’ Browser loads bundle.js β†’ React Router reads window.location.pathname β†’ Renders <Dashboard /> component
03

Deploying Full-Stack Applications: Process Managers & Containerization

βš™οΈ Full-Stack Server Lifecycle & Process Supervision

Unlike static sites that sit passively on a CDN, backend servers (Express, Fastify, NestJS) must run continuously as active OS processes. Deploying full-stack applications introduces four critical operational concerns:

  1. Dynamic Port Binding: Production cloud environments inject a dynamic port via process.env.PORT. Hardcoding port 3000 or 5000 causes immediate deployment crashes.
  2. Process Supervision (PM2): If an uncaught exception crashes the Node.js process, a supervisor like PM2 or container orchestrator must automatically reboot the process in milliseconds without human intervention.
  3. Graceful Shutdown: When the cloud host updates or scales an application, it sends a SIGTERM signal. The server must stop accepting new traffic, finish processing in-flight database transactions, close active connections, and exit safely.
  4. Multi-Stage Docker Containers: Packaging applications in Docker containers guarantees identical behavior across development, staging, and production environments, eliminating "it works on my machine" bugs.
Dockerfile (Production Multi-Stage Build for Node.js)
# Stage 1: Dependency resolution and compilation
FROM node:20-alpine AS builder
WORKDIR /app

# Cache dependency installation layer
COPY package*.json ./
RUN npm ci

# Copy source code and run build (TypeScript / Vite / Next.js)
COPY . .
RUN npm run build --if-present

# Stage 2: Minimal lean production runner image
FROM node:20-alpine AS runner
WORKDIR /app

# Set production environment flags
ENV NODE_ENV=production

# Run as a non-privileged root user for security compliance
USER node

COPY --chown=node:node package*.json ./
# Install ONLY production dependencies (no dev tools or compilers)
RUN npm ci --only=production && npm cache clean --force

# Copy built artifacts from the builder stage
COPY --from=builder --chown=node:node /app/dist ./dist

# Render/AWS dynamically assign PORT at runtime
EXPOSE 8080

CMD ["node", "dist/server.js"]
Multi-Stage Build Metrics
Stage 1 (Builder): 1.1 GB (Includes TypeScript compiler, devDependencies, cache) Stage 2 (Runner): 84 MB (Minimal Alpine Linux runtime + stripped production dependencies) Security: Container runs under non-root 'node' UID (mitigates container-escape vulnerabilities)
04

Environment Configuration & The Twelve-Factor Methodology

πŸ” Factor III: Store Config in the Environment

The Twelve-Factor App methodology establishes industry benchmarks for cloud-native web applications. Factor III mandates strict separation of configuration from code. Configuration consists of anything that varies between deployment environments (Development, Staging, Production):

  • Database connection strings (e.g., local SQLite vs. AWS RDS PostgreSQL with TLS credentials).
  • Third-party API keys and credentials (Stripe, SendGrid, AWS IAM secrets).
  • Hostnames and CORS origins (e.g., localhost:5173 vs. https://devascentacademy.me).
⚠️ The Golden Rule of Secrets Management

NEVER commit .env files or API secrets into Git repositories! .env must be listed in .gitignore. In production, environment variables are injected directly into the process runtime via platform management dashboards (Render Environment tab, Vercel Project Settings, AWS Secrets Manager) or CI/CD encrypted secrets. Provide a sanitized .env.example with dummy placeholder keys for fellow team members.

src/config/env.js β€” Fail-Fast Environment Validation
import dotenv from 'dotenv';
import { z } from 'zod';

// Load .env file for local development
dotenv.config();

// Define strict schema for all mandatory production environment variables
const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
  PORT: z.string().regex(/^\d+$/).transform(Number).default('8080'),
  DATABASE_URL: z.string().url({ message: 'DATABASE_URL must be a valid PostgreSQL connection URI' }),
  JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters for security'),
  ALLOWED_ORIGIN: z.string().url()
});

// Validate process.env on server boot: fail immediately if misconfigured!
const parseResult = envSchema.safeParse(process.env);

if (!parseResult.success) {
  console.error('❌ FATAL: Invalid application environment configuration:');
  console.error(JSON.stringify(parseResult.error.format(), null, 2));
  process.exit(1); // Abort startup before serving compromised traffic
}

export const env = parseResult.data;
Fail-Fast Crash Output (When JWT_SECRET is Missing)
❌ FATAL: Invalid application environment configuration: { "JWT_SECRET": { "_errors": ["JWT_SECRET must be at least 32 characters for security"] }, "DATABASE_URL": { "_errors": ["DATABASE_URL must be a valid PostgreSQL connection URI"] } } Process terminated with status code 1. Deployment aborted before corrupt state reached.
05

CI/CD Automation Pipelines with GitHub Actions

πŸ”„ Continuous Integration vs. Continuous Deployment

Continuous Integration (CI): The automated practice of compiling code, running linter checks, and executing automated test suites on every pull request or git push. CI ensures broken code never merges into the main branch.

Continuous Delivery & Deployment (CD): Continuous Delivery automatically prepares, builds, and stages release artifacts so they can be deployed at the click of a button. Continuous Deployment automatically deploys every passing build directly to live production servers without manual approval.

Visual Architecture: Automated GitHub Actions CI/CD Pipeline
Developer: git push origin main
βž”
Job 1: Lint & Format
ESLint & Prettier
βž”
Job 2: Test Suite
Jest & Supertest
β–Ό All Tests Pass (Status: Green)
Job 3: Build & Package
Vite / Docker Container
βž”
Job 4: Production Deploy
Webhook Trigger to Render/Vercel
βž”
Job 5: Health Check
Verify /healthz endpoint
.github/workflows/production-pipeline.yml
name: Production CI/CD Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    name: Automated Verification & Linting
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup Node.js Runtime
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install Clean Dependencies
        run: npm ci

      - name: Run Linter Check
        run: npm run lint

      - name: Execute Unit & Integration Tests
        run: npm test -- --coverage
        env:
          CI: true
          NODE_ENV: test

  deploy:
    name: Trigger Production Deployment
    needs: [ test ]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest

    steps:
      - name: Trigger Render Production Webhook
        run: |
          curl -f -X POST "${{ secrets.RENDER_DEPLOY_HOOK_URL }}" \
               -H "Content-Type: application/json"
      
      - name: Verify Production Health Endpoint
        run: |
          sleep 15
          curl -f https://api.devascentacademy.me/healthz || exit 1
GitHub Actions Workflow Log
βœ“ Checkout Repository (1.2s) βœ“ Setup Node.js 20 (0.8s) βœ“ Install Clean Dependencies (11.4s) βœ“ Run Linter Check (2.1s) βœ“ Execute Unit & Integration Tests (6.8s) - 24 tests passed, 94.2% coverage βœ“ Trigger Render Production Webhook (0.4s) - HTTP 200 Deployment Queued βœ“ Verify Production Health Endpoint (15.2s) - HTTP 200 OK {"status":"healthy"} Status: All checks passed. Deployment successful!
06

Domains, DNS Routing & SSL/TLS Encryption

🌐 DNS Record Types & Hierarchy

The Domain Name System (DNS) acts as the internet's telephone directory, translating human-friendly domain names (e.g., devascentacademy.me) into machine-routable IP addresses:

  • A Record (Address): Maps an apex domain directly to an IPv4 address (e.g., @ βž” 76.76.21.21).
  • AAAA Record: Maps a domain to an IPv6 address.
  • CNAME Record (Canonical Name): Maps an alias subdomain to another canonical hostname (e.g., www βž” cname.vercel-dns.com). Note: RFC rules forbid CNAME records on the root apex domain (@).
  • ALIAS / ANAME: A pseudo-record provided by DNS registrars (Cloudflare, Route53, Namecheap) that permits CNAME-like routing at the apex domain by dynamically resolving IPs.
  • TXT Record: Arbitrary text data used for domain ownership verification (Google Workspace, SendGrid SPF records).
πŸ”’ SSL/TLS & Automated Let's Encrypt Handshakes

HTTPS (Hypertext Transfer Protocol Secure) encapsulates HTTP communication inside a cryptographic TLS 1.3 tunnel. Modern cloud hosts automate certificate generation and 90-day renewal via Let's Encrypt using the ACME protocol (Automated Certificate Management Environment). The certificate authority verifies domain control via HTTP-01 or DNS-01 challenges, signs an X.509 certificate, and configures modern 2048-bit RSA or ECDSA encryption with zero developer intervention.

Visual Architecture: Browser DNS Lookup & TLS 1.3 Handshake
1. User enters
https://devascentacademy.me
βž”
2. DNS Resolver queries
A/ALIAS Record
βž”
3. Returns Anycast CDN Edge IP
76.76.21.21
β–Ό TCP 3-Way Handshake (SYN βž” SYN-ACK βž” ACK)
4. Client sends ClientHello
Supported Cipher Suites
βž”
5. Edge Server responds
TLS Cert + Diffie-Hellman Key
βž”
6. Symmetric Session Key Generated
Encrypted HTTPS Stream Active
07

Production Observability: Logging, Health Checks & Monitoring

πŸ“Š The Three Pillars of Observability

Shipping code to production is only half the battle; maintaining system reliability requires continuous observability:

  • Structured Logs: Writing logs as structured JSON objects (using libraries like Pino or Winston) rather than messy plain strings. Structured logs enable automated searching, filtering, and indexing in log engines (Datadog, Logtail, AWS CloudWatch).
  • Metrics (Telemetry): Quantitative measurements over time, such as CPU utilization, heap memory usage, HTTP request rate (RPS), and p95/p99 latency distributions.
  • Distributed Traces & Error Tracking: Services like Sentry capture unhandled exceptions in real time, automatically grouping stack traces, user breadcrumbs, device context, and git commit hashes.
src/server.js β€” Production Observability & Graceful Shutdown
import express from 'express';
import pino from 'pino';
import http from 'http';

// Initialize high-performance structured JSON logger
const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  formatters: {
    level: (label) => ({ level: label })
  }
});

const app = express();
const PORT = process.env.PORT || 8080;

// 1. Standard Liveness & Readiness Health Check Endpoint
app.get('/healthz', (req, res) => {
  const healthStatus = {
    status: 'healthy',
    uptimeSeconds: Math.floor(process.uptime()),
    timestamp: new Date().toISOString(),
    memory: process.memoryUsage()
  };
  res.status(200).json(healthStatus);
});

// 2. Structured Request Logging Middleware
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    logger.info({
      method: req.method,
      url: req.originalUrl,
      statusCode: res.statusCode,
      durationMs: Date.now() - start,
      ip: req.ip
    }, 'HTTP request executed');
  });
  next();
});

const server = http.createServer(app);

server.listen(PORT, () => {
  logger.info({ port: PORT, env: process.env.NODE_ENV }, 'Application server started');
});

// 3. Graceful Shutdown Routine (Handling SIGTERM / SIGINT from Docker/K8s/Render)
const shutdown = (signal) => {
  logger.warn({ signal }, 'Received kill signal. Initiating graceful shutdown...');
  
  server.close(() => {
    logger.info('All active HTTP connections closed. Releasing database pools...');
    // Close database connections or message brokers here
    process.exit(0);
  });

  // Force exit if cleanup takes longer than 10 seconds
  setTimeout(() => {
    logger.error('Graceful shutdown timed out. Forcing process termination.');
    process.exit(1);
  }, 10000).unref();
};

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
Structured JSON Log Stream (STDOUT)
{"level":"info","time":1773661200000,"port":8080,"env":"production","msg":"Application server started"} {"level":"info","time":1773661204210,"method":"GET","url":"/healthz","statusCode":200,"durationMs":2.4,"ip":"::ffff:10.0.1.12","msg":"HTTP request executed"} {"level":"warn","time":1773661260000,"signal":"SIGTERM","msg":"Received kill signal. Initiating graceful shutdown..."} {"level":"info","time":1773661260450,"msg":"All active HTTP connections closed. Releasing database pools..."}
08

Unit 15 Practical Capstone: Infrastructure-as-Code Render Blueprint

πŸ—οΈ Production Blueprint: Declarative Multi-Service Deployment

Modern cloud engineering uses Infrastructure as Code (IaC) to declare services, databases, background workers, and environment variables in code files checked into version control. Below is a production render.yaml blueprint that orchestrates a full-stack Node.js API with a managed PostgreSQL database, automated migrations, and health checks.

render.yaml β€” Full-Stack Production Blueprint
services:
  # --- Full-Stack Node.js API Web Service ---
  - type: web
    name: devascent-api
    runtime: node
    region: oregon
    plan: starter
    buildCommand: npm ci && npm run build
    startCommand: npm run db:migrate && npm start
    healthCheckPath: /healthz
    autoDeploy: true
    envVars:
      - key: NODE_ENV
        value: production
      - key: DATABASE_URL
        fromDatabase:
          name: devascent-db
          property: connectionString
      - key: JWT_SECRET
        generateValue: true

databases:
  # --- Managed PostgreSQL Database Instance ---
  - name: devascent-db
    region: oregon
    plan: starter
    databaseName: devascent_production
    user: devascent_admin
Render Cloud Blueprint Deployment Log
==> Provisioning PostgreSQL instance: devascent-db (oregon) ... Ready! ==> Building service: devascent-api ==> Running build command 'npm ci && npm run build'... ==> Build succeeded! Uploading build artifacts to cluster... ==> Running start command 'npm run db:migrate && npm start'... ==> Migrations applied: 20261015001_create_users.sql, 20261015002_create_courses.sql ==> Server listening on PORT 10000 ==> Performing health check at /healthz ... HTTP 200 OK ==> Zero-downtime cutover: New container promoted to live production traffic!
09

University Exam & Technical Interview Viva Preparation Guide

Q1. What is the fundamental architectural difference between deploying to Vercel/Netlify vs. Render/AWS EC2?
Vercel and Netlify are primarily Serverless / Jamstack Edge Platforms. They distribute static HTML/JS/CSS bundles across global Content Delivery Networks (CDNs) and execute backend logic inside short-lived, ephemeral serverless functions that spin down after execution. Render and AWS EC2 are Container/Virtual Machine Platforms that maintain persistent, long-running Linux operating system processes. They support stateful connections like WebSockets, background queue workers, and in-memory caches that serverless platforms cannot sustain.
Q2. Explain why refreshing a Single Page Application (SPA) on a sub-route produces a 404 error, and how rewrite rules fix it.
In an SPA (like React Router), route changes are handled locally in the browser by manipulating the HTML5 History API (pushState). When a user reloads /settings, the request hits the cloud web server directly. Because the server does not find a physical file or directory named /settings or /settings/index.html, it returns an HTTP 404 status. An SPA rewrite rule (e.g., /* /index.html 200) forces the web server to serve the root index.html file with a 200 OK status for all non-file routes, permitting client-side JavaScript to load and render the correct sub-route.
Q3. What are the core tenets of Factor III (Config) in the Twelve-Factor App methodology?
Factor III dictates that configuration must be strictly separated from code and injected at runtime through environment variables (process.env). Any values that differ between deployment targets (development, staging, production)β€”such as database credentials, payment API keys, hostnames, and portsβ€”must never be hardcoded or checked into source control (Git). This ensures identical code builds can be deployed across any environment simply by changing environment variables.
Q4. What is the difference between Continuous Integration (CI) and Continuous Deployment (CD)?
Continuous Integration (CI) is the automated practice where developers frequently commit code to a shared repository, triggering automated pipelines that install dependencies, run linters, and execute test suites to catch integration regressions early. Continuous Deployment (CD) is the automated extension of CI where any commit that passes all automated tests and validation stages is automatically built, containerized, and deployed directly to live production servers without manual human intervention.
Q5. Explain the DNS differences between an 'A' Record and a 'CNAME' Record.
An A Record (Address Record) maps a domain name directly to a specific static IPv4 address (e.g., devascentacademy.me βž” 76.76.21.21). A CNAME Record (Canonical Name) maps an alias name to another domain name or hostname (e.g., www.devascentacademy.me βž” cname.vercel-dns.com). According to DNS RFC specifications, a CNAME cannot coexist with other records and therefore cannot be placed on the root apex domain (zone apex @); instead, ALIAS/ANAME pseudo-records or A records must be used for root domains.
Q6. How does Let's Encrypt automate SSL/TLS certificate provisioning and renewal?
Let's Encrypt uses the automated ACME protocol (Automated Certificate Management Environment). When a domain is pointed to a cloud host, the hosting platform requests a certificate. The Let's Encrypt certificate authority sends an automated challenge (such as hosting a cryptographic token at /.well-known/acme-challenge/ or creating a TXT record). Once the CA verifies that the host controls the domain, it issues a signed X.509 TLS certificate valid for 90 days. Modern cloud hosts run automated background cron daemons that re-validate and renew the certificate every 60 days before expiration.
Q7. Why is a Graceful Shutdown routine critical in containerized cloud deployments?
When cloud platforms (Render, Kubernetes, Docker) perform zero-downtime updates or scale-in events, they terminate old containers by sending an OS SIGTERM signal. Without graceful shutdown handling, the process immediately aborts, severing active HTTP connections, dropping in-flight payments or database writes, and causing 502/503 errors for active users. A graceful shutdown listens for SIGTERM, stops accepting new requests, allows active requests to complete, safely closes database connection pools, and then exits cleanly.
Q8. What are Structured Logs and why are they superior to plain-text console.log() in production?
Structured logs output events as machine-readable JSON objects containing standardized keys (e.g., {"level": "error", "timestamp": "...", "method": "POST", "url": "/login", "userId": 42}). Plain-text console.log() strings are unstructured, difficult to query, and cannot be parsed reliably. Structured JSON logs allow centralized log management platforms (Datadog, ElasticSearch, CloudWatch) to automatically index, filter, aggregate metrics (e.g., error rate per endpoint), and trigger automated PagerDuty alerts when anomaly thresholds are breached.