GitHub Actions: Complete CI/CD Guide (2026)
GitHub Actions is the most popular CI/CD platform for open-source and private repositories. It runs workflows defined as YAML files directly in your repository — no separate CI server to manage. This guide covers the full workflow authoring experience: triggers, jobs, matrix builds, Docker pipelines, AWS deployment with OIDC (no static credentials), and reusable workflows.
Table of Contents
1. Workflow Anatomy
A complete workflow for a Java Spring Boot app:
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
JAVA_VERSION: '21'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: temurin
cache: maven
- name: Run tests
run: mvn test -B
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: target/surefire-reports/
2. Triggers
on:
# Push to specific branches
push:
branches: [main]
paths-ignore: ['**.md', 'docs/**']
# Pull request events
pull_request:
types: [opened, synchronize, reopened]
branches: [main]
# Manual trigger with inputs
workflow_dispatch:
inputs:
environment:
description: 'Deploy environment'
required: true
default: staging
type: choice
options: [staging, production]
# Scheduled (cron syntax — UTC)
schedule:
- cron: '0 2 * * 1-5' # 2 AM UTC weekdays
# Triggered by another workflow completing
workflow_run:
workflows: ["CI/CD Pipeline"]
types: [completed]
branches: [main]
3. Jobs and Steps
jobs:
build:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
# Conditional step
- name: Run only on main
if: github.ref == 'refs/heads/main'
run: echo "Deploying to production"
# Multi-line run
- name: Build and test
run: |
mvn clean package -DskipTests
echo "Build completed: $(ls -la target/*.jar)"
# Set output for use in other jobs
- name: Set image tag
id: meta
run: echo "tags=myapp:${{ github.sha }}" >> $GITHUB_OUTPUT
deploy:
needs: build # runs after build job
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production # requires manual approval if configured
steps:
- name: Use build output
run: echo "Deploying ${{ needs.build.outputs.image-tag }}"
4. Matrix Builds
jobs:
test-matrix:
strategy:
fail-fast: false # don't cancel other jobs if one fails
matrix:
java: ['17', '21']
os: [ubuntu-latest, windows-latest]
include:
- java: '21'
os: ubuntu-latest
extra-args: '--enable-preview'
exclude:
- java: '17'
os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: temurin
- run: mvn test ${{ matrix.extra-args }}
5. Caching Dependencies
- uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Node.js — use setup-node built-in cache
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Python — use setup-python built-in cache
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
6. Docker Build and Push
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
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: ${{ 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
7. Deploy to AWS with OIDC (No Static Credentials)
OIDC lets GitHub Actions authenticate to AWS using short-lived tokens — no AWS access keys stored in secrets:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
aws-region: us-east-1
- name: Login to ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag and push to ECR
run: |
IMAGE_URI="${{ steps.login-ecr.outputs.registry }}/myapp:${{ github.sha }}"
docker build -t $IMAGE_URI .
docker push $IMAGE_URI
- name: Deploy to EKS
run: |
aws eks update-kubeconfig --name my-cluster --region us-east-1
kubectl set image deployment/myapp myapp=$IMAGE_URI
kubectl rollout status deployment/myapp
token.actions.githubusercontent.com:sub: repo:myorg/myrepo:ref:refs/heads/main to restrict which branches can assume the role.8. Reusable Workflows
# .github/workflows/reusable-test.yml
on:
workflow_call:
inputs:
java-version:
type: string
default: '21'
secrets:
SONAR_TOKEN:
required: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: ${{ inputs.java-version }}
distribution: temurin
- run: mvn verify sonar:sonar
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# Calling the reusable workflow:
# .github/workflows/main.yml
jobs:
run-tests:
uses: ./.github/workflows/reusable-test.yml
with:
java-version: '21'
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Frequently Asked Questions
GitHub Actions vs Jenkins — which should I use?
GitHub Actions if your code is on GitHub — zero infrastructure to manage, native integration, generous free tier. Jenkins if you need on-premise CI, complex custom logic, or you're not on GitHub. GitHub Actions has largely replaced Jenkins for cloud-native teams.
How do I store and use secrets in GitHub Actions?
Add secrets in Settings → Secrets and variables → Actions. Access via ${{ secrets.MY_SECRET }}. Secrets are masked in logs. For environment-specific secrets, use Environments with protection rules and required reviewers before production deployments.
How do I debug a failing workflow?
Enable debug logging by setting secret ACTIONS_STEP_DEBUG=true. Use tmate action for an interactive SSH session into the runner. Add continue-on-error: true to failing steps to see all output. Download artifacts for test reports.
What are GitHub Actions usage limits?
Free tier: 2,000 minutes/month on public repos (unlimited), 500 MB storage. Private repos: 2,000 minutes free then $0.008/min for Linux. Self-hosted runners are free but you manage the infrastructure.