GitHub Actions CI/CD Setup
Overview
GitHub Actions automates testing, linting, and deployment on every push and pull request. This guide covers common workflows for Python, R, and multi-platform testing.
Templates by Language
- Python -- Minimal workflow + testing + linting + coverage
- R -- Multi-platform matrix testing + CRAN check
- Node.js -- Matrix testing + type checking + publishing
- Go -- Build matrix + Docker builds + release artifacts
- Multi-language -- Orchestrated jobs across repos
Find your language below and copy the workflow template directly into .github/workflows/.
Quick Start: Python Project
Minimal Python Testing Workflow
Create .github/workflows/test.yml:
name: Python Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Lint with Ruff
run: ruff check .
- name: Type check with mypy
run: mypy src/
- name: Format check with Black
run: black --check .
- name: Run tests with pytest
run: pytest --cov=src --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
Commit and push:
git add .github/workflows/test.yml
git commit -m "Add GitHub Actions CI/CD"
git push
R Package Testing Workflow
Comprehensive R Testing
# .github/workflows/r-check.yml
name: R Package Check
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
check:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
r-version: ['4.2', '4.3', '4.4']
steps:
- uses: actions/checkout@v3
- name: Set up R
uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.r-version }}
- name: Install system dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libgdal-dev \
libgeos-dev \
libproj-dev \
libspatialindex-dev
- name: Install system dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install gdal geos proj spatialindex
- name: Install dependencies
uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::devtools, any::roxygen2, any::testthat
- name: Run roxygen
run: Rscript -e "devtools::load_all(); roxygen2::roxygenise()"
- name: Check
uses: r-lib/actions/check-r-package@v2
with:
upload-snapshots: true
- name: Test coverage
run: |
Rscript -e "install.packages('covr')"
Rscript -e "covr::codecov()"
R Memory Testing (ASAN)
# .github/workflows/r-memory-test.yml
name: R Memory Tests
on: [push, pull_request]
jobs:
asan-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up R
uses: r-lib/actions/setup-r@v2
with:
r-version: '4.3'
- name: Install ASAN
run: |
sudo apt-get update
sudo apt-get install -y clang valgrind
- name: Install package with ASAN
run: |
export CC=clang
export CFLAGS="-fsanitize=address -g"
export LDFLAGS="-fsanitize=address"
R CMD INSTALL .
- name: Run tests under ASAN
run: |
export ASAN_OPTIONS="halt_on_error=1"
Rscript -e "testthat::test_all()"
- name: Valgrind testing
run: |
valgrind --leak-check=full \
Rscript -e "library(mypackage); TRUE"
Docker-Based Testing
Multi-OS Testing with Docker
# .github/workflows/docker-test.yml
name: Docker Tests
on: [push, pull_request]
jobs:
docker:
runs-on: ubuntu-latest
strategy:
matrix:
os: ['ubuntu:22.04', 'ubuntu:20.04', 'debian:11']
steps:
- uses: actions/checkout@v3
- name: Build and test with ${{ matrix.os }}
run: |
docker run --rm -v $(pwd):/workspace -w /workspace \
${{ matrix.os }} \
bash -c "
apt-get update && apt-get install -y \
r-base-dev build-essential gfortran &&
R CMD INSTALL . &&
Rscript -e 'testthat::test_all()'
"
Conditional Steps
Only on Main Branch
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: ./deploy.sh
Only on Pull Requests
- name: Comment on PR
if: github.event_name == 'pull_request'
run: echo "Running PR checks"
Only on Tags
- name: Create release
if: startsWith(github.ref, 'refs/tags/')
run: ./release.sh
Skip Specific Commits
- uses: actions/checkout@v3
if: "!contains(github.event.head_commit.message, '[skip ci]')"
Environment Variables and Secrets
Use Repository Secrets
- Go to Settings > Secrets and variables > Actions
- Click "New repository secret"
- Add
API_KEY, DOCKER_TOKEN, etc.
In workflow:
- name: Use secret
env:
API_KEY: ${{ secrets.API_KEY }}
run: ./script.sh
Set Workflow-Level Variables
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
env:
BUILD_TYPE: release
steps:
- run: echo "Building ${{ env.IMAGE_NAME }}"
Artifact Handling
Upload Test Results
- name: Run tests
run: pytest --junit-xml=results.xml
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results-${{ matrix.os }}
path: results.xml
Download and Use Artifacts
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: test-results
- name: Process results
run: cat results.xml
Matrix Testing
Test Multiple Combinations
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
python: ['3.8', '3.9', '3.10']
include:
- os: windows-latest
python: '3.11' # Only test Windows with Python 3.11
exclude:
- os: macos-latest
python: '3.8' # Skip old Python on macOS
Caching Dependencies
Cache Python Dependencies
- uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: requirements*.txt
Cache R Dependencies
- uses: r-lib/actions/setup-r-dependencies@v2
with:
cache-version: 1
Manual Cache
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
Notifications and Reports
Create Badge
Add to README.md:

Send Slack Notification
- name: Notify Slack
if: failure()
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
payload: |
{
"text": "Build failed for ${{ github.repository }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
}
]
}
Publish Test Report
- name: Publish test results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: 'test-results.xml'
Code Coverage
Generate and Upload Coverage
- name: Generate coverage
run: |
pip install coverage
coverage run -m pytest
coverage xml
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
fail_ci_if_error: false
Add Coverage Badge
In README:
[](https://codecov.io/gh/username/repo)
Common Workflow Patterns
Full Test Suite
name: Complete CI
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: pip install -r requirements-dev.txt
- run: black --check .
- run: ruff check .
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: pip install -r requirements.txt
- run: pytest
security:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: pip install bandit safety
- run: bandit -r src/
- run: safety check
Troubleshooting
Debug Workflow
Add debug: true to step:
- run: echo "Debugging..."
shell: bash
env:
ACTIONS_STEP_DEBUG: true
View detailed logs in job output.
Re-run Failed Jobs
In GitHub UI: Click "Re-run jobs" on failed workflow
Or via CLI:
gh run rerun <run_id>
Disable Workflow
Rename file or add to .github/workflows/workflow.yml:
# Add this at top to disable
on:
workflow_dispatch: # Manual only
Performance Optimization
Parallel Jobs with Needs
jobs:
setup:
runs-on: ubuntu-latest
steps:
- run: echo "Setup complete"
test1:
runs-on: ubuntu-latest
needs: setup
steps:
- run: npm test -- src/module1
test2:
runs-on: ubuntu-latest
needs: setup
steps:
- run: npm test -- src/module2
Use Actions/runner
Pin versions for reproducibility:
- uses: actions/checkout@v3 # Pinned version
- uses: actions/setup-python@v4
Best Practices
- Keep workflows simple: One workflow per CI concern (test, lint, security)
- Use matrix for variations: Test multiple versions systematically
- Cache dependencies: Reduce setup time
- Fail fast: Use conditional steps to skip unnecessary work
- Document secrets: List required secrets in README
- Use workflow templates: Create reusable workflows in
.github/workflows/
- Monitor build times: Keep under 30 minutes
- Test locally first: Use
act to run workflows locally:
brew install act
act -j test