A production-ready Express.js TypeScript application with automated CI/CD deployment to AWS EC2 using GitHub Actions and Docker Hub.
- Overview
- Features
- Tech Stack
- Project Structure
- Prerequisites
- Quick Start
- CI/CD Pipeline
- Deployment Process
- Environment Variables
- API Endpoints
- Manual Deployment
- Monitoring & Maintenance
- Troubleshooting
This project demonstrates a complete DevOps workflow for deploying a Node.js/Express.js application to AWS EC2. It includes:
- Automated Docker image building and pushing to Docker Hub
- GitHub Actions CI/CD pipeline
- Zero-downtime deployments with health checks
- Nginx reverse proxy configuration
- Production-ready Docker setup
- Automated CI/CD: Push to main branch triggers automatic deployment
- Docker Containerization: Application runs in isolated Docker containers
- Zero Downtime: Health checks ensure smooth deployments
- Security: SSH-based deployment with secrets management
- Monitoring: Health check endpoint for application status
- Scalability: Easy to scale with Docker Compose
- Runtime: Node.js 20 (LTS)
- Framework: Express.js 5
- Language: TypeScript
- Containerization: Docker & Docker Compose
- CI/CD: GitHub Actions
- Cloud: AWS EC2
- Web Server: Nginx (Reverse Proxy)
- Container Registry: Docker Hub
devops-aws/
βββ .github/
β βββ workflows/
β βββ deploy-to-ec2.yml # GitHub Actions CI/CD workflow
βββ scripts/
β βββ build-and-push.sh # Build & push Docker images
β βββ deploy.sh # Deploy script for EC2
βββ server/
β βββ src/
β β βββ index.ts # Express application entry point
β βββ Dockerfile # Multi-stage Docker build
β βββ docker-compose.yml # Container orchestration
β βββ ecosystem.config.cjs # PM2 configuration (optional)
β βββ package.json # Node.js dependencies
β βββ tsconfig.json # TypeScript configuration
βββ deploy.md # Detailed deployment guide
βββ deployed-pic.png # Deployment screenshot
βββ README.md # This file
- Node.js 20 or higher
- npm or yarn
- Docker and Docker Compose
- Git
- Ubuntu 20.04/22.04 LTS
- Minimum t2.micro instance
- Security Group with ports 80, 8080, 22 open
- Elastic IP (recommended)
- GitHub account
- Repository with Actions enabled
- Docker Hub account
git clone <your-repo-url>
cd devops-awscd server
npm install# Development mode
npm run dev
# Production build
npm run build
npm startcd server
docker-compose up -dAccess the application at http://localhost:8080
The GitHub Actions workflow automatically:
-
Build Stage:
- Checks out the repository
- Generates image tag from Git SHA
- Builds Docker image
- Pushes to Docker Hub
-
Deploy Stage:
- Copies necessary files to EC2
- Pulls the latest Docker image
- Performs zero-downtime deployment
- Validates health checks
The pipeline runs automatically on:
- Push to
mainbranch
sudo apt update && sudo apt upgrade -ysudo apt-get install npm -y
sudo npm i -g n
sudo n lts# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add user to docker group
sudo usermod -aG docker $USER
# Install Docker Compose
sudo apt-get install docker-compose-plugin -y# Install Nginx
sudo apt install nginx -y
# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginxCreate configuration file:
sudo nano /etc/nginx/sites-available/express-appAdd the following configuration:
server {
listen 80;
server_name YOUR_EC2_PUBLIC_IP;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}Enable the configuration:
sudo ln -s /etc/nginx/sites-available/express-app /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl restart nginxsudo mkdir -p /home/$USER/server
cd /home/$USER/serverAdd these secrets to your GitHub repository (Settings β Secrets and variables β Actions):
| Secret Name | Description | Example |
|---|---|---|
DOCKERHUB_USERNAME |
Docker Hub username | your-username |
DOCKERHUB_TOKEN |
Docker Hub access token | dckr_pat_xxx... |
EC2_HOST |
EC2 public IP address | 3.109.59.94 |
EC2_USER |
SSH username | ubuntu |
EC2_SSH_KEY |
Private SSH key | Contents of .pem file |
EC2_SSH_PORT |
SSH port (optional) | 22 |
Create a .env file in the server directory:
PORT=8000
NODE_ENV=productionSet in workflow file or repository variables:
env:
DOCKERHUB_REPO: your-username/your-repo-nameGET /healthResponse:
{
"status": "ok",
"timestamp": "2025-12-13T10:30:00.000Z"
}GET /Response:
It's working guysπ. Hello
If you need to deploy manually:
export DOCKERHUB_REPO=your-username/your-repo
export DOCKERHUB_USERNAME=your-username
export DOCKERHUB_TOKEN=your-token
./scripts/build-and-push.shssh -i your-key.pem ubuntu@your-ec2-ip
# Run deployment script
./scripts/deploy.sh your-username/your-repo:tag# Check Docker containers
docker ps
# View logs
docker logs express-app
# Check Nginx status
sudo systemctl status nginx# Check health endpoint
curl http://your-ec2-ip/health
# Or visit in browser
http://your-ec2-ip/# Real-time logs
docker logs -f express-app
# Last 100 lines
docker logs --tail 100 express-app- Check Security Group: Ensure ports 80, 8080, and 22 are open
- Verify Nginx:
sudo systemctl status nginx - Check Docker:
docker ps- container should be running - View Logs:
docker logs express-app
- Verify GitHub Secrets: All required secrets are set
- Check SSH Key: Ensure private key format is correct
- EC2 Permissions: User has docker group permissions
- Disk Space:
df -h- ensure sufficient space
# Restart Docker daemon
sudo systemctl restart docker
# Clean up unused containers/images
docker system prune -a
# Rebuild and restart
cd ~/server
docker-compose down
docker-compose up -d --build# Test configuration
sudo nginx -t
# Restart Nginx
sudo systemctl restart nginx
# Check error logs
sudo tail -f /var/log/nginx/error.log- Zero Downtime: The deployment script performs health checks before marking deployment as successful
- Image Tagging: Images are tagged with Git SHA (first 7 characters)
- Rollback: Previous images are retained in Docker Hub for easy rollback
- Security: Never commit secrets or private keys to the repository
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
This project is open source and available under the MIT License.
Created with β€οΈ for learning DevOps practices
Need Help? Check deploy.md for detailed deployment instructions.
