Part 1: Theory & Fundamentals
What is CI/CD?
Continuous Integration (CI)
- Developers merge code changes into a central repository frequently
- Automated builds and tests run on each commit
- Catches bugs early and improves software quality
Continuous Deployment (CD)
- Automatically deploys code changes to production after passing tests
- Reduces manual deployment errors
- Enables rapid feature delivery
CI/CD Pipeline Flow
Part 2: Prerequisites
Before diving into implementation, make sure you have the following ready:
VPS Initial Setup
# Update system
sudo apt update && sudo apt upgrade -y
# Install Node.js (for Ubuntu)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Install nginx (reverse proxy)
sudo apt install -y nginx
# Install PM2 (process manager)
sudo npm install -g pm2Part 3: Practical Implementation
Step 1: Create Your Application
Let's create a simple Node.js Express application with health checks:
// app.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello from CI/CD Pipeline!',
version: '1.0.0',
timestamp: new Date().toISOString()
});
});
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, closing server...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
module.exports = app;Step 2: Set Up SSH Keys for Deployment
Generate SSH keys for secure, automated deployments:
# Generate SSH key pair (run locally)
ssh-keygen -t ed25519 -C "cicd@yourproject" -f ~/.ssh/cicd_deploy_key
# Copy public key to VPS
ssh-copy-id -i ~/.ssh/cicd_deploy_key.pub YOUR_USER@YOUR_VPS_IP
# Test SSH connection
ssh -i ~/.ssh/cicd_deploy_key YOUR_USER@YOUR_VPS_IP
# Get private key for CI/CD secrets
cat ~/.ssh/cicd_deploy_keyStep 3: GitHub Actions CI/CD Pipeline
Create .github/workflows/deploy.yml in your repository:
name: CI/CD Pipeline
on:
push:
branches: [ main, master ]
env:
NODE_VERSION: '20.x'
APP_DIR: /home/deploy/app
APP_NAME: myapp
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to VPS
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd ${{ env.APP_DIR }}
git pull origin main
npm ci --production
pm2 restart ${{ env.APP_NAME }}
pm2 save
- name: Health Check
run: |
sleep 10
curl -f http://${{ secrets.VPS_HOST }}/healthStep 4: VPS Preparation Script
Run this script on your VPS to prepare it for deployment:
#!/bin/bash
set -e
echo "🚀 Starting VPS setup for CI/CD..."
# Update system
apt update && apt upgrade -y
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
# Install nginx
apt install -y nginx
# Install PM2
npm install -g pm2
# Create deployment user
useradd -m -s /bin/bash deploy
# Create app directory
mkdir -p /home/deploy/app
chown -R deploy:deploy /home/deploy/app
# Setup PM2 startup
su - deploy -c "pm2 startup systemd -u deploy --hp /home/deploy"
# Configure nginx
cat > /etc/nginx/sites-available/app << 'EOF'
server {
listen 80;
server_name _;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
EOF
ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl restart nginx
echo "✅ VPS setup complete!"Step 5: Configure GitHub Secrets
Add these secrets to your GitHub repository (Settings → Secrets and variables → Actions):
VPS_HOSTYour VPS IP or domain (e.g., 192.168.1.100)
VPS_USERNAMESSH username (e.g., deploy)
VPS_SSH_KEYPrivate SSH key content (from ~/.ssh/cicd_deploy_key)
VPS_PORTSSH port (default: 22) - Optional
Testing Your Pipeline
Deploy Your First Version
- 1.Commit and push your code to the main branch
- 2.Check the Actions tab in GitHub to see your pipeline running
- 3.Wait for tests to pass and deployment to complete
- 4.Visit your VPS IP or domain to see your app live!
# Test your deployment
curl http://YOUR_VPS_IP/
# Check health endpoint
curl http://YOUR_VPS_IP/health
# View PM2 status on VPS
ssh deploy@YOUR_VPS_IP
pm2 status
pm2 logs myappBest Practices & Tips
Security
- Rotate SSH keys regularly
- Use environment variables for secrets
- Enable firewall (UFW) on VPS
- Keep dependencies updated
Performance
- Use PM2 cluster mode for scaling
- Enable Nginx caching
- Monitor with PM2 Keymetrics
- Set up proper logging
Reliability
- Always run tests before deploy
- Implement health checks
- Set up error alerting
- Keep backups of your code
Development
- Use staging environment first
- Test deployments locally
- Document your pipeline
- Version your infrastructure
🎉 You're All Set!
You now have a fully automated CI/CD pipeline that deploys your application to a VPS whenever you push to the main branch.
Start building, commit your changes, and watch your code automatically deploy to production!