DEV Community

Cover image for Building a Complete CI/CD Pipeline with Node.js, Docker, and K8s.
lotanna obianefo
lotanna obianefo

Posted on

Building a Complete CI/CD Pipeline with Node.js, Docker, and K8s.

Modern software delivery demands speed, reliability, and scalability. Continuous Integration and Continuous Deployment (CI/CD) pipelines enable teams to ship code faster while maintaining high quality and operational stability. When combined with Node.js, Docker, and Kubernetes (K8s), CI/CD becomes a powerful system for building cloud-native, production-ready applications.

This article provides a complete technical walkthrough of designing and implementing a CI/CD pipeline.

Before starting, you need to install these tools on your machine.

Required Downloads:

  1. Node.js (v18 or higher) - Current LTS: v20.x
    Download from: https://nodejs.org/
    Choose the LTS version (20.x as of 2025)
    Verify installation: node --version and npm --version

  2. Git - Latest stable version
    Download from: https://git-scm.com/downloads
    Choose your operating system version
    Verify installation: git --version

  3. Docker Desktop - Latest version
    Download from: https://www.docker.com/products/docker-desktop/
    Install and start Docker Desktop
    Verify installation: docker --version and docker-compose --version

  4. GitHub Account
    Sign up at: https://github.com
    You'll need this for hosting your code and CI/CD pipeline

  5. VS Code
    Download: https://code.visualstudio.com/

Verify Everything is Installed

ygtrty

Step 1: Set Up Git for Version Control

Configures Git on your machine so it knows who you are when you make commits and sets up proper project tracking.

One-time Git Configuration

     git config --global user.name "Your Name"
     git config --global user.email "you@example.com"
     git config --global init.defaultBranch main
Enter fullscreen mode Exit fullscreen mode

trreyu

To displays all Git configuration settings that are currently applied for your environment use the command git config --list

jdjsjcsds

Create and Initialize Project

To create and initialize the project, first create a new directory, navigate into it, and ensure the directory is initialized with a local Git repository using the below commands.

     mkdir my-devops-project
     cd my-devops-project
     git init
Enter fullscreen mode Exit fullscreen mode

kjgtft

Step 2: Build a Node.js Web App

Creates a web application using Node.js that can serve web pages and API endpoints.

Initialize Node.js Project
This command creates a package.json file that describes your project and manages dependencies.

      npm init -y
Enter fullscreen mode Exit fullscreen mode

The -y flag automatically responds β€œyes” to all confirmation prompts during command execution.

gfytftut

Update package.json

This customizes the package.json with proper scripts and metadata for your DevOps project.

Create/edit package.json:

      {
        "name": "devops-project01",
        "version": "1.0.0",
       "description": "DevOps learning project with Node.js",
        "main": "app.js",
        "scripts": {
          "start": "node app.js",
          "test": "jest",
          "dev": "node app.js",
          "lint": "eslint ."
        },
        "keywords": ["devops", "nodejs", "docker"],
        "author": "lotanna",
        "license": "MIT",
        "engines": {
          "node": ">=18.0.0"
        },
        "devDependencies": {
          "jest": "^29.7.0",
          "eslint": "^8.57.0",
          "supertest": "^7.1.4"
        }
      }
Enter fullscreen mode Exit fullscreen mode

uhgtfft

Create Application File

To create the application file, use the touch command to generate app.js, then insert the required code, which defines the following functionalities and outlines.

  • HTTP server that listens on port 3000

  • Serves different endpoints (/, /health, /info, /metrics)

  • Includes security headers and proper error handling

  • Provides graceful shutdown capability

  • Exports the server for testing

Create app.js:

      // core modules
      const http = require("http");
      const url = require("url");

      // environment configuration
      const PORT = process.env.PORT || 3000;
      const ENVIRONMENT = process.env.NODE_ENV || "development";

      let requestCount = 0;

      // helper: send JSON responses
      function sendJSON(res, statusCode, data) {
        res.statusCode = statusCode;
        res.setHeader("Content-Type", "application/json");
        res.end(JSON.stringify(data, null, 2));
      }

      // helper: send HTML responses
      function sendHTML(res, statusCode, content) {
        res.statusCode = statusCode;
        res.setHeader("Content-Type", "text/html");
        res.end(content);
      }

      // helper: send Prometheus metrics
      function sendMetrics(res) {
        const mem = process.memoryUsage();
        const metrics = `
      # HELP http_requests_total Total HTTP requests
      # TYPE http_requests_total counter
      http_requests_total ${requestCount}

      # HELP app_uptime_seconds Application uptime in seconds
      # TYPE app_uptime_seconds gauge
      app_uptime_seconds ${process.uptime()}

      # HELP nodejs_memory_usage_bytes Node.js memory usage
      # TYPE nodejs_memory_usage_bytes gauge
      nodejs_memory_usage_bytes{type="rss"} ${mem.rss}
      nodejs_memory_usage_bytes{type="heapUsed"} ${mem.heapUsed}
      nodejs_memory_usage_bytes{type="heapTotal"} ${mem.heapTotal}
      nodejs_memory_usage_bytes{type="external"} ${mem.external}
      `;
        res.statusCode = 200;
        res.setHeader("Content-Type", "text/plain");
        res.end(metrics);
      }

      // main server
      const server = http.createServer((req, res) => {
        requestCount++;
        const timestamp = new Date().toISOString();
        const { pathname } = url.parse(req.url, true);

        // logging
        console.log(
          `${timestamp} - ${req.method} ${pathname} - ${
            req.headers["user-agent"] || "Unknown"
          }`
        );

        // CORS headers
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
        res.setHeader("Access-Control-Allow-Headers", "Content-Type");

        // security headers
        res.setHeader("X-Content-Type-Options", "nosniff");
        res.setHeader("X-Frame-Options", "DENY");
        res.setHeader("X-XSS-Protection", "1; mode=block");

        // route handling
        switch (pathname) {
          case "/":
           sendHTML(
              res,
              200,
              `
      <!DOCTYPE html>
      <html>
      <head>
        <title>DevOps Lab 2025</title>
        <style>
          body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
          .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; }
          .endpoint { background: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 5px; border-left: 4px solid #007bff; }
        </style>
      </head>
      <body>
        <div class="header">
          <h1>DevOps Lab 2025</h1>
          <p>Modern Node.js application with CI/CD pipeline</p>
        </div>
        <h2>Available Endpoints:</h2>
        <div class="endpoint"><strong>GET /</strong> - This welcome page</div>
        <div class="endpoint"><strong>GET /health</strong> - Health check (JSON)</div>
        <div class="endpoint"><strong>GET /info</strong> - System information</div>
        <div class="endpoint"><strong>GET /metrics</strong> - Prometheus metrics</div>
        <p>Environment: <strong>${ENVIRONMENT}</strong></p>
        <p>Server time: <strong>${timestamp}</strong></p>
        <p>Requests served: <strong>${requestCount}</strong> </p>
      </body>
      </html>`
            );
            break;

         case "/health":
             sendJSON(res, 200, {
              status: "healthy",
              timestamp,
              uptime: process.uptime(),
              environment: ENVIRONMENT,
              version: "1.0.0",
              node_version: process.version,
              requests_served: requestCount,
            });
            break;

          case "/info":
            sendJSON(res, 200, {
              platform: process.platform,
              architecture: process.arch,
              node_version: process.version,
              memory_usage: process.memoryUsage(),
              environment: ENVIRONMENT,
              pid: process.pid,
              uptime: process.uptime(),
            });
            break;

          case "/metrics":
            sendMetrics(res);
            break;

          default:
            sendJSON(res, 404, {
              error: "Not Found",
              message: `Route ${pathname} not found`,
              timestamp,
            });
        }
      });

      // graceful shutdown
      function shutdown(signal) {
        console.log(`\nReceived ${signal}, shutting down gracefully...`);
        server.close(() => {
          console.log("Server closed");
          process.exit(0);
        });
      }
      process.on("SIGTERM", () => shutdown("SIGTERM"));
      process.on("SIGINT", () => shutdown("SIGINT"));

      // start server
      server.listen(PORT, () => {
        console.log(`πŸš€ Server running at http://localhost:${PORT}/`);
        console.log(`Environment: ${ENVIRONMENT}`);
        console.log(`Node.js version: ${process.version}`);
      });

      // export for testing
      module.exports = server;
Enter fullscreen mode Exit fullscreen mode

touch app.js
jdjdjdj
idjdjdj

Install Dependencies

      # Install testing and development tools
      npm install --save-dev jest eslint supertest

      # Install all dependencies (creates node_modules folder)
      npm install
Enter fullscreen mode Exit fullscreen mode

npm install --save-dev jest eslint supertest installs development-only tools for testing (Jest), code quality enforcement (ESLint), and HTTP/API testing (Supertest), and records them under devDependencies.

npm install installs all dependencies defined in package.json (both dependencies and devDependencies), creates the node_modules directory, and ensures version consistency using package-lock.json.

After running the code, you'll see:

  • A node_modules/ folder with all installed packages
  • A package-lock.json file that locks dependency versions

rgefsrge

Step 3: Create Proper Tests

This step sets up automated testing so you can verify your application works correctly every time you make changes.

Create tests directory and test file

      # Create a folder for your tests
      mkdir tests

      # Create the main test file
      touch tests/app.test.js
Enter fullscreen mode Exit fullscreen mode

defeqfw

Create test file

Copy this code into already created tests/app.test.js:

      const request = require('supertest');
      const server = require('../app');

      describe('App Endpoints', () => {
        afterAll(() => {
          server.close();
        });

        test('GET / should return welcome page', async () => {
          const response = await request(server).get('/');
          expect(response.status).toBe(200);
          expect(response.text).toContain('DevOps Lab 2025');
        });

        test('GET /health should return health status', async () => {
          const response = await request(server).get('/health');
          expect(response.status).toBe(200);
          expect(response.body.status).toBe('healthy');
          expect(response.body.timestamp).toBeDefined();
          expect(typeof response.body.uptime).toBe('number');
        });

        test('GET /info should return system info', async () => {
          const response = await request(server).get('/info');
          expect(response.status).toBe(200);
          expect(response.body.platform).toBeDefined();
          expect(response.body.node_version).toBeDefined();
        });

        test('GET /metrics should return prometheus metrics', async () => {
          const response = await request(server).get('/metrics');
          expect(response.status).toBe(200);
          expect(response.text).toContain('http_requests_total');
          expect(response.text).toContain('app_uptime_seconds');
        });

        test('GET /nonexistent should return 404', async () => {
          const response = await request(server).get('/nonexistent');
          expect(response.status).toBe(404);
          expect(response.body.error).toBe('Not Found');
        });
      });
Enter fullscreen mode Exit fullscreen mode

sgrwesrgew

Create Jest configuration

Create a jest.config.js file, add the required configuration content, and save the file.

      module.exports = {
        testEnvironment: 'node',
        collectCoverage: true,
        coverageDirectory: 'coverage',
        testMatch: ['**/tests/**/*.test.js'],
        verbose: true
      };
Enter fullscreen mode Exit fullscreen mode

ugyftftty
sagrgewr

Step 4: GitHub Actions CI/CD Pipeline

This Creates an automated pipeline that runs tests and builds Docker images every time you push code to GitHub.

Create workflow directory

The .github/workflows directory is the local repository path where GitHub Actions workflows are defined and executed to manage CI/CD processes.

      # Create the GitHub Actions directory structure
      mkdir -p .github/workflows
Enter fullscreen mode Exit fullscreen mode

gyftyft

Create CI/CD pipeline file

Create .github/workflows/ci.yml:

ugygty

Now add the required configuration content and save the file.

      name: CI/CD Pipeline

      on:
        push:
          branches: [ main, develop ]
          tags: [ 'v*' ]
        pull_request:
          branches: [ main ]

      env:
        REGISTRY: ghcr.io
        IMAGE_NAME: ${{ github.repository }}

      concurrency:
        group: ${{ github.workflow }}-${{ github.ref }}
        cancel-in-progress: true

      jobs:
        test:
          name: Test & Lint
          runs-on: ubuntu-latest

          strategy:
            matrix:
              node-version: [20, 22]

          steps:
            - name: Checkout code
              uses: actions/checkout@v4

            - name: Setup Node.js ${{ matrix.node-version }}
              uses: actions/setup-node@v4
              with:
                node-version: ${{ matrix.node-version }}
                cache: 'npm'

            - name: Install dependencies
              run: npm ci

            - name: Run linting
              run: npm run lint

            - name: Run tests
              run: npm test

            - name: Security audit
              run: npm audit --audit-level=critical || echo "Audit completed with warnings"

       build:
          name: Build & Push Image
          runs-on: ubuntu-latest
          needs: test
          if: github.event_name == 'push'

          permissions:
            contents: read
            packages: write
            security-events: write

          outputs:
            image-tag: ${{ steps.meta.outputs.tags }}
            image-digest: ${{ steps.build.outputs.digest }}

          steps:
            - name: Checkout code
              uses: actions/checkout@v4

            - name: Set up Docker Buildx
              uses: docker/setup-buildx-action@v3
              with:
                platforms: linux/amd64,linux/arm64

            - name: Log in to Container Registry
              uses: docker/login-action@v3
              with:
                registry: ${{ env.REGISTRY }}
                username: ${{ github.actor }}
                password: ${{ secrets.GITHUB_TOKEN }}

            - name: Extract metadata
              id: meta
              uses: docker/metadata-action@v5
              with:
                images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
                tags: |
                  type=ref,event=branch
                  type=ref,event=pr
                  type=semver,pattern={{version}}
                  type=semver,pattern={{major}}.{{minor}}
                  type=sha,prefix={{branch}}-
                  type=raw,value=${{ github.run_id }}
                  type=raw,value=latest,enable={{is_default_branch}}
                labels: |
                  org.opencontainers.image.title=DevOps Lab 2025
                  org.opencontainers.image.description=Modern Node.js DevOps application

            - name: Build and push Docker image
              id: build
              uses: docker/build-push-action@v5
              with:
                context: .
                platforms: linux/amd64,linux/arm64
                push: true
                tags: ${{ steps.meta.outputs.tags }}
                labels: ${{ steps.meta.outputs.labels }}
                cache-from: type=gha
                cache-to: type=gha,mode=max
                target: production

            - name: Run Trivy vulnerability scanner
              uses: aquasecurity/trivy-action@0.24.0
              with:
                image-ref: ${{ steps.meta.outputs.tags }}
                format: 'sarif'
                output: 'trivy-results.sarif'
                severity: 'CRITICAL,HIGH'
              continue-on-error: true

            - name: Upload Trivy scan results
              uses: github/codeql-action/upload-sarif@v3
              if: always() && hashFiles('trivy-results.sarif') != ''
              with:
                sarif_file: 'trivy-results.sarif'

        deploy-staging:
          name: Deploy to Staging
          runs-on: ubuntu-latest
          needs: build
          if: github.ref == 'refs/heads/develop'
          environment: staging

          steps:
            - name: Deploy to Staging
              run: |
                echo "πŸš€ Deploying to staging environment..."
                echo "Image: ${{ needs.build.outputs.image-tag }}"
                echo "Digest: ${{ needs.build.outputs.image-digest }}"
                # Add your staging deployment commands here (kubectl, helm, etc.)

        deploy-production:
          name: Deploy to Production
          runs-on: ubuntu-latest
          needs: build
          if: github.ref == 'refs/heads/main'
          environment: production

          steps:
            - name: Deploy to Production
              run: |
                echo "🎯 Deploying to production environment..."
                echo "Image: ${{ needs.build.outputs.image-tag }}"
                echo "Digest: ${{ needs.build.outputs.image-digest }}"
                # Add your production deployment commands here
Enter fullscreen mode Exit fullscreen mode

ytfrtryt

Step 5: Dockerfile

This step defines the build instructions Docker uses to create a portable container image of the application, ensuring consistent execution across environments.

The Dockerfile performs the following functions:

  • Uses multi-stage builds for smaller image size
  • Installs curl for health checks
  • Creates a non-root user for security
  • Sets up proper file permissions
  • Configures health checks

Create Dockerfile:

Input these commands into the Dockerfile

      # Multi-stage build for optimized image
      FROM node:20-alpine AS dependencies

      # Update packages for security
      RUN apk update && apk upgrade --no-cache

      WORKDIR /app

      # Copy package files first for better caching
      COPY package*.json ./

      # Install only production dependencies
      RUN npm ci --only=production && npm cache clean --force

      # Production stage  
      FROM node:20-alpine AS production

      # Update packages and install necessary tools
      RUN apk update && apk upgrade --no-cache && \
          apk add --no-cache curl dumb-init && \
          rm -rf /var/cache/apk/*

      # Create non-root user with proper permissions
      RUN addgroup -g 1001 -S nodejs && \
          adduser -S nodeuser -u 1001 -G nodejs

      WORKDIR /app

      # Copy dependencies from previous stage with proper ownership
      COPY --from=dependencies --chown=nodeuser:nodejs /app/node_modules ./node_modules

      # Copy application code with proper ownership
      COPY --chown=nodeuser:nodejs package*.json ./
      COPY --chown=nodeuser:nodejs app.js ./

      # Switch to non-root user
      USER nodeuser

      # Expose port
      EXPOSE 3000

      # Health check with proper timing for Node.js startup
      HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
        CMD curl -f http://localhost:3000/health || exit 1

      # Use dumb-init for proper signal handling in containers
      ENTRYPOINT ["dumb-init", "--"]

      # Start application
      CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

Ihjgffd
kyfttfrfyh

Step 6: Essential Configuration Files

This creates configuration files that tell various tools what to ignore, how to behave, and what settings to use.

Create .dockerignore

Create .dockerignore:

Input these commands into .dockerignore and save.

      # =========================
      # Dependencies & Package Managers
      # =========================
      node_modules
      npm-debug.log*
      coverage
      .nyc_output

      # =========================
      # Environment Variables
      # =========================
      .env
      .env.local
      .env.*.local

      # =========================
      # Version Control
      # =========================
      .git
      .github

      # =========================
      # Logs
      # =========================
      logs
      *.log

      # =========================
      # Editor & IDE Files
      # =========================
      .vscode
      .idea
      *.swp
      *.swo

      # =========================
      # OS Files
      # =========================
      .DS_Store
      Thumbs.db

      # =========================
      # Testing & Tooling
      # =========================
      tests/
      jest.config.js
      .eslintrc*

      # =========================
      # Documentation
      # =========================
      README.md
Enter fullscreen mode Exit fullscreen mode

Ikfdgdgd
rgereyeryw

Create .gitignore

Create .gitignore:

Input these commands into already created .gitignore and save.

         # ===============================
         # Dependencies
         # ===============================
         node_modules/
         npm-debug.log*
         yarn-debug.log*
         yarn-error.log*

         # ===============================
         # Runtime Data
         # ===============================
         pids
         *.pid
         *.seed
         *.pid.lock

         # ===============================
         # Coverage & Test Reports
         # ===============================
         coverage/
         .nyc_output/

         # ===============================
         # Environment Variables
         # ===============================
         .env
         .env.local
         .env.*.local

         # ===============================
         # Logs
         # ===============================
         logs/
         *.log

         # ===============================
         # IDE / Editor
         # ===============================
         .vscode/
         .idea/
         *.swp
         *.swo

         # ===============================
         # OS Files
         # ===============================
         .DS_Store
         Thumbs.db
Enter fullscreen mode Exit fullscreen mode

hggchg
ffdfghf

Create environment template

Create .env.example:

Input these commands into already created .env.example and save

       # ===============================
       # Server Configuration
       # ===============================
       PORT=3000
       NODE_ENV=production

       # ===============================
       # Logging Configuration
       # ===============================
       LOG_LEVEL=info
Enter fullscreen mode Exit fullscreen mode

dfsdgre
rhrhtregr

Create ESLint configuration

Create .eslintrc.js:

Input these commands into already created .eslintrc.js and save.

      module.exports = {
        env: {
          node: true,
          es2021: true,
          jest: true
        },
        extends: ['eslint:recommended'],
        parserOptions: {
          ecmaVersion: 12,
          sourceType: 'module'
        },
        rules: {
          'no-console': 'off',
          'no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }]
        }
      };
Enter fullscreen mode Exit fullscreen mode

Ihgfytin
hgyugyuhuiu

Step 7: Docker Compose for Development

This creates a Docker Compose file that makes it easy to run your application and any supporting services with a single command.

Create docker-compose.yml:

      version: '3.8'

      services:
        app:
          build: .
          ports:
            - "3000:3000"
          environment:
            - NODE_ENV=development
            - PORT=3000
          restart: unless-stopped
          healthcheck:
            test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
            interval: 30s
            timeout: 10s
            retries: 3
            start_period: 10s
Enter fullscreen mode Exit fullscreen mode

I76tr65ry6n
yuttyun

Step 8: Test Everything Locally

This shows you how to actually run and test your application locally before deploying it.

Install and Test Locally

      # Install all dependencies from package.json
      npm install
      # Run your test suite to make sure everything works
      npm test
      # Start the application server
      npm start
Enter fullscreen mode Exit fullscreen mode

ytfyfytfyt

ygyuyy

  • Tests should pass with green checkmarks: βœ“ GET / should return welcome page

hfdtrtyhg

Test endpoints (in a new terminal window):

      curl http://localhost:3000/         # Homepage
      curl http://localhost:3000/health   # Health check JSON
      curl http://localhost:3000/info     # System info JSON  
      curl http://localhost:3000/metrics  # Prometheus metrics
Enter fullscreen mode Exit fullscreen mode

jdfcjsc
idsfsfs
fgdgeage
drwbhywsr

Docker Commands

      # Build image
      docker build -t my-devops-app:latest .

      # Run container
      docker run -d \
        --name my-devops-container \
        -p 3000:3000 \
        --restart unless-stopped \
        my-devops-app:latest

      # Check container status
      docker ps
      docker logs my-devops-container

      # Test health check
      curl http://localhost:3000/health

      # Stop container
      docker stop my-devops-container
      docker rm my-devops-container
Enter fullscreen mode Exit fullscreen mode

Build image

htfytytdt
Run container

Iygytfyy
Check container status

Iyugyu
Test health check

dsfcsd
Stop container

Iytytytn

Docker Compose Commands
Docker compose is not used to test in production environment but locally.

      # Start all services defined in docker-compose.yml
      docker-compose up -d

      # View real-time logs from all services
      docker-compose logs -f

      # Stop all services and clean up
      docker-compose down
Enter fullscreen mode Exit fullscreen mode

Start all services defined in docker-compose.yml
frdtrgfy
View real-time logs from all services
jgytfyt
Stop all services and clean up
yugytfyt

Step 9: Deploy to GitHub
This commits your code to Git and pushes it to GitHub so the automated CI/CD pipeline can start working.

Initial commit

      # Add all files to Git staging area
      git add .

      # Create your first commit with a descriptive message
      git commit -m "Initial commit: Complete DevOps setup with working CI/CD"
Enter fullscreen mode Exit fullscreen mode

Iygtftfyy

Connect to GitHub

Before running these commands:

  • Go to GitHub.com and create a new repository called my-devops-project
  • DO NOT initialize it with README, .gitignore, or license (we already have these)
  • Copy the repository URL from GitHub
  • Replace YOUR_GITHUB_USERNAME below with your actual GitHub username

       # Set main as the default branch
       git branch -M main
    
       # Connect to your GitHub repository (UPDATE THIS URL!)
       git remote add origin https://github.com/YOUR_GITHUB_USERNAME/my-devops-project.git
    
       # Push your code to GitHub for the first time
       git push -u origin main
    

Ikjhyftrdy
Your code appears on GitHub, and the CI/CD pipeline starts running automatically.
hygtyuj

Step 10: Kubernetes Deployment Configurations
This step creates Kubernetes configuration files that define how your application should run in staging and production environments.

Create directories

       # Create directories for Kubernetes configurations
       mkdir -p k8s/staging k8s/production
Enter fullscreen mode Exit fullscreen mode

gftfyhgy

Create Staging Deployment
Create k8s/staging/deployment.yml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: devops-app-staging
        namespace: staging
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: devops-app
            environment: staging
        template:
          metadata:
            labels:
              app: devops-app
              environment: staging
          spec:
            containers:
            - name: app
              image: ghcr.io/YOUR_GITHUB_USERNAME/my-devops-project:develop-latest
              ports:
              - containerPort: 3000
              env:
              - name: NODE_ENV
                value: "staging"
              - name: PORT
                value: "3000"
              livenessProbe:
                httpGet:
                  path: /health
                  port: 3000
                initialDelaySeconds: 30
                periodSeconds: 10
              readinessProbe:
                httpGet:
                  path: /health
                  port: 3000
                initialDelaySeconds: 5
                periodSeconds: 5
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: devops-app-service-staging
        namespace: staging
      spec:
        selector:
          app: devops-app
          environment: staging
        ports:
        - protocol: TCP
          port: 80
          targetPort: 3000
        type: LoadBalancer
Enter fullscreen mode Exit fullscreen mode

isisfifisi

Create Production Deployment
Create k8s/production/deployment.yml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: devops-app-production
        namespace: production
      spec:
        replicas: 3
        selector:
          matchLabels:
            app: devops-app
            environment: production
        template:
          metadata:
            labels:
              app: devops-app
              environment: production
          spec:
            containers:
            - name: app
              image: ghcr.io/YOUR_GITHUB_USERNAME/my-devops-project:latest
              ports:
              - containerPort: 3000
              env:
              - name: NODE_ENV
                value: "production"
              - name: PORT
                value: "3000"
              resources:
                requests:
                  memory: "128Mi"
                  cpu: "100m"
                limits:
                  memory: "256Mi"
                  cpu: "200m"
              livenessProbe:
                httpGet:
                  path: /health
                  port: 3000
                initialDelaySeconds: 30
                periodSeconds: 10
              readinessProbe:
                httpGet:
                  path: /health
                  port: 3000
                initialDelaySeconds: 5
                periodSeconds: 5
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: devops-app-service-production
        namespace: production
      spec:
        selector:
          app: devops-app
          environment: production
        ports:
        - protocol: TCP
          port: 80
          targetPort: 3000
        type: LoadBalancer
Enter fullscreen mode Exit fullscreen mode

Ikdsfwewf

Step 11: Complete Deployment Workflow

This step shows you how to use the complete CI/CD pipeline with proper branching strategy for staging and production deployments.

Branch-based Deployment Strategy

develop branch β†’ Automatically deploys to staging environment
main branch β†’ Automatically deploys to production environment
Pull requests β†’ Run tests only (no deployment)

Deploy Changes
Deploy to staging:

      # Create and switch to develop branch
      git checkout -b develop

      # Make your changes, then commit and push
      git add .
      git status
      git commit -m "Add new feature"
      git push origin develop
Enter fullscreen mode Exit fullscreen mode

dfghsrthr
GitHub Actions automatically runs tests and deploys to staging.

Deploy to production:

      # Switch to main branch
      git checkout main

      # Merge changes from develop
      git merge develop

      # Push to trigger production deployment
      git push origin main
Enter fullscreen mode Exit fullscreen mode

jdfvdijgids
Ioikgkrjgwes

Monitor Deployments

You can execute this command to monitor the GitHub Actions workflow execution and verify the health status of the deployed application.

      # Check GitHub Actions status
      # Visit: https://github.com/YOUR_GITHUB_USERNAME/my-devops-project/actions

      # Check your container registry
      # Visit: https://github.com/YOUR_GITHUB_USERNAME/my-devops-project/pkgs/container/my-devops-project

      # Test your deployed applications (once you have URLs)
      curl https://your-staging-url.com/health      # Staging health check
      curl https://your-production-url.com/health   # Production health check
Enter fullscreen mode Exit fullscreen mode

This project implements a production grade CI/CD pipeline for a containerized Node.js application using GitHub Actions, Docker, and Kubernetes, demonstrating end-to-end DevOps engineering practices. The application exposes health, metrics, and system endpoints with graceful shutdown handling for Kubernetes environments.

Top comments (0)