Category: DevOps

  • GitHub Actions CI/CD: Build, Test, and Deploy Directly from Your Repository

    GitHub Actions brings CI/CD directly into your repository. Every push, PR, or scheduled event can trigger automated build/test/deploy workflows. Its marketplace of 20,000+ pre-built actions means you rarely write complex scripts from scratch.

    Complete CI/CD Pipeline

    name: CI/CD Pipeline
    on:
      push: { branches: [main, develop] }
      pull_request: { branches: [main] }
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
      cancel-in-progress: true
    
    jobs:
      test:
        runs-on: ubuntu-latest
        strategy:
          fail-fast: true
          matrix: { node-version: [18, 20, 22] }
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with: { node-version: "${{ matrix.node-version }}", cache: 'npm' }
          - run: npm ci
          - run: npm run lint
          - run: npm test -- --coverage --ci
          - name: Upload coverage
            if: matrix.node-version == 20
            uses: codecov/codecov-action@v4
    
      build:
        needs: test
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with: { node-version: 20, cache: 'npm' }
          - run: npm ci && npm run build
          - uses: actions/upload-artifact@v4
            with: { name: build-output, path: dist/, retention-days: 7 }
    
      deploy:
        needs: build
        runs-on: ubuntu-latest
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        environment: production
        steps:
          - uses: actions/checkout@v4
          - uses: actions/download-artifact@v4
            with: { name: build-output, path: dist/ }
          - name: Deploy
            env: { DEPLOY_KEY: "${{ secrets.DEPLOY_KEY }}" }
            run: echo "Deploying to production..."

    Key Features

    Matrix builds: Test across Node versions, OSes, database versions in parallel. Caching: Reuse node_modules between runs — 90s builds drop to 20s. Environments & secrets: Gate deployments with approvals, inject encrypted credentials. Reusable workflows: Define CI patterns once, reference across repos. Composite actions: Package multiple steps into one reusable action.

    Security Best Practices

    Pin action versions to prevent supply chain attacks (use @v4 or commit SHA, never @main). Use fail-fast: true on matrices. Use concurrency groups to cancel redundant runs. Minimize secret exposure — least-privilege permissions, never echo secrets. GitHub Actions is powerful enough for enterprise CI/CD while simple enough for side projects.

    Further reading: GitHub Actions Docs | Actions Marketplace

  • Deploying Microservices on Kubernetes: A Step-by-Step Production Guide

    Kubernetes has become the de facto standard for orchestrating containerized microservices. Moving from Docker to production K8s involves understanding Deployments, Services, ConfigMaps, Secrets, health probes, resource limits, and scaling strategies. This guide walks through deploying a real microservice — every step, every manifest.

    Kubernetes Architecture

    A cluster consists of a control plane (API server, etcd, scheduler, controller manager) and worker nodes running the kubelet agent. The fundamental unit is a Pod — one or more containers sharing network and storage. You rarely create Pods directly; instead use Deployments (stateless), StatefulSets (databases), or DaemonSets (one-per-node agents).

    Step 1: Production Dockerfile

    # Multi-stage build for a Node.js microservice
    FROM node:20-alpine AS builder
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --production
    COPY . .
    
    FROM node:20-alpine
    RUN addgroup -S appgroup && adduser -S appuser -G appgroup
    WORKDIR /app
    COPY --from=builder /app .
    USER appuser
    EXPOSE 8080
    HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:8080/health || exit 1
    CMD ["node", "server.js"]

    Step 2: Deployment Manifest

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: order-service
      labels: { app: order-service, version: v1 }
    spec:
      replicas: 3
      strategy:
        type: RollingUpdate
        rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
      selector:
        matchLabels: { app: order-service }
      template:
        metadata:
          labels: { app: order-service, version: v1 }
        spec:
          securityContext: { runAsNonRoot: true, runAsUser: 1000 }
          containers:
          - name: order-service
            image: myregistry/order-service:1.2.0
            ports: [{ containerPort: 8080, name: http }]
            resources:
              requests: { cpu: "100m", memory: "128Mi" }
              limits: { cpu: "500m", memory: "512Mi" }
            livenessProbe:
              httpGet: { path: /health, port: 8080 }
              initialDelaySeconds: 15
              periodSeconds: 20
            readinessProbe:
              httpGet: { path: /ready, port: 8080 }
              initialDelaySeconds: 5
              periodSeconds: 10
            startupProbe:
              httpGet: { path: /health, port: 8080 }
              failureThreshold: 30
              periodSeconds: 2
            envFrom:
            - configMapRef: { name: order-service-config }
            - secretRef: { name: order-service-secrets }

    Step 3: Service & Networking

    apiVersion: v1
    kind: Service
    metadata: { name: order-service }
    spec:
      selector: { app: order-service }
      ports: [{ protocol: TCP, port: 80, targetPort: 8080 }]
      type: ClusterIP  # Internal; use LoadBalancer or Ingress for external

    Other services reach yours at http://order-service within the same namespace. For external traffic, use an Ingress controller with path-based routing.

    Step 4: Configuration & Secrets

    Use ConfigMaps for non-sensitive settings and Secrets for credentials. For production, consider HashiCorp Vault, AWS Secrets Manager, or Sealed Secrets instead of plain K8s Secrets (which are only base64-encoded by default).

    Step 5: Autoscaling & Observability

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata: { name: order-service-hpa }
    spec:
      scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: order-service }
      minReplicas: 3
      maxReplicas: 20
      metrics:
      - type: Resource
        resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }

    Pair with Prometheus (metrics), Grafana (dashboards), and Loki or ELK (logs). Use OpenTelemetry for distributed tracing across microservices. Start simple — 3 replicas, health probes, iterate. Don’t try service mesh, GitOps, and autoscaling all at once.

    Further reading: K8s Architecture | K8s Deployments

  • DevSecOps Best Practices: Embedding Security Into Every Stage of Your Pipeline

    Security can no longer be an afterthought. DevSecOps integrates security into every phase of the development lifecycle — from code commit to production. A bug found in development costs 10x less to fix than one found in production; a security vulnerability in production can cost millions.

    Shift Left: Security at the IDE

    “Shifting left” means integrating security checks into your editor. SonarLint flags security hotspots inline (XSS, insecure crypto, open redirects). Semgrep runs custom pattern-based rules. Snyk scans dependencies in real-time for known CVEs.

    Automated CI/CD Security Scanning

    name: CI with Security
    on: [push, pull_request]
    jobs:
      build-and-scan:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
            with: { fetch-depth: 0 }
          - uses: actions/setup-node@v4
            with: { node-version: '20', cache: 'npm' }
          - run: npm ci && npm test -- --coverage
    
          # SAST — Static Application Security Testing
          - name: SonarCloud Scan
            uses: sonarsource/sonarcloud-github-action@v2
            env: { SONAR_TOKEN: "${{ secrets.SONAR_TOKEN }}" }
    
          # SCA — Dependency vulnerability scanning
          - name: Snyk Security Check
            uses: snyk/actions/node@master
            env: { SNYK_TOKEN: "${{ secrets.SNYK_TOKEN }}" }
            with: { args: --severity-threshold=high }
    
          # Container scanning
          - run: docker build -t myapp:${{ github.sha }} .
          - name: Trivy Container Scan
            uses: aquasecurity/trivy-action@master
            with: { image-ref: 'myapp:${{ github.sha }}', severity: 'CRITICAL,HIGH', exit-code: '1' }
    
          # Secret scanning
          - name: Gitleaks
            uses: gitleaks/gitleaks-action@v2

    The Security Scanning Toolchain

    SAST (SonarCloud, Semgrep, CodeQL): analyzes source code for injection flaws, insecure crypto, hardcoded credentials. SCA (Snyk, Dependabot, Renovate): scans dependencies for known CVEs — most apps are 80-90% third-party code. DAST (OWASP ZAP, Nuclei): tests running applications with malicious requests. Container Scanning (Trivy, Grype): checks Docker images for OS-level vulnerabilities. IaC Scanning (Checkov, tfsec): catches Terraform/CloudFormation misconfigurations before provisioning.

    Secrets Management

    GitHub found over 12 million secret exposures in public repos (2024). Use dedicated secrets managers (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) and inject at runtime. Add pre-commit hooks: detect-secrets, gitleaks, or trufflehog to block accidental credential commits before they reach your repository.

    Runtime Protection

    Use Falco for container runtime security (detects shell access, privilege escalation), a WAF for public services, and SIEM for centralized log analysis. Alert on anomalous behavior: unusual API patterns, failed auth exceeding thresholds, unexpected outbound connections. The core principle: every security check that can be automated should be.

    Further reading: OWASP DevSecOps Guideline | Snyk DevSecOps Guide