Automated Deployment with GitHub Actions: CI/CD Pipeline Setup
Build CI/CD pipelines with GitHub Actions: Docker build and push, SSH server deployment, test automation, secrets management, and workflow optimization.
Table of Contents
Automated Deployment with GitHub Actions: CI/CD Pipeline Setup
GitHub Actions is a powerful CI/CD platform that automatically tests, builds, and deploys code changes to your server. In this guide we'll build a production-ready pipeline from scratch.
What is GitHub Actions?
GitHub Actions is an automation platform integrated into GitHub repositories:
- Reacts to events like push, PR, and schedule
- Runs parallel and sequential workflows
- 2,000 minutes/month free (unlimited for public repos)
- Extensible with thousands of ready-made actions
GitHub Actions workflow files are stored in YAML format in the .github/workflows/ directory.
Basic Workflow Structure
# .github/workflows/deploy.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- 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
run: echo "Deploy steps here"
on / jobs / steps Structure
Triggers (on)
on:
# Push to branch
push:
branches: [main, develop]
paths:
- 'src/**'
- 'package.json'
# Pull request
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Scheduled run (cron)
schedule:
- cron: '0 2 * * *' # Every night at 02:00
# Manual trigger
workflow_dispatch:
inputs:
environment:
description: 'Deploy environment'
required: true
default: 'staging'
type: choice
options: [staging, production]
# When another workflow completes
workflow_run:
workflows: ["Build"]
types: [completed]
Job Configuration
jobs:
build:
runs-on: ubuntu-latest
env:
NODE_ENV: test
timeout-minutes: 30
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
Docker Build and Push
Push to Docker Hub
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: username/myapp
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,prefix=sha-
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
GitHub Container Registry (GHCR)
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push (GHCR)
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
SSH Deployment to Server
Using appleboy/ssh-action
deploy:
needs: docker
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SERVER_PORT }}
script: |
cd /root/myapp
git pull origin main
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
echo "Deploy completed: $(date)"
Deployment with Docker Compose
- name: Docker Compose deploy
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# Pull new image
docker pull ghcr.io/${{ github.repository }}:latest
# Zero-downtime deploy
docker compose -f /root/app/docker-compose.yml up -d \
--no-deps \
--build \
--remove-orphans
# Clean old images
docker image prune -af --filter "until=24h"
# Health check
sleep 10
curl -f http://localhost:3000/health || exit 1
Secrets Management
GitHub Secrets securely stores sensitive information:
steps:
- name: Application configuration
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "Configuration loaded"
# Secrets are never printed to logs, automatically masked
Required secrets:
SERVER_HOST: Server IP addressSERVER_USER: SSH usernameSSH_PRIVATE_KEY: SSH private keyDOCKERHUB_USERNAME: Docker Hub usernameDOCKERHUB_TOKEN: Docker Hub access token
Cache Usage
- name: Cache Node.js dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: npm ci (cached)
run: npm ci
# Docker layer cache
- name: Build and Push
uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
Matrix Builds
jobs:
test:
strategy:
fail-fast: false
matrix:
node: [18, 20]
os: [ubuntu-latest, macos-latest]
include:
- node: 20
os: ubuntu-latest
coverage: true
exclude:
- node: 18
os: macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
- name: Coverage report
if: matrix.coverage
run: npm run coverage
Environment Protection Rules
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: Production deploy
run: echo "Deploying to production"
In GitHub under Settings > Environments > production:
- Required reviewers (require approval)
- Wait timer (waiting period)
- Deployment branches (main only)
Full CI/CD Pipeline Example
name: Full CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- uses: docker/build-push-action@v5
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://example.com
steps:
- uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
docker compose -f /root/app/docker-compose.yml up -d
docker image prune -f
Conclusion
By setting up a fully automated CI/CD pipeline with GitHub Actions, you can improve code quality, reduce deployment errors, and speed up your development process. With secrets management, cache optimization, and environment protection rules, you get a production-grade pipeline.
Related Articles
Docker Compose Guide: Multi-Container Application Management
Define and manage multi-container applications with Docker Compose: service dependencies, environment variables, health checks, scaling, and production deployment. Comprehensive guide.
Server Automation with Ansible: Infrastructure as Code
Ansible installation, inventory file, playbook writing, role structure, variables, encrypted vault, ad-hoc commands, and server configuration automation. Step-by-step guide.
Caddy Web Server Setup: Automatic HTTPS and Reverse Proxy
Caddy web server installation, Caddyfile configuration, automatic Let's Encrypt SSL, reverse proxy, static site hosting, PHP-FPM integration, and performance settings.