A production-ready CI/CD pipeline is no longer a luxury reserved for large engineering teams. With Docker, Nginx, and GitHub Actions, you can set up automated build, test, and deploy on a small VPS in an afternoon. This is the exact setup we use at Digi Innovative Solutions for client projects.
Prerequisites: A Linux VPS (Ubuntu 22.04 or AlmaLinux 9), a domain name, Docker and Docker Compose installed, and a GitHub repository.
Architecture Overview
architectureGitHub Push -> GitHub Actions
-> Build Docker Image
-> Run Tests
-> Push to GitHub Container Registry (GHCR)
-> SSH into VPS
-> docker compose pull && up -d --force-recreate
VPS: Nginx (port 443) -> Docker container (app:3000)
The Dockerfile
Use multi-stage builds to keep the production image small:
dockerfileFROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]
Docker Compose Setup
yamlversion: "3.9"
services:
app:
image: ghcr.io/your-org/your-app:latest
restart: unless-stopped
env_file: .env
expose: ["3000"]
db:
image: postgres:16-alpine
restart: unless-stopped
volumes: [pgdata:/var/lib/postgresql/data]
env_file: .env
volumes:
pgdata:
Nginx Reverse Proxy Config
nginxserver {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
GitHub Actions Workflow
yamlname: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:latest
- uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: root
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/myapp
docker compose pull
docker compose up -d --force-recreate
docker system prune -f
SSL with Let's Encrypt
bash# AlmaLinux/RHEL
dnf install -y certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Certificates auto-renew via systemd timer
With this setup, every git push to main automatically builds, tests, and deploys your app in 3-5 minutes with zero downtime. This is the baseline DevOps setup we implement for all client projects.
Ready to apply these strategies?
Need a production-ready CI/CD pipeline? Our DevOps team sets up automated deployment end-to-end.
Get a Free Consultation →