GitHub Actions Skill
Master GitHub Actions for CI/CD pipelines, automated testing, deployments, and repository automation. This skill covers workflow syntax, triggers, jobs, matrix builds, caching, artifacts, reusable workflows, and secrets management.
When to Use This Skill
USE when:
-
Building CI/CD pipelines for GitHub repositories
-
Automating tests across multiple OS/language versions
-
Creating release and deployment workflows
-
Publishing packages to npm, PyPI, Docker Hub
-
Automating issue triage and PR management
-
Scheduling periodic maintenance tasks
-
Building reusable workflow components
-
Implementing GitOps deployment patterns
DON'T USE when:
-
Repository not hosted on GitHub (use Jenkins, GitLab CI)
-
Need complex DAG-based workflow orchestration (use Airflow)
-
Require visual workflow design (use n8n, Activepieces)
-
Self-hosted runners not available for compute-intensive tasks
-
Need real-time event processing (use dedicated message queues)
Prerequisites
GitHub Repository Setup
Create workflow directory
mkdir -p .github/workflows
Verify GitHub CLI installed
gh --version
Authenticate with GitHub
gh auth login
Check workflow permissions
gh api repos/{owner}/{repo}/actions/permissions
Local Testing with act
Install act for local workflow testing
macOS
brew install act
Linux
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
Verify installation
act --version
Run workflow locally
act -l # List available workflows act push # Simulate push event act pull_request # Simulate PR event act -j build # Run specific job
Workflow Linting
Install actionlint
brew install actionlint # macOS go install github.com/rhysd/actionlint/cmd/actionlint@latest # Go
Lint workflows
actionlint .github/workflows/*.yml
YAML validation
pip install yamllint yamllint .github/workflows/
Core Capabilities
- Basic Workflow Structure
.github/workflows/ci.yml
name: CI Pipeline
Workflow triggers
on: push: branches: [main, develop] paths: - 'src/' - 'tests/' - 'pyproject.toml' pull_request: branches: [main] types: [opened, synchronize, reopened] 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
Environment variables for all jobs
env: PYTHON_VERSION: '3.11' NODE_VERSION: '20' REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}
Concurrency control
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
Workflow permissions
permissions: contents: read packages: write pull-requests: write
jobs: lint: name: Code Quality runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- name: Install linters
run: |
pip install ruff mypy
- name: Run linting
run: |
ruff check src/
ruff format --check src/
- name: Type checking
run: mypy src/ --ignore-missing-imports
test: name: Test Suite needs: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- name: Install dependencies
run: |
pip install -e ".[dev]"
- name: Run tests
run: |
pytest tests/ -v --cov=src --cov-report=xml --cov-report=html
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
fail_ci_if_error: true
build: name: Build Package needs: test runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} steps: - uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install build tools
run: pip install build
- name: Get version
id: version
run: |
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build package
run: python -m build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist-${{ steps.version.outputs.version }}
path: dist/
retention-days: 5
2. Matrix Builds for Cross-Platform Testing
.github/workflows/matrix-test.yml
name: Cross-Platform Tests
on: push: branches: [main] pull_request:
jobs: test-matrix: name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }}) runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
max-parallel: 4
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12']
include:
# Additional configuration for specific combinations
- os: ubuntu-latest
python-version: '3.12'
coverage: true
# Experimental Python version
- os: ubuntu-latest
python-version: '3.13-dev'
experimental: true
exclude:
# Skip Windows + Python 3.10 (known issues)
- os: windows-latest
python-version: '3.10'
continue-on-error: ${{ matrix.experimental || false }}
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'
cache-dependency-path: |
pyproject.toml
requirements*.txt
- name: Install dependencies (Unix)
if: runner.os != 'Windows'
run: |
pip install -e ".[dev]"
- name: Install dependencies (Windows)
if: runner.os == 'Windows'
run: |
pip install -e ".[dev]"
shell: pwsh
- name: Run tests
run: |
pytest tests/ -v --tb=short
env:
CI: true
PLATFORM: ${{ matrix.os }}
- name: Run tests with coverage
if: matrix.coverage
run: |
pytest tests/ -v --cov=src --cov-report=xml
- name: Upload coverage
if: matrix.coverage
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
test-summary: name: Test Summary needs: test-matrix if: always() runs-on: ubuntu-latest steps: - name: Check matrix results run: | if [ "${{ needs.test-matrix.result }}" == "failure" ]; then echo "Some matrix jobs failed" exit 1 fi echo "All matrix jobs passed"
- Caching Strategies
.github/workflows/caching.yml
name: Build with Caching
on: [push, pull_request]
jobs: build-with-cache: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
# Python pip cache
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: |
requirements.txt
requirements-dev.txt
# Node modules cache
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
# Custom cache for build artifacts
- name: Cache build outputs
uses: actions/cache@v4
id: build-cache
with:
path: |
build/
dist/
.pytest_cache/
key: build-${{ runner.os }}-${{ hashFiles('src/**', 'pyproject.toml') }}
restore-keys: |
build-${{ runner.os }}-
# Docker layer cache
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: docker-${{ runner.os }}-${{ hashFiles('Dockerfile') }}
restore-keys: |
docker-${{ runner.os }}-
# Rust cargo cache
- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Skip build if cached
if: steps.build-cache.outputs.cache-hit == 'true'
run: echo "Using cached build artifacts"
- name: Build
if: steps.build-cache.outputs.cache-hit != 'true'
run: |
pip install -e ".[dev]"
python -m build
- name: Run tests
run: pytest tests/ -v
4. Artifact Management
.github/workflows/artifacts.yml
name: Build and Release Artifacts
on: push: tags: ['v*'] workflow_dispatch:
jobs: build-artifacts: name: Build (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: matrix: include: - os: ubuntu-latest artifact_name: app-linux-x64 asset_name: app-linux-x64.tar.gz - os: macos-latest artifact_name: app-macos-x64 asset_name: app-macos-x64.tar.gz - os: windows-latest artifact_name: app-windows-x64 asset_name: app-windows-x64.zip
steps:
- uses: actions/checkout@v4
- name: Build application
run: |
echo "Building for ${{ matrix.os }}"
mkdir -p dist
# Build commands here
- name: Package (Unix)
if: runner.os != 'Windows'
run: |
tar -czvf ${{ matrix.asset_name }} -C dist .
- name: Package (Windows)
if: runner.os == 'Windows'
run: |
Compress-Archive -Path dist/* -DestinationPath ${{ matrix.asset_name }}
shell: pwsh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.asset_name }}
retention-days: 30
compression-level: 9
create-release: name: Create Release needs: build-artifacts runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
merge-multiple: false
- name: List artifacts
run: find artifacts/ -type f
- name: Generate changelog
id: changelog
run: |
# Extract changelog for this version
VERSION=${GITHUB_REF#refs/tags/}
echo "version=$VERSION" >> $GITHUB_OUTPUT
# Generate release notes
cat << EOF > release_notes.md
## What's Changed
See [CHANGELOG.md](CHANGELOG.md) for details.
## Assets
- \`app-linux-x64.tar.gz\` - Linux (x64)
- \`app-macos-x64.tar.gz\` - macOS (x64)
- \`app-windows-x64.zip\` - Windows (x64)
EOF
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: Release ${{ steps.changelog.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }}
files: |
artifacts/**/*
generate_release_notes: true
5. Reusable Workflows
.github/workflows/reusable-python-ci.yml
name: Reusable Python CI
on: workflow_call: inputs: python-version: description: 'Python version to use' required: false type: string default: '3.11' test-command: description: 'Test command to run' required: false type: string default: 'pytest tests/ -v' coverage: description: 'Enable coverage reporting' required: false type: boolean default: true install-extras: description: 'Package extras to install' required: false type: string default: 'dev' secrets: CODECOV_TOKEN: description: 'Codecov upload token' required: false outputs: coverage-percent: description: 'Test coverage percentage' value: ${{ jobs.test.outputs.coverage }}
jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
cache: 'pip'
- name: Install linters
run: pip install ruff
- name: Run ruff
run: ruff check .
test: name: Test needs: lint runs-on: ubuntu-latest outputs: coverage: ${{ steps.coverage.outputs.percent }} steps: - uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
cache: 'pip'
- name: Install dependencies
run: pip install -e ".[${{ inputs.install-extras }}]"
- name: Run tests
run: ${{ inputs.test-command }}
- name: Run tests with coverage
if: inputs.coverage
run: |
pip install pytest-cov
pytest tests/ -v --cov=src --cov-report=xml --cov-report=term-missing
- name: Extract coverage
if: inputs.coverage
id: coverage
run: |
COVERAGE=$(python -c "import xml.etree.ElementTree as ET; print(f\"{float(ET.parse('coverage.xml').getroot().get('line-rate')) * 100:.1f}\")")
echo "percent=$COVERAGE" >> $GITHUB_OUTPUT
- name: Upload coverage
if: inputs.coverage && secrets.CODECOV_TOKEN
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
.github/workflows/ci.yml - Using the reusable workflow
name: CI
on: push: branches: [main] pull_request:
jobs: python-ci: uses: ./.github/workflows/reusable-python-ci.yml with: python-version: '3.11' test-command: 'pytest tests/ -v --tb=short' coverage: true install-extras: 'dev,test' secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- Composite Actions
.github/actions/setup-project/action.yml
name: 'Setup Project' description: 'Set up Python environment with dependencies and caching'
inputs: python-version: description: 'Python version' required: false default: '3.11' install-dev: description: 'Install dev dependencies' required: false default: 'true' working-directory: description: 'Working directory' required: false default: '.'
outputs: python-path: description: 'Path to Python executable' value: ${{ steps.setup-python.outputs.python-path }} cache-hit: description: 'Whether cache was hit' value: ${{ steps.pip-cache.outputs.cache-hit }}
runs: using: 'composite' steps: - name: Set up Python id: setup-python uses: actions/setup-python@v5 with: python-version: ${{ inputs.python-version }}
- name: Get pip cache dir
id: pip-cache-dir
shell: bash
run: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache pip
id: pip-cache
uses: actions/cache@v4
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: pip-${{ runner.os }}-${{ inputs.python-version }}-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }}
restore-keys: |
pip-${{ runner.os }}-${{ inputs.python-version }}-
pip-${{ runner.os }}-
- name: Install base dependencies
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
python -m pip install --upgrade pip wheel setuptools
- name: Install project
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
if [ "${{ inputs.install-dev }}" == "true" ]; then
pip install -e ".[dev]"
else
pip install -e .
fi
Using the composite action
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup Project
uses: ./.github/actions/setup-project
with:
python-version: '3.11'
install-dev: 'true'
- name: Run tests
run: pytest tests/ -v
7. Secrets and Environment Management
.github/workflows/deploy.yml
name: Deploy
on: push: branches: [main] workflow_dispatch: inputs: environment: type: environment description: 'Select environment' required: true
jobs: deploy-staging: name: Deploy to Staging runs-on: ubuntu-latest environment: name: staging url: https://staging.example.com steps: - uses: actions/checkout@v4
- 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 staging
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
ENVIRONMENT: staging
run: |
echo "Deploying to staging..."
# Deployment commands
deploy-production: name: Deploy to Production needs: deploy-staging runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' environment: name: production url: https://example.com concurrency: group: production-deploy cancel-in-progress: false steps: - uses: actions/checkout@v4
- 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 production
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
ENVIRONMENT: production
run: |
echo "Deploying to production..."
# Deployment commands
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Production deployment failed!",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Production Deployment Failed*\nWorkflow: ${{ github.workflow }}\nRun: ${{ github.run_id }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
8. Container Builds and Registry Publishing
.github/workflows/docker.yml
name: Docker Build and Push
on: push: branches: [main] tags: ['v*'] pull_request: branches: [main]
env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}
jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write # For signing
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
if: github.event_name != 'pull_request'
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=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}
- name: Sign image
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@v3
- name: Sign container image
if: github.event_name != 'pull_request'
run: |
cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
9. Scheduled Workflows and Maintenance
.github/workflows/maintenance.yml
name: Repository Maintenance
on: schedule: # Run every Monday at 6 AM UTC - cron: '0 6 * * 1' workflow_dispatch:
permissions: contents: write issues: write pull-requests: write
jobs: dependency-update: name: Update Dependencies runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Update dependencies
run: |
pip install pip-tools
pip-compile --upgrade requirements.in -o requirements.txt
pip-compile --upgrade requirements-dev.in -o requirements-dev.txt
- name: Create PR if changes
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore(deps): update dependencies'
title: 'chore(deps): Weekly dependency update'
body: |
Automated weekly dependency update.
Please review the changes and merge if CI passes.
branch: deps/weekly-update
delete-branch: true
labels: dependencies,automated
stale-issues: name: Close Stale Issues runs-on: ubuntu-latest steps: - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. stale-pr-message: | This PR has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. stale-issue-label: 'stale' stale-pr-label: 'stale' days-before-stale: 60 days-before-close: 7 exempt-issue-labels: 'pinned,security,enhancement' exempt-pr-labels: 'pinned,security'
security-scan: name: Security Audit runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
- name: Python safety check
run: |
pip install safety
safety check --full-report || true
10. PR Automation and Checks
.github/workflows/pr-checks.yml
name: PR Checks
on: pull_request: types: [opened, synchronize, reopened, edited]
permissions: contents: read pull-requests: write
jobs: validate-pr: name: Validate PR runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Check PR title
uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
requireScope: false
subjectPattern: ^.{1,50}$
subjectPatternError: |
PR title must be 50 characters or less
- name: Check for breaking changes
if: contains(github.event.pull_request.title, '!')
run: |
echo "::warning::This PR contains breaking changes"
- name: Add size labels
uses: codelytv/pr-size-labeler@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xs_label: 'size/xs'
xs_max_size: 10
s_label: 'size/s'
s_max_size: 100
m_label: 'size/m'
m_max_size: 500
l_label: 'size/l'
l_max_size: 1000
xl_label: 'size/xl'
fail_if_xl: false
message_if_xl: |
This PR is very large. Please consider breaking it into smaller PRs.
auto-assign: name: Auto Assign runs-on: ubuntu-latest steps: - name: Auto-assign author uses: kentaro-m/auto-assign-action@v1 with: configuration-path: '.github/auto-assign.yml'
label-pr: name: Label PR runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler.yml
Integration Examples
Integration with Slack Notifications
.github/workflows/notify.yml
name: Slack Notifications
on: workflow_run: workflows: ["CI Pipeline"] types: [completed]
jobs: notify: runs-on: ubuntu-latest steps: - name: Notify Slack uses: slackapi/slack-github-action@v1 with: payload: | { "blocks": [ { "type": "header", "text": { "type": "plain_text", "text": "${{ github.event.workflow_run.conclusion == 'success' && 'Build Succeeded' || 'Build Failed' }}" } }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "Repository:\n${{ github.repository }}" }, { "type": "mrkdwn", "text": "Branch:\n${{ github.event.workflow_run.head_branch }}" }, { "type": "mrkdwn", "text": "Commit:\n<${{ github.event.workflow_run.head_commit.url }}|${{ github.event.workflow_run.head_sha }}>" }, { "type": "mrkdwn", "text": "Author:\n${{ github.event.workflow_run.head_commit.author.name }}" } ] }, { "type": "actions", "elements": [ { "type": "button", "text": { "type": "plain_text", "text": "View Run" }, "url": "${{ github.event.workflow_run.html_url }}" } ] } ] } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
Integration with AWS Deployment
.github/workflows/aws-deploy.yml
name: AWS Deployment
on: push: branches: [main]
jobs: deploy-lambda: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install SAM CLI
run: pip install aws-sam-cli
- name: Build
run: sam build
- name: Deploy
run: |
sam deploy \
--no-confirm-changeset \
--no-fail-on-empty-changeset \
--stack-name my-app \
--capabilities CAPABILITY_IAM \
--parameter-overrides Environment=production
Best Practices
- Security Best Practices
Always pin action versions with SHA
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Use minimum required permissions
permissions: contents: read packages: write
Never hardcode secrets
env: API_KEY: ${{ secrets.API_KEY }} # Good
API_KEY: "sk-1234567890" # Never do this
Use OIDC for cloud provider authentication
- uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- Performance Optimization
Use caching aggressively
- uses: actions/cache@v4 with: path: ~/.cache/pip key: pip-${{ hashFiles('requirements.txt') }}
Use matrix fail-fast wisely
strategy: fail-fast: false # Continue other jobs on failure
Limit concurrent runs
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
- Maintainability
Use reusable workflows
jobs: test: uses: ./.github/workflows/reusable-test.yml
Extract common steps into composite actions
- uses: ./.github/actions/setup-project
Use environment variables for configuration
env: PYTHON_VERSION: '3.11' NODE_VERSION: '20'
- Error Handling
Use continue-on-error for non-critical steps
- name: Optional step continue-on-error: true run: optional-command
Add timeout to prevent stuck jobs
jobs: build: timeout-minutes: 30
Always clean up resources
- name: Cleanup if: always() run: cleanup-command
Troubleshooting
Common Issues
Issue: Workflow not triggering
Check trigger paths
on: push: paths: - 'src/**' # Only triggers for src/ changes
Verify branch names match
on: push: branches: - main - 'release/*' # Use quotes for patterns
Issue: Cache not restoring
Verify cache key matches
- uses: actions/cache@v4 with: path: ~/.cache/pip key: pip-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }} restore-keys: | pip-${{ runner.os }}-
Issue: Secrets not available
Secrets not available in forks
- name: Deploy if: github.event.pull_request.head.repo.full_name == github.repository env: SECRET: ${{ secrets.MY_SECRET }}
Issue: Permission denied
Add required permissions
permissions: contents: write packages: write pull-requests: write
Debugging Workflows
Enable debug logging
jobs: debug: runs-on: ubuntu-latest steps: - name: Dump context env: GITHUB_CONTEXT: ${{ toJson(github) }} JOB_CONTEXT: ${{ toJson(job) }} STEPS_CONTEXT: ${{ toJson(steps) }} run: | echo "GitHub context:" echo "$GITHUB_CONTEXT" echo "Job context:" echo "$JOB_CONTEXT"
Version History
Version Date Changes
1.0.0 2026-01-17 Initial release with comprehensive CI/CD patterns
Resources
-
GitHub Actions Documentation
-
Workflow Syntax Reference
-
GitHub Actions Marketplace
-
act - Local Testing
-
actionlint - Workflow Linter
This skill provides production-ready patterns for GitHub Actions workflows, tested across multiple repositories and CI/CD pipelines.