A complete CI/CD pipeline using GitHub Actions, Docker, DockerHub, and AWS EC2 for automated deployments!
Features β’ Architecture β’ Setup β’ Tech Stack
This project demonstrates a production-style CI/CD pipeline for the Laundry Buddy backend. Whenever code is pushed to the main branch, it automatically runs tests, builds a Docker image, pushes to DockerHub, and deploys to AWS EC2 - all without manual intervention.
| Traditional Deployment β | This Solution β |
|---|---|
| Manual server SSH and deployment | Fully automated via GitHub Actions |
| "It works on my machine" syndrome | Consistent Docker images everywhere |
| Unreliable deployments | Tested code only reaches production |
| No version control for deployments | Every image tagged with commit SHA |
| Manual testing before deploy | Automated testing in CI pipeline |
Manual deployment is slow and risky. This pipeline ensures:
- β Consistent deployments (same Docker image everywhere)
- β Automation (no manual server work after setup)
- β Reliability (only tested code deploys)
- β Scalability (works for any server that can pull Docker images)
- β Version control (every image tagged with commit SHA)
graph TD
A[π¨βπ» Developer Push to main] -->|Triggers CI/CD| B[βοΈ GitHub Actions]
B -->|Stage 1| C[π§ͺ Run Tests]
C -->|Stage 2| D[π§Ή Lint Code]
D -->|Stage 3| E[π Security Audit]
E -->|Stage 4| F[π³ Build Docker Image]
F -->|Push to Registry| G[π¦ DockerHub]
G -->|Stage 5| H[βοΈ Deploy to EC2]
H -->|SSH & Pull| I[π Live Application]
style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#e8f5e9
style D fill:#e8f5e9
style E fill:#fff3e0
style F fill:#e3f2fd
style G fill:#f3e5f5
style H fill:#fff8e1
style I fill:#c8e6c9
Complete Pipeline Flow:
- Developer pushes code to
mainbranch - GitHub Actions triggers automatically
- CI Stage - Run tests, lint, security audit
- Build Stage - Create Docker image with two tags (latest + commit SHA)
- Push Stage - Upload image to DockerHub registry
- Deploy Stage - SSH to EC2, pull latest image, restart container
- Application is live with zero downtime! π
| Technology | Purpose | Version |
|---|---|---|
| Node.js | Backend Runtime | 22+ |
| Docker | Containerization | Latest |
| DockerHub | Image Registry | - |
| GitHub Actions | CI/CD Automation | - |
| AWS EC2 | Production Server | Ubuntu 24 |
| ESLint | Code Quality | Latest |
| npm audit | Security Scanning | Built-in |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 1: Testing (CI) β
β β’ Checkout code β
β β’ Setup Node.js 22 β
β β’ Cache dependencies β
β β’ Install dependencies (npm ci) β
β β’ Run automated tests (npm test) β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β β
Tests Pass
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β STAGE 2: Linting (CI) β
β β’ Run ESLint checks β
β β’ Validate code quality β
β β’ Ensure coding standards β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β β
Lint Pass
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β STAGE 3: Security Scan (CI) β
β β’ Run npm audit β
β β’ Check for high-severity vulnerabilities β
β β’ Validate dependencies β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β β
Security Pass
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β STAGE 4: Build & Push Docker Image β
β β’ Login to DockerHub β
β β’ Build Docker image β
β β’ Tag with 'latest' and commit SHA β
β β’ Push to DockerHub registry β
β β mayur2410/laundrybuddy-backend:latest β
β β mayur2410/laundrybuddy-backend:{commit-sha} β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β β
Image Built & Pushed
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β STAGE 5: Deploy to EC2 (CD) β
β β’ SSH into EC2 instance β
β β’ Pull latest Docker image β
β β’ Stop old container β
β β’ Start new container β
β β’ Verify deployment β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β
LIVE ON EC2!
Total Time: ~5-7 minutesproduction-ready-docker-cicd/
β
βββ .github/
β βββ workflows/
β βββ ci-cd.yaml # Complete CI/CD workflow
β
βββ Backend/
β βββ Dockerfile # Docker image configuration
β βββ package.json # Node.js dependencies
β βββ package-lock.json # Lock file for dependencies
β βββ server.js # Application entry point
β βββ config/ # Configuration files
β βββ controllers/ # Route controllers
β βββ models/ # Database models
β βββ routes/ # API routes
β βββ middleware/ # Custom middleware
β
βββ README.md # Project documentation (you're here!)
- β AWS Account with EC2 instance running
- β DockerHub account
- β GitHub repository
- β SSH key pair for EC2 access
- β Basic knowledge of Docker, Git & AWS
SSH into your EC2 instance and install Docker:
# SSH into your EC2 instance
ssh -i your-key.pem ubuntu@your-ec2-public-ip
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add user to docker group (no sudo needed)
sudo usermod -aG docker $USER
# Enable and start Docker
sudo systemctl enable docker
sudo systemctl start docker
# Verify installation
docker --versionCreate deployment directory and environment file:
# Create deployment directory
mkdir -p ~/laundrybuddy-backend
cd ~/laundrybuddy-backend
# Create environment file
nano .envAdd your environment variables to .env:
PORT=3000
MONGODB_URL=mongodb+srv://your-connection-string
JWT_SECRET=your-secret-key-here
TWILIO_ACCOUNT_SID=your-twilio-sid
TWILIO_AUTH_TOKEN=your-twilio-token
EMAIL_USER=your-email@gmail.com
EMAIL_PASSWORD=your-email-password
FRONTEND_URL=https://your-frontend-url.com.env to GitHub!
Create deployment script:
nano deploy.shAdd this content:
#!/bin/bash
# Pull latest image from DockerHub
docker pull mayur2410/laundrybuddy-backend:latest
# Stop and remove old container
docker stop laundrybuddy-backend || true
docker rm laundrybuddy-backend || true
# Run new container
docker run -d \
--name laundrybuddy-backend \
--restart unless-stopped \
-p 3000:3000 \
--env-file .env \
mayur2410/laundrybuddy-backend:latest
# Show logs
docker logs -f laundrybuddy-backendMake it executable:
chmod +x deploy.shTest manual deployment:
./deploy.shIf you see the server running β EC2 setup is complete! π
Navigate to: Repository β Settings β Secrets and variables β Actions β New repository secret
| Secret Name | Value | How to Get |
|---|---|---|
DOCKERHUB_USERNAME |
mayur2410 | Your DockerHub username |
DOCKERHUB_TOKEN |
dckr_pat_xxxxx... | DockerHub β Account Settings β Security β New Access Token |
| Secret Name | Value | How to Get |
|---|---|---|
EC2_HOST |
ec2-xx-xx-xx-xx.compute.amazonaws.com | EC2 Public IPv4 DNS or IP |
EC2_USERNAME |
ubuntu | Default user (ubuntu for Ubuntu AMI) |
EC2_SSH_KEY |
-----BEGIN RSA PRIVATE KEY-----... | Content of your .pem file |
How to get EC2_SSH_KEY:
# On your local machine
cat your-key.pem
# Copy entire content including:
# -----BEGIN RSA PRIVATE KEY-----
# ... (all lines)
# -----END RSA PRIVATE KEY-----Create Dockerfile in your Backend/ directory:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]What This Does:
- β Uses Node.js 22 Alpine (lightweight base image)
- β Sets working directory to /app
- β Copies dependency files first (layer caching optimization)
- β Installs dependencies
- β Copies application code
- β Exposes port 3000
- β Starts server with node server.js
Create .github/workflows/ci-cd.yaml:
name: CI/CD Pipeline
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: Backend
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: Backend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
lint:
needs: test
runs-on: ubuntu-latest
defaults:
run:
working-directory: Backend
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
security:
needs: [test, lint]
runs-on: ubuntu-latest
defaults:
run:
working-directory: Backend
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Run security audit
run: npm audit --audit-level=high
build_and_push:
needs: [test, lint, security]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: ./Backend
push: true
tags: |
mayur2410/laundrybuddy-backend:latest
mayur2410/laundrybuddy-backend:${{ github.sha }}
deploy:
needs: build_and_push
runs-on: ubuntu-latest
steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd ~/laundrybuddy-backend
docker pull mayur2410/laundrybuddy-backend:latest
docker stop laundrybuddy-backend || true
docker rm laundrybuddy-backend || true
docker run -d \
--name laundrybuddy-backend \
--restart unless-stopped \
-p 3000:3000 \
--env-file .env \
mayur2410/laundrybuddy-backend:latest
docker logs --tail 50 laundrybuddy-backend# Make a change to your code
echo "// CI/CD pipeline test" >> Backend/server.js
# Commit and push
git add .
git commit -m "Test CI/CD pipeline"
git push origin main
# Watch GitHub Actions run automatically
# Visit: https://github.com/your-username/your-repo/actions| Feature | Benefit |
|---|---|
| π§ͺ Automated Testing | Every push runs full test suite |
| π§Ή Code Quality Checks | ESLint ensures clean code |
| π Security Scanning | npm audit catches vulnerabilities |
| π³ Docker Containerization | Consistent environment everywhere |
| π¦ Image Registry | DockerHub stores all versions |
| π·οΈ Version Tagging | Latest + commit SHA for rollbacks |
| β‘ Zero Downtime | Seamless container replacement |
| π Automatic Deployment | Push to main = live in 5-7 minutes |
# View running containers
docker ps
# View all containers (including stopped)
docker ps -a
# View backend logs
docker logs laundrybuddy-backend
# Follow logs in real-time
docker logs -f laundrybuddy-backend
# View last 100 lines
docker logs --tail 100 laundrybuddy-backend
# Check container stats (CPU, Memory)
docker stats laundrybuddy-backend
# Restart container
docker restart laundrybuddy-backend
# Stop container
docker stop laundrybuddy-backend
# Remove container
docker rm laundrybuddy-backend
# View Docker images
docker images
# Remove unused images (cleanup)
docker image prune -af
# Remove all stopped containers
docker container prune -f# If CI/CD fails, deploy manually:
cd ~/laundrybuddy-backend
./deploy.sh# Use specific commit SHA tag
docker pull mayur2410/laundrybuddy-backend:f6dcb3096fd2532f896bb438bc3172a096349886
docker stop laundrybuddy-backend
docker rm laundrybuddy-backend
docker run -d \
--name laundrybuddy-backend \
--restart unless-stopped \
-p 3000:3000 \
--env-file .env \
mayur2410/laundrybuddy-backend:f6dcb3096fd2532f896bb438bc3172a096349886β Pipeline Failing at Test Stage
# Run tests locally
cd Backend/
npm install
npm test
# Check Node.js version
node --version # Should be v22β Pipeline Failing at Lint Stage
# Run lint locally
npm run lint
# Auto-fix issues
npx eslint . --fixβ Pipeline Failing at Security Stage
# Run audit locally
npm audit
# Fix vulnerabilities automatically
npm audit fix
# Force fix (may break things)
npm audit fix --forceβ Docker Build Failing
# Build locally to debug
cd Backend/
docker build -t test-build .
# Check for syntax errors in Dockerfile
docker build --no-cache -t test-build .β Cannot Push to DockerHub
Check:
- β DOCKERHUB_USERNAME is correct
- β DOCKERHUB_TOKEN is valid (regenerate if needed)
- β Token has Read & Write permissions
β SSH Connection to EC2 Failing
Check:
- β EC2_HOST is correct (public IP or DNS)
- β EC2_USERNAME is correct (ubuntu or ec2-user)
- β EC2_SSH_KEY includes entire .pem file content
- β EC2 Security Group allows SSH (port 22) from GitHub IP ranges
- β EC2 instance is running
β Container Not Starting on EC2
# Check Docker logs
docker logs laundrybuddy-backend
# Check if port 3000 is already in use
sudo netstat -tulpn | grep 3000
# Kill process using port 3000
sudo kill -9 <PID>
# Check disk space
df -h
# Check memory
free -m
# Restart Docker service
sudo systemctl restart dockerβ Application Running but Not Accessible
Check:
- β EC2 Security Group allows inbound traffic on port 3000
- β MongoDB connection string is correct in .env
- β All environment variables are set correctly
# Test locally on EC2
curl http://localhost:3000
# Should return: "Server is ready on EC2"| Stage | Average Time | Can Fail? |
|---|---|---|
| Test | 45-60 sec | β Yes |
| Lint | 20-30 sec | β Yes |
| Security | 30-40 sec | β Yes |
| Build & Push | 2-3 min | β Yes |
| Deploy | 30-45 sec | β Yes |
| Total | ~5-7 min |
This project implements production-grade security:
| Security Measure | Implementation |
|---|---|
| π Environment Variables | Stored in GitHub Secrets (never in code) |
| π SSH Key Storage | Encrypted in GitHub Secrets |
| π‘οΈ DockerHub Token | Access token instead of password |
| π Security Audits | npm audit runs on every build |
| π§ Alpine Linux | Smaller attack surface |
| π Container Restart Policy | Ensures uptime and reliability |
By working with this project, you'll understand:
- β How to build a complete CI/CD pipeline from scratch
- β How to containerize Node.js applications with Docker
- β How to write multi-stage GitHub Actions workflows
- β How to push images to DockerHub registry
- β How to deploy to AWS EC2 securely via SSH
- β How to implement automated testing in CI/CD
- β How to handle secrets and environment variables
- β DevOps best practices for production deployments
- Health Checks β Automatic rollback on deployment failure
- Slack/Discord Notifications β Build status alerts
- Nginx Reverse Proxy β HTTPS with SSL certificates
- Staging Environment β Test before production deployment
- Blue-Green Deployment β Zero-downtime updates
- Database Backups β Automated MongoDB backups
- Monitoring β Prometheus + Grafana dashboards
- Log Aggregation β ELK Stack (Elasticsearch, Logstash, Kibana)
- Multi-Region Deployment β High availability setup
- Auto-Scaling β Based on traffic patterns
- GitHub Actions Documentation
- Docker Best Practices
- AWS EC2 User Guide
- Node.js Docker Guide
- DockerHub Documentation
- Docker Security Best Practices
Mayur
π B.Tech Student | DevOps Enthusiast
π³ Docker β’ βοΈ GitHub Actions β’ βοΈ AWS β’ π§ͺ CI/CD
GitHub: @mayur2410-tech
This project is licensed under the ISC License.
Contributions are welcome! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
If this project helped you understand CI/CD pipelines, please give it a star! β
Made with β€οΈ for learning DevOps
