GitHub Actions CI/CD Pipeline: Complete Tutorial
Build automated CI/CD pipelines with GitHub Actions. Learn workflows, jobs, actions, and deploy applications automatically with practical examples.
Moshiour Rahman
Advertisement
What is GitHub Actions?
GitHub Actions is a CI/CD platform that automates your build, test, and deployment pipeline directly from your GitHub repository. It’s free for public repositories and offers generous free minutes for private repos.
Key Concepts
| Concept | Description |
|---|---|
| Workflow | Automated process defined in YAML |
| Job | Set of steps that run on the same runner |
| Step | Individual task (run command or action) |
| Action | Reusable unit of code |
| Runner | Server that runs your workflows |
Your First Workflow
Create .github/workflows/ci.yml:
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
Workflow Triggers
Push and Pull Request
on:
push:
branches:
- main
- 'release/**'
paths:
- 'src/**'
- 'package.json'
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
Scheduled Workflows
on:
schedule:
# Every day at midnight UTC
- cron: '0 0 * * *'
# Every Monday at 9am UTC
- cron: '0 9 * * 1'
Manual Trigger
on:
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
debug:
description: 'Enable debug mode'
required: false
type: boolean
default: false
Complete CI/CD Pipeline
Node.js Application
name: Node.js CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Job 1: Lint and Type Check
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Type check
run: npm run type-check
# Job 2: Run Tests
test:
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --coverage
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
# Job 3: Build and Push Docker Image
build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
- 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
# Job 4: Deploy to Staging
deploy-staging:
runs-on: ubuntu-latest
needs: build
environment: staging
steps:
- name: Deploy to staging
run: |
echo "Deploying to staging..."
# Add deployment commands here
# Job 5: Deploy to Production
deploy-production:
runs-on: ubuntu-latest
needs: deploy-staging
environment: production
steps:
- name: Deploy to production
run: |
echo "Deploying to production..."
Python Application
name: Python CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov flake8 black mypy
- name: Lint with flake8
run: flake8 src/ tests/
- name: Check formatting with black
run: black --check src/ tests/
- name: Type check with mypy
run: mypy src/
- name: Run tests
run: pytest --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
Using Secrets and Variables
Setting Secrets
- Go to Repository Settings → Secrets and variables → Actions
- Add repository secrets
Using in Workflows
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
echo "Deploying with credentials..."
Environment-Specific Secrets
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Uses production secrets
steps:
- name: Deploy to production
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: ./deploy.sh
Matrix Builds
Test across multiple versions and operating systems:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 21]
exclude:
- os: windows-latest
node-version: 21
include:
- os: ubuntu-latest
node-version: 20
coverage: true
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
- name: Upload coverage
if: matrix.coverage
uses: codecov/codecov-action@v3
Caching Dependencies
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# Or use built-in caching
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
Reusable Workflows
Create Reusable Workflow
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
version:
required: true
type: string
secrets:
deploy-token:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- name: Deploy version ${{ inputs.version }}
env:
TOKEN: ${{ secrets.deploy-token }}
run: echo "Deploying..."
Call Reusable Workflow
# .github/workflows/main.yml
name: Main Pipeline
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- id: version
run: echo "version=1.0.${{ github.run_number }}" >> $GITHUB_OUTPUT
deploy-staging:
needs: build
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
version: ${{ needs.build.outputs.version }}
secrets:
deploy-token: ${{ secrets.DEPLOY_TOKEN }}
Deploy to Cloud Providers
AWS
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to ECS
run: |
aws ecs update-service --cluster my-cluster --service my-service --force-new-deployment
Vercel
- name: Deploy to Vercel
uses: vercel/action@v24
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Netlify
- name: Deploy to Netlify
uses: netlify/actions/cli@master
with:
args: deploy --prod --dir=dist
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
Best Practices
1. Use Specific Action Versions
# Good
uses: actions/checkout@v4
# Bad
uses: actions/checkout@main
2. Minimize Secret Exposure
- name: Deploy
run: ./deploy.sh
env:
TOKEN: ${{ secrets.TOKEN }}
# Secrets are masked in logs
3. Use Job Dependencies
jobs:
lint:
runs-on: ubuntu-latest
# ...
test:
needs: lint # Only runs if lint passes
# ...
deploy:
needs: [lint, test] # Requires both
# ...
4. Cancel In-Progress Workflows
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Summary
| Feature | Use Case |
|---|---|
| Matrix | Test multiple versions |
| Secrets | Store credentials |
| Environments | Deployment approvals |
| Caching | Speed up builds |
| Reusable workflows | DRY principle |
| Concurrency | Prevent duplicate runs |
GitHub Actions provides everything you need for modern CI/CD. Start simple and gradually add complexity as your needs grow.
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
GitHub Actions CI/CD: Automate Your Development Workflow
Master GitHub Actions for CI/CD pipelines. Learn workflows, jobs, actions, secrets management, and deploy to any cloud platform automatically.
DevOpsDocker Compose for Microservices: Complete Development Guide
Master Docker Compose for local microservices development. Learn multi-container orchestration, networking, volumes, and production-ready configurations.
DevOpsArgoCD: GitOps Continuous Delivery for Kubernetes
Master ArgoCD for Kubernetes deployments. Learn GitOps principles, application management, sync strategies, and automate your deployment pipeline.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.