CI/CD on Your VPS: Automated Deployment Pipelines

Published on

How to set up continuous integration and deployment on a VPS. Covers GitHub Actions, webhooks, Docker-based deploys, and zero-downtime deployments.

Written by Space-Node Team – Infrastructure Team – 15+ years combined experience in game server hosting, VPS infrastructure, and 24/7 streaming solutions. Read author bio →

Stop SSH-ing into your server and running git pull manually. Set up CI/CD and every push to main deploys automatically.

Continuous deployment pipeline

The Simple Pipeline

Push to GitHub -> GitHub Actions builds -> Deploy to VPS

Method 1: GitHub Actions + SSH

GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Deploy to VPS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/myapp
            git pull origin main
            npm install --production
            npm run build
            pm2 restart myapp

Setting Up Secrets

In your GitHub repository settings:

  • VPS_HOST: Your VPS IP address
  • VPS_USER: SSH username
  • SSH_PRIVATE_KEY: Your private SSH key

Method 2: Docker-Based Deployment

Build Docker Image in CI

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t myapp:latest .

      - name: Save and transfer image
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/myapp
            docker compose pull
            docker compose up -d --build

Docker Compose on VPS

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    restart: always
    environment:
      - NODE_ENV=production

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app
    restart: always

Method 3: Webhook-Based Deploy

Simple Deploy Script

#!/bin/bash
# /opt/deploy/deploy.sh
cd /var/www/myapp
git pull origin main
npm install --production
npm run build
pm2 restart myapp
echo "Deployed at $(date)" >> /var/log/deploy.log

Webhook Listener

// Simple webhook server (Node.js)
const http = require('http');
const { execSync } = require('child_process');
const crypto = require('crypto');

const SECRET = process.env.WEBHOOK_SECRET;

http.createServer((req, res) => {
    if (req.method === 'POST' && req.url === '/deploy') {
        let body = '';
        req.on('data', chunk => body += chunk);
        req.on('end', () => {
            const sig = req.headers['x-hub-signature-256'];
            const expected = 'sha256=' + crypto
                .createHmac('sha256', SECRET)
                .update(body)
                .digest('hex');

            if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
                execSync('/opt/deploy/deploy.sh');
                res.writeHead(200);
                res.end('Deployed');
            } else {
                res.writeHead(403);
                res.end('Unauthorized');
            }
        });
    }
}).listen(9000);

Zero-Downtime Deployment

Blue-Green with PM2

# Deploy new version alongside old
pm2 start ecosystem.config.js --env production

# Wait for new version to be ready
sleep 5

# Switch traffic to new version
pm2 reload myapp

# Old version gracefully shuts down

With Nginx Upstream

upstream myapp {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001 backup;
}

Deploy to the backup port, test it, then swap.

Testing Before Deployment

Add tests to your pipeline:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm install
      - run: npm test

  deploy:
    needs: test  # Only deploy if tests pass
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        # ... deploy steps

Deployment Checklist

| Step | Automated? | How | |------|-----------|-----| | Run tests | Yes | GitHub Actions | | Build application | Yes | GitHub Actions | | Transfer to VPS | Yes | SSH/Docker | | Install dependencies | Yes | npm install in deploy script | | Restart application | Yes | PM2/Docker | | Health check | Should be | Curl endpoint after deploy | | Rollback if failed | Semi | Keep previous version available |

Every push to main triggers a fully automated deployment on your Space-Node VPS. No manual intervention needed.

Space-Node Team

About the Author

Space-Node Team – Infrastructure Team – Experts in game server hosting, VPS infrastructure, and 24/7 streaming solutions with 15+ years combined experience.

Since 2023
500+ servers hosted
4.8/5 avg rating

Our team specializes in Minecraft, FiveM, Rust, and 24/7 streaming infrastructure, operating enterprise-grade AMD Ryzen 9 hardware in Netherlands datacenters. We maintain GDPR compliance and ISO 27001-aligned security standards.

View Space-Node's full team bio and credentials →

Launch Your VPS Today

Get started with professional VPS hosting powered by enterprise hardware. Instant deployment and 24/7 support included.

CI/CD on Your VPS: Automated Deployment Pipelines