Introduction
This guide provides a hands-on, end-to-end walkthrough for building, testing, containerizing, and deploying a modern Node.js web application using real-world DevOps best practices.
Instead of focusing only on writing application code, this project is designed to help you understand how software is built, tested, packaged, automated, and deployed in professional environments. By the end of this guide, you will have a fully functional Node.js application with:
- Automated testing
- A CI/CD pipeline using GitHub Actions
- Docker containerization
- Kubernetes deployment configurations
- A clear staging and production workflow
The project intentionally uses core Node.js (without frameworks like Express) to help you understand what happens under the hood, making it ideal for DevOps engineers, backend developers, cloud engineers, and beginners transitioning into DevOps.
This guide follows a step-by-step approach, ensuring you can complete the project in short sessions while gaining practical, job-ready skills.
Objectives
By completing this project, you will be able to:
Build a production-ready Node.js web application
- Create a basic HTTP server
- Serve HTML pages and JSON APIs
- Implement health checks, metrics, and system info endpoints
Apply software engineering best practices
- Use Git for version control
- Write automated tests with Jest and Supertest
- Enforce code quality using ESLint
Implement CI/CD with GitHub Actions
- Automatically run tests and linting on every push
- Build and push Docker images to a container registry
- Scan container images for security vulnerabilities
Containerize applications using Docker
- Create optimized, multi-stage Docker images
- Run applications securely as a non-root user
- Configure health checks and proper signal handling
Run and manage applications locally with Docker Compose
- Simplify local development and testing
- Manage environment variables and service health
Prepare applications for Kubernetes deployment
- Create staging and production Kubernetes manifests
- Configure replicas, probes, and resource limits
- Follow environment-based deployment strategies
Prepare applications for Kubernetes deployment
- Create staging and production Kubernetes manifests
- Configure replicas, probes, and resource limits
- Follow environment-based deployment strategies
Understand real-world DevOps workflows
- Use branch-based deployments (develop - staging, main - production)
- Monitor CI/CD pipelines and container builds
- Troubleshoot common DevOps and deployment issues
Prerequisites – Download These First
Before we start building the CI/CD pipeline, you need to install a few essential tools. These tools form the foundation of the development, containerization, and automation workflow used throughout this project.
Node.js (v18 or higher)
- Node.js is the runtime used to build and run our web application.
- Current LTS (2025): v20.x
- Download: https://nodejs.org/
- Choose the LTS version for stability
Verify installation:
node --version
npm --version
Expected output:
Node.js: v18.x+ or v20.x+
npm: 9.x+ or 10.x+
Git (Latest Stable Version)
Git is used for version control and collaboration. It also triggers our CI/CD pipeline when code is pushed to GitHub.
- Download: https://git-scm.com/downloads
- Select your operating system and complete the installation
Verify installation:
git --version
Expected output:
Git version 2.34+
Docker Desktop (Latest Version)
Docker allows us to package the application into containers so it runs the same everywhere — solving the classic “it works on my machine” problem.
- Download: https://www.docker.com/products/docker-desktop/
- Install and start Docker Desktop after installation
Verify installation:
docker --version
docker-compose --version
Expected output:
Docker version 24.x+
Docker Compose available and running
GitHub Account
- GitHub hosts your source code and runs the automated CI/CD pipeline using GitHub Actions.
- Sign up: https://github.com
- You will use GitHub to:
- Store your project code
- Trigger automated tests
- Build Docker images
- Deploy to staging and production environments
Code Editor (Optional but Recommended)
A code editor makes writing, reading, and managing your code much easier.
Recommended:
- VS Code: https://code.visualstudio.com/
- Alternatives:
- Sublime Text
- Atom
- Any editor you’re comfortable with
Final Verification Checklist
Run the following commands to confirm everything is installed correctly:
node --version # v18.x+ or v20.x+
npm --version # 9.x+ or 10.x+
git --version # 2.34+
docker --version # 24.x+
If all commands return valid versions, you’re ready to move on.
Step 1: Set Up Git for Version Control
What this step does: it 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"
1.1 Select** Terminal** at the top of visual studio code and select *new terminal *
1.2 Enter the One-time Git Configuration
What this step does: it Configures Git on your machine so it knows who you are when you make commits, and sets up proper project tracking.
1.3 Create and Initialize Project
mkdir my-devops-project
cd my-devops-project
git init
mkdir my-devops-project
Creates a new folder (directory) named my-devops-project.
cd my-devops-project
What it does: Moves you into the newly created folder, Your terminal is now pointing to: .../my-devops-project Anything you create next will live inside this folder.
git init
What it does: Initializes a new Git repository in the current folder
Behind the scenes: Git creates a hidden .git/ directory This folder stores: Version history, Branches, Commits and configurations.
code .
use code . to open the current folder in Visual Studio Code.
Current folder (my-devops-project)
Step 2: Build a Node.js Web App
What this step does: Creates a web application using Node.js that can serve web pages and API endpoints.
Initialize Node.js Project using npm init -y
What this step does: Creates a package.json file that describes your project and manages dependencies.
2.1 Update package.json
Open the package.json file and update it with the appropriate project details and scripts that will be used throughout the CI/CD pipeline.
This includes:
- Project name and description
- Entry point (app.js or server.js)
- Start and test scripts
- Metadata used during builds and deployments
{
"name": "my-devops-project",
"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": "Your Name",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"devDependencies": {
"jest": "^29.7.0",
"eslint": "^8.57.0",
"supertest": "^7.1.4"
}
}
*2.2 Create Application File (app.js) *
What this code does:
- Creates an 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 file using touch app.js or New-item app.js.
the app.js file is the entry point i.e the starting point of our application it contains configurations. It is the file that Node.js loads first when the application starts.
Copy this code into the applicagion file 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;
2.3 Install Dependencies
Install testing and development tools using
npm install --save-dev jest eslint supertest
Install all dependencies (creates node_modules folder)
npm install
What you'll see:
A node_modules/ folder with all installed packages
A package-lock.json file that locks dependency versions
Step 3: Create Proper Tests
What this step does: Sets up automated testing so you can verify your application works correctly every time you make changes.
3.1 Create tests directory and test file
Create a folder for your tests
mkdir tests
Create the main test file
touch tests/app.test.js or New-item tests/app.test.js
Copy this code into 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');
});
});
Create Jest configuration
Create jest.config.js using the command touch jest.config.js or New-item jest.config.js
Copy the code into the jest.config.js file*
module.exports = {
testEnvironment: 'node',
collectCoverage: true,
coverageDirectory: 'coverage',
testMatch: ['**/tests/**/*.test.js'],
verbose: true
};
Step 4: GitHub Actions CI/CD Pipeline
What this step does: Creates an automated pipeline that runs tests and builds Docker images every time you push code to GitHub.
4.1 Create workflow directory
Create the GitHub Actions directory structure using the command
mkdir -p .github/workflows
4.2 Create CI/CD pipeline file
Create .github/workflows/ci.yml using the command touch .github/workflows/ci.yml or New-item github/workflows/ci.yml
4.3 Copy this code into the CI/CD Pipeline 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
Step 5: Dockerfile
What this step does: Creates instructions for Docker to build a container image of your application that can run anywhere.
What this Dockerfile does:
- 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:
create a dockerfile using the command touch Dockerfile or New-item Dockerfile
Copy the docker instructions 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"]
Step 6: Essential Configuration Files
What this step does: Creates configuration files that tell various tools what to ignore, how to behave, and what settings to use.
Create .dockerignore using the command touch .dockerignore or New-item .dockerignore
Copy this code into the .dockerignore file.
# Node.js dependencies
node_modules
npm-debug.log*
yarn-error.log*
# Git & CI/CD
.git
.github
# Environment variables
.env
.env.local
.env.*.local
# Logs
logs
*.log
# Test & coverage files
tests/
coverage
.nyc_output
jest.config.js
# Linting & config files
.eslintrc*
README.md
# IDE & editor files
.vscode
.idea
*.swp
*.swo
# OS-specific files
.DS_Store
Thumbs.db
6.1 Create .gitignore
Create .gitignore usinf the command touch .gitignore or New-item .gitignore
Copy this code into the .gitignore file.
# Node.js dependencies
node_modules
npm-debug.log*
yarn-error.log*
# Git & CI/CD
.git
.github
# Environment variables
.env
.env.local
.env.*.local
# Logs
logs
*.log
# Test & coverage files
tests/
coverage
.nyc_output
jest.config.js
# Linting & config files
.eslintrc*
README.md
# IDE & editor files
.vscode
.idea
*.swp
*.swo
# OS-specific files
.DS_Store
Thumbs.db
6.2 Create environment template
Create .env.example using the command touch .env.example or touch New-item .env.example
Copy this configuration into the .env.example file and save.
# Server Configuration PORT=3000 NODE_ENV=production # Logging LOG_LEVEL=info
6.3 Create ESLint configuration
Create .eslintrc.js file using the command touch .eslintrc.js or New-item .eslintrc.js
Copy this code into the .eslintrc.js file 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': '^_' }]
}
};
Step 7: Docker Compose for Development
What this step does: 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 using the command touch docker-compose.yml or New-item docker-compose.yml
Copy this code into the docker-compose.yml file and save.
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
Step 8: Test Everything Locally
What this step does: Shows you how to actually run and test your application locally before deploying it.
Install and Test Locally
Install all dependencies from package.json using the command npm install
Run your test suite to make sure everything works using the command
npm test
Start the application server using the command
npm start
What you'll see:
Tests should pass with green checkmarks: ✓ GET / should return welcome page
Server starts and shows: 🚀 Server running at http://localhost:3000/
Test endpoints (in a new terminal window) by copying and pasting each into a terminal.
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
Copy the url localhost:3000 into a browser to see your application.
Docker Commands
Start docker desktop application to start the docker engine and run the docker command to build the image
# Build image
docker build -t my-devops-app:latest .
Navigate to docker desktop to see the image
Run container
docker run -d --name my-devops-container -p 3000:3000 --restart unless-stopped my-devops-app:latest
Navigate to docker desktop to see the container
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
Navigate to Docker desktop to see the stopped container
Remove the container
docker rm my-devops-container
Navigate to Docker desktop to see if the container as been removed.
Docker Compose Commands
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
Step 9: Deploy to GitHub
What this step does: 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"
Connect to GitHub
IMPORTANT: 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 origin main
What you'll see: Your code appears on GitHub, and the CI/CD pipeline starts running automatically.
Step 10: Kubernetes Deployment Configurations
What this step does: 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
*Create Staging Deployment file *
Create k8s/staging/deployment.yml file using touch k8s/staging/deployment.yml or New-item k8s/staging/deployment.yml
Copy this code into the deployment file
NOTE: Update YOUR_GITHUB_USERNAME in the image URL below!
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
Create Production Deployment
Create k8s/production directory using the command mkdir k8s/production and create the depployment file touch k8s/production/deployment.yml or New-item k8s/production/deployment.yml
Copy this code into the k8s/production/deployment.yml file
NOTE: Update YOUR_GITHUB_USERNAME in the image URL below
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
Step 11: Complete Deployment Workflow
What this step does: 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
use the command git checkout -b develop
Make your changes, then commit and push
git add .
git commit -m "Add new feature
git push origin develop
What happens: 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
What happens: GitHub Actions runs full pipeline and deploys to production.
Monitor Deployments
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/REPO NAME/pkgs/container/REPO NAME
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
Conclusion
This project successfully demonstrates a complete DevOps workflow for modern web application development. We transformed a basic Node.js application into a professional, automated system with CI/CD pipelines, containerization, and deployment strategies.
Key Achievements
End-to-End Automation: GitHub Actions automates testing, building, and deployment from code to production
Containerization: Docker creates secure, portable images that run consistently anywhere
Professional Testing: Jest and Supertest ensure application reliability with automated tests
Security Integration: Trivy vulnerability scanning and security headers protect the application
Kubernetes Ready: Production-grade deployment configurations for staging and production
Branch-Based Workflow: Safe deployments using develop/staging and main/production branches
































































Top comments (0)