From Beginner to Expert
Table of Contents
- Introduction to GitHub Actions
- Getting Started
- Workflow Fundamentals
- Jobs and Steps
- Actions and Marketplace
- Environment Variables and Secrets
- Matrix Builds and Strategies
- Conditional Workflows
- Custom Actions
- Advanced Patterns
- Security Best Practices
- Monitoring and Debugging
- Enterprise Features
- Real-World Examples
1. Introduction to GitHub Actions
GitHub Actions is a powerful CI/CD platform that allows you to automate your software development workflows directly in your GitHub repository. It enables you to build, test, package, release, and deploy your code right from GitHub.
What are GitHub Actions?
GitHub Actions makes it easy to automate all your software workflows with world-class CI/CD. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want.
flowchart TD
A[Developer pushes code] --> B[GitHub Repository]
B --> C[Trigger Event]
C --> D[GitHub Actions Runner]
D --> E[Execute Workflow]
E --> F[Run Jobs]
F --> G[Execute Steps]
G --> H[Complete Workflow]
H --> I[Report Results]Key Components
- Workflows: Automated procedures added to your repository
- Events: Specific activities that trigger a workflow
- Jobs: Set of steps that execute on the same runner
- Steps: Individual tasks that can run commands or actions
- Actions: Reusable units of code
- Runners: Servers that run your workflows
Benefits
- Integrated: Native to GitHub ecosystem
- Flexible: Supports any language, any OS
- Powerful: Matrix builds, environments, secrets
- Community: Large marketplace of actions
- Free: Generous free tier for public repositories
2. Getting Started
Your First Workflow
To create your first GitHub Action workflow, you need to add a YAML file in the .github/workflows directory of your repository.
# .github/workflows/hello-world.yml
name: Hello World
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
hello:
runs-on: ubuntu-latest
steps:
- name: Say Hello
run: echo "Hello, World!"YAMLWorkflow Structure
graph TB
A[Workflow File] --> B[name]
A --> C[on: Events]
A --> D[jobs]
C --> C1[push]
C --> C2[pull_request]
C --> C3[schedule]
C --> C4[workflow_dispatch]
D --> D1[job1]
D --> D2[job2]
D1 --> E1[runs-on]
D1 --> F1[steps]
F1 --> G1[Step 1]
F1 --> G2[Step 2]
F1 --> G3[Step N]
G1 --> H1[name]
G1 --> I1[run/uses]Repository Setup
- Create
.github/workflowsdirectory - Add workflow YAML files
- Commit and push to trigger workflows
3. Workflow Fundamentals
Workflow Events
GitHub Actions can be triggered by various events:
on:
# Trigger on push to main branch
push:
branches: [ main ]
paths: [ 'src/**' ]
# Trigger on pull requests
pull_request:
branches: [ main, develop ]
types: [opened, synchronize, reopened]
# Schedule workflows
schedule:
- cron: '0 2 * * 1-5' # Run at 2 AM, Monday to Friday
# Manual trigger
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
required: true
default: 'staging'
type: choice
options:
- staging
- production
# Repository events
issues:
types: [opened, closed]
release:
types: [published]YAMLEvent Flow Diagram
sequenceDiagram
participant Developer
participant GitHub
participant Runner
participant Workflow
Developer->>GitHub: Push code
GitHub->>Workflow: Trigger event
Workflow->>Runner: Request runner
Runner->>Workflow: Execute jobs
Workflow->>GitHub: Report status
GitHub->>Developer: Show resultsWorkflow Syntax
name: Workflow Name
run-name: Custom run name # Optional
on:
# Event configuration
env:
# Global environment variables
NODE_VERSION: 18
defaults:
run:
shell: bash
working-directory: ./app
jobs:
# Job definitionsYAML4. Jobs and Steps
Job Configuration
Jobs run in parallel by default but can be configured to run sequentially:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: npm test
build:
needs: test # Wait for test job to complete
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build application
run: npm run build
deploy:
needs: [test, build] # Wait for both jobs
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: echo "Deploying..."YAMLJob Dependencies
graph TD
A[test] --> C[deploy]
B[build] --> C[deploy]
style A fill:#e1f5fe
style B fill:#e1f5fe
style C fill:#f3e5f5Runner Types
jobs:
linux-job:
runs-on: ubuntu-latest
windows-job:
runs-on: windows-latest
macos-job:
runs-on: macos-latest
self-hosted-job:
runs-on: [self-hosted, linux, x64]
specific-version:
runs-on: ubuntu-20.04YAMLStep Types
steps:
# Checkout code
- uses: actions/checkout@v3
# Run shell command
- name: List files
run: ls -la
# Multi-line script
- name: Complex script
run: |
echo "Starting deployment"
./deploy.sh
echo "Deployment complete"
# Use action with parameters
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
# Conditional step
- name: Deploy to staging
if: github.ref == 'refs/heads/develop'
run: echo "Deploying to staging"YAML5. Actions and Marketplace
Using Actions
Actions are reusable units of code that you can use in your workflows:
steps:
# Official GitHub actions
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
# Community actions
- uses: docker/build-push-action@v4
with:
context: .
push: true
tags: myapp:latest
# Action from another repository
- uses: username/action-name@v1
with:
parameter: valueYAMLPopular Actions
mindmap
root((GitHub Actions Marketplace))
Setup Actions
actions/checkout
actions/setup-node
actions/setup-python
actions/setup-java
Build & Deploy
docker/build-push-action
azure/webapps-deploy
peaceiris/actions-gh-pages
Testing
codecov/codecov-action
dorny/test-reporter
mikepenz/action-junit-report
Security
github/codeql-action
snyk/actions
aquasecurity/trivy-action
Utilities
actions/cache
actions/upload-artifact
actions/download-artifactAction Inputs and Outputs
# Using action outputs
- name: Get current time
id: time
uses: josStorer/get-current-time@v2
with:
format: YYYYMMDD-HHmmss
utcOffset: "+08:00"
- name: Use the time
run: echo "Current time is ${{ steps.time.outputs.time }}"
# Passing secrets to actions
- uses: some-action@v1
with:
api-key: ${{ secrets.API_KEY }}YAML6. Environment Variables and Secrets
Environment Variables
env:
# Global environment variables
NODE_ENV: production
API_URL: https://api.example.com
jobs:
build:
runs-on: ubuntu-latest
env:
# Job-level environment variables
BUILD_DIR: ./dist
steps:
- name: Build with environment variables
env:
# Step-level environment variables
CUSTOM_VAR: custom-value
run: |
echo "NODE_ENV: $NODE_ENV"
echo "API_URL: $API_URL"
echo "BUILD_DIR: $BUILD_DIR"
echo "CUSTOM_VAR: $CUSTOM_VAR"YAMLDefault Environment Variables
GitHub provides many default environment variables:
steps:
- name: Show GitHub context
run: |
echo "Repository: $GITHUB_REPOSITORY"
echo "Actor: $GITHUB_ACTOR"
echo "SHA: $GITHUB_SHA"
echo "Ref: $GITHUB_REF"
echo "Workspace: $GITHUB_WORKSPACE"
echo "Run ID: $GITHUB_RUN_ID"YAMLManaging Secrets
flowchart LR
A[Developer] --> B[Repository Settings]
B --> C[Secrets and Variables]
C --> D[Actions Secrets]
D --> E[Add Secret]
E --> F[Use in Workflow]
F --> G[Encrypted in Transit]
F --> H[Redacted in Logs]Using Secrets
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws s3 sync ./dist s3://my-bucket
- name: Send notification
uses: some-notification-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
message: "Deployment successful!"YAMLEnvironment-Specific Secrets
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Links to environment secrets
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
run: ./deploy.shYAML7. Matrix Builds and Strategies
Basic Matrix Strategy
Matrix strategies allow you to run the same job with different configurations:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [14, 16, 18]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm testYAMLMatrix Visualization
graph TD
A[Matrix Job] --> B[ubuntu-latest + Node 14]
A --> C[ubuntu-latest + Node 16]
A --> D[ubuntu-latest + Node 18]
A --> E[windows-latest + Node 14]
A --> F[windows-latest + Node 16]
A --> G[windows-latest + Node 18]
A --> H[macos-latest + Node 14]
A --> I[macos-latest + Node 16]
A --> J[macos-latest + Node 18]Advanced Matrix Configuration
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # Don't stop other jobs if one fails
max-parallel: 2 # Limit concurrent jobs
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [16, 18]
include:
# Add specific combinations
- os: macos-latest
node-version: 18
experimental: true
exclude:
# Remove specific combinations
- os: windows-latest
node-version: 16
steps:
- name: Show matrix values
run: |
echo "OS: ${{ matrix.os }}"
echo "Node: ${{ matrix.node-version }}"
echo "Experimental: ${{ matrix.experimental }}"YAMLDynamic Matrix
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: |
if [ "${{ github.event_name }}" == "pull_request" ]; then
echo "matrix=[\"ubuntu-latest\"]" >> $GITHUB_OUTPUT
else
echo "matrix=[\"ubuntu-latest\", \"windows-latest\", \"macos-latest\"]" >> $GITHUB_OUTPUT
fi
test:
needs: setup
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: ${{ fromJSON(needs.setup.outputs.matrix) }}
steps:
- run: echo "Testing on ${{ matrix.os }}"YAML8. Conditional Workflows
Basic Conditionals
jobs:
deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: echo "Deploying to production"
test:
runs-on: ubuntu-latest
steps:
- name: Always run tests
run: npm test
- name: Run integration tests
if: github.event_name == 'pull_request'
run: npm run test:integration
- name: Deploy to staging
if: |
github.ref == 'refs/heads/develop' &&
github.event_name == 'push'
run: ./deploy-staging.shYAMLConditional Expressions
steps:
- name: Check conditions
if: |
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.force_deploy == 'true')
run: echo "Condition met for deployment"
- name: Check step status
if: always() # Run even if previous steps failed
run: echo "This always runs"
- name: Only on success
if: success()
run: echo "Previous steps succeeded"
- name: Only on failure
if: failure()
run: echo "Something failed"YAMLConditional Job Dependencies
flowchart TD
A[push event] --> B{Is main branch?}
B -->|Yes| C[production-deploy]
B -->|No| D{Is develop branch?}
D -->|Yes| E[staging-deploy]
D -->|No| F[test-only]
G[pull_request event] --> F
style C fill:#ffeb3b
style E fill:#ff9800
style F fill:#4caf50Environment-Based Conditionals
jobs:
deploy:
runs-on: ubuntu-latest
if: |
(github.ref == 'refs/heads/main' && vars.ENVIRONMENT == 'production') ||
(github.ref == 'refs/heads/develop' && vars.ENVIRONMENT == 'staging')
environment: ${{ vars.ENVIRONMENT }}
steps:
- name: Deploy
run: |
echo "Deploying to ${{ vars.ENVIRONMENT }}"YAML9. Custom Actions
Types of Custom Actions
graph TD
A[Custom Actions] --> B[JavaScript Actions]
A --> C[Docker Actions]
A --> D[Composite Actions]
B --> B1[Runs on Node.js]
B --> B2[Fast execution]
B --> B3[GitHub API access]
C --> C1[Any language/runtime]
C --> C2[Isolated environment]
C --> C3[Slower startup]
D --> D1[Combine existing actions]
D --> D2[Shell scripts]
D --> D3[Reusable workflows]JavaScript Action
Create a JavaScript action:
// action.js
const core = require('@actions/core');
const github = require('@actions/github');
try {
const nameToGreet = core.getInput('who-to-greet');
console.log(`Hello ${nameToGreet}!`);
const time = (new Date()).toTimeString();
core.setOutput("time", time);
// Get the JSON webhook payload for the event that triggered the workflow
const payload = JSON.stringify(github.context.payload, undefined, 2);
console.log(`The event payload: ${payload}`);
} catch (error) {
core.setFailed(error.message);
}JavaScript# action.yml
name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
who-to-greet:
description: 'Who to greet'
required: true
default: 'World'
outputs:
time:
description: 'The time we greeted you'
runs:
using: 'node16'
main: 'action.js'YAML# package.json
{
"name": "hello-world-action",
"version": "1.0.0",
"main": "action.js",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
}
}JSONDocker Action
# Dockerfile
FROM alpine:3.10
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]Dockerfile#!/bin/sh -l
# entrypoint.sh
echo "Hello $1"
time=$(date)
echo "time=$time" >> $GITHUB_OUTPUTBash# action.yml
name: 'Hello World Docker'
description: 'Greet someone using Docker'
inputs:
who-to-greet:
description: 'Who to greet'
required: true
default: 'World'
outputs:
time:
description: 'The time we greeted you'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.who-to-greet }}YAMLComposite Action
# action.yml
name: 'Setup Node.js with Cache'
description: 'Setup Node.js and configure npm cache'
inputs:
node-version:
description: 'Node.js version'
required: true
default: '18'
working-directory:
description: 'Working directory'
required: false
default: '.'
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node-version }}
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
shell: bash
working-directory: ${{ inputs.working-directory }}YAML10. Advanced Patterns
Reusable Workflows
# .github/workflows/reusable-deploy.yml
name: Reusable Deployment
on:
workflow_call:
inputs:
environment:
required: true
type: string
version:
required: false
type: string
default: 'latest'
secrets:
deploy-token:
required: true
outputs:
deployment-url:
description: "URL of the deployment"
value: ${{ jobs.deploy.outputs.url }}
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
outputs:
url: ${{ steps.deploy.outputs.url }}
steps:
- name: Deploy
id: deploy
run: |
echo "Deploying ${{ inputs.version }} to ${{ inputs.environment }}"
echo "url=https://${{ inputs.environment }}.example.com" >> $GITHUB_OUTPUTYAML# .github/workflows/main.yml
name: Main Workflow
on:
push:
branches: [main]
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
version: ${{ github.sha }}
secrets:
deploy-token: ${{ secrets.STAGING_DEPLOY_TOKEN }}
deploy-production:
needs: deploy-staging
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
version: ${{ github.sha }}
secrets:
deploy-token: ${{ secrets.PRODUCTION_DEPLOY_TOKEN }}YAMLWorkflow Dependencies
graph TD
A[Push to main] --> B[Build]
B --> C[Test Unit]
B --> D[Test Integration]
C --> E[Security Scan]
D --> E
E --> F[Deploy Staging]
F --> G[E2E Tests]
G --> H{Manual Approval}
H -->|Approved| I[Deploy Production]
H -->|Rejected| J[Rollback]Artifacts and Caching
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Cache 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-
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
# Upload artifacts
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: dist-files
path: dist/
retention-days: 30
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Download artifacts
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: dist-files
path: dist/
- name: Run tests
run: npm testYAMLMulti-Stage Deployments
name: Multi-Stage Deployment
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v3
- id: version
run: echo "version=$(date +%Y%m%d%H%M%S)" >> $GITHUB_OUTPUT
- name: Build
run: echo "Building version ${{ steps.version.outputs.version }}"
deploy-dev:
needs: build
runs-on: ubuntu-latest
environment: development
steps:
- name: Deploy to dev
run: echo "Deploying ${{ needs.build.outputs.version }} to dev"
deploy-staging:
needs: [build, deploy-dev]
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: echo "Deploying ${{ needs.build.outputs.version }} to staging"
deploy-production:
needs: [build, deploy-staging]
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: echo "Deploying ${{ needs.build.outputs.version }} to production"YAML11. Security Best Practices
Security Considerations
mindmap
root((Security))
Secrets Management
Use repository secrets
Environment-specific secrets
Avoid logging secrets
Rotate regularly
Permissions
GITHUB_TOKEN permissions
Least privilege principle
Read-only when possible
Dependencies
Pin action versions
Verify action sources
Regular updates
Dependency scanning
Code Security
Static analysis
Vulnerability scanning
Secure coding practices
Code signingSecure Workflow Example
name: Secure Workflow
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
actions: read
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4 # Pinned to specific version
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.12.0 # Pinned version
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'
code-analysis:
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: javascript
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2YAMLSecret Management Best Practices
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Validate secrets exist
run: |
if [ -z "${{ secrets.API_KEY }}" ]; then
echo "API_KEY secret is not set"
exit 1
fi
- name: Use secrets securely
env:
API_KEY: ${{ secrets.API_KEY }}
run: |
# Good: Use environment variable
curl -H "Authorization: Bearer $API_KEY" api.example.com
# Bad: Don't echo secrets
# echo "API Key: $API_KEY"
- name: Clean up sensitive data
if: always()
run: |
unset API_KEY
rm -f /tmp/sensitive-fileYAMLGITHUB_TOKEN Permissions
name: Limited Permissions
on: [push, pull_request]
permissions:
contents: read # Read repository contents
packages: write # Publish packages
pull-requests: write # Comment on PRs
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Build and publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Build and publish with minimal permissions
npm run build
npm publishYAML12. Monitoring and Debugging
Debugging Workflows
name: Debug Workflow
on: [push]
jobs:
debug:
runs-on: ubuntu-latest
steps:
- name: Enable debug logging
run: echo "::debug::This is a debug message"
- name: Show environment
run: |
echo "::group::Environment Variables"
env | sort
echo "::endgroup::"
- name: Show GitHub context
run: |
echo "::group::GitHub Context"
echo "Event: ${{ github.event_name }}"
echo "Repository: ${{ github.repository }}"
echo "Actor: ${{ github.actor }}"
echo "SHA: ${{ github.sha }}"
echo "Ref: ${{ github.ref }}"
echo "::endgroup::"
- name: Show runner context
run: |
echo "::group::Runner Information"
echo "OS: ${{ runner.os }}"
echo "Architecture: ${{ runner.arch }}"
echo "Temp directory: ${{ runner.temp }}"
echo "Workspace: ${{ runner.workspace }}"
echo "::endgroup::"
- name: Conditional debugging
if: runner.debug
run: |
echo "Debug mode is enabled"
ls -la ${{ github.workspace }}YAMLLogging and Annotations
steps:
- name: Logging examples
run: |
echo "::notice::This is a notice"
echo "::warning::This is a warning"
echo "::error::This is an error"
echo "::notice file=app.js,line=1,col=5,endColumn=7::Missing semicolon"
echo "::warning file=package.json::Outdated dependency"
- name: Group logs
run: |
echo "::group::Build Process"
echo "Starting build..."
npm run build
echo "Build completed"
echo "::endgroup::"
- name: Mask sensitive values
run: |
TEMP_PASSWORD="secret123"
echo "::add-mask::$TEMP_PASSWORD"
echo "Password is: $TEMP_PASSWORD" # This will be masked in logsYAMLWorkflow Monitoring
graph TD
A[Workflow Run] --> B[Job Execution]
B --> C[Step Execution]
B --> D[Logs]
B --> E[Annotations]
B --> F[Status Checks]
D --> G[Debug Information]
E --> H[Warnings/Errors]
F --> I[Success/Failure]
G --> J[Troubleshooting]
H --> J
I --> J
J --> K[Workflow Optimization]Performance Monitoring
jobs:
performance-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: |
start_time=$(date +%s)
npm ci
end_time=$(date +%s)
duration=$((end_time - start_time))
echo "::notice::Dependency installation took ${duration} seconds"
- name: Run performance tests
run: |
npm run test:performance | tee performance-results.txt
- name: Upload performance results
uses: actions/upload-artifact@v3
with:
name: performance-results
path: performance-results.txtYAML13. Enterprise Features
Self-Hosted Runners
name: Self-Hosted Runner Example
on: [push]
jobs:
build:
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- name: Build on self-hosted runner
run: |
echo "Running on $(hostname)"
./build.shYAMLRunner Groups and Labels
graph TD
A[GitHub Enterprise] --> B[Runner Groups]
B --> C[Default Group]
B --> D[Production Group]
B --> E[Development Group]
C --> F[ubuntu-latest runners]
D --> G[production, secure]
E --> H[dev, testing]
I[Workflows] --> J{Runner Selection}
J --> K[runs-on: production, secure]
J --> L[runs-on: self-hosted, dev]Organization Policies
# Organization-level workflow template
name: Organization Standard Workflow
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
security-compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Required security scanning
- name: Run security scan
uses: github/codeql-action/analyze@v2
# Required license check
- name: License compliance
run: ./scripts/check-licenses.sh
build-test:
needs: security-compliance
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and test
run: |
npm ci
npm run build
npm testYAMLEnvironment Protection Rules
name: Protected Deployment
on:
push:
branches: [main]
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://prod.example.com
steps:
- name: Deploy to production
run: |
echo "Deploying to production environment"
echo "This requires manual approval"YAMLAudit Logging
jobs:
audit-example:
runs-on: ubuntu-latest
steps:
- name: Log deployment action
run: |
echo "::notice::Deployment initiated by ${{ github.actor }}"
echo "::notice::Repository: ${{ github.repository }}"
echo "::notice::SHA: ${{ github.sha }}"
echo "::notice::Timestamp: $(date -Iseconds)"
- name: Send audit log
env:
AUDIT_WEBHOOK: ${{ secrets.AUDIT_WEBHOOK }}
run: |
curl -X POST "$AUDIT_WEBHOOK" \
-H "Content-Type: application/json" \
-d '{
"event": "deployment",
"actor": "${{ github.actor }}",
"repository": "${{ github.repository }}",
"sha": "${{ github.sha }}",
"timestamp": "'$(date -Iseconds)'"
}'YAML14. Real-World Examples
Node.js Application CI/CD
name: Node.js Application CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: 18
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: dist-files
path: dist/
deploy-staging:
needs: [test, build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: dist-files
path: dist/
- name: Deploy to staging
env:
STAGING_URL: ${{ vars.STAGING_URL }}
DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}
run: |
echo "Deploying to $STAGING_URL"
# Your deployment script here
deploy-production:
needs: [test, build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: dist-files
path: dist/
- name: Deploy to production
env:
PRODUCTION_URL: ${{ vars.PRODUCTION_URL }}
DEPLOY_TOKEN: ${{ secrets.PRODUCTION_DEPLOY_TOKEN }}
run: |
echo "Deploying to $PRODUCTION_URL"
# Your deployment script hereYAMLDocker Application Pipeline
name: Docker Application Pipeline
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in 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}}
- name: Build and push Docker image
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
security-scan:
runs-on: ubuntu-latest
needs: build-and-test
if: github.event_name != 'pull_request'
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
deploy:
runs-on: ubuntu-latest
needs: [build-and-test, security-scan]
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
run: |
echo "Deploying ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}"
# Your deployment script hereYAMLMulti-Language Monorepo
name: Monorepo CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.changes.outputs.frontend }}
backend: ${{ steps.changes.outputs.backend }}
shared: ${{ steps.changes.outputs.shared }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
frontend:
- 'frontend/**'
- 'shared/**'
backend:
- 'backend/**'
- 'shared/**'
shared:
- 'shared/**'
test-frontend:
needs: detect-changes
if: needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run test
- run: npm run build
test-backend:
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
- run: pip install -r requirements.txt
- run: python -m pytest
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
deploy:
needs: [detect-changes, test-frontend, test-backend]
if: always() && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Deploy frontend
if: needs.test-frontend.result == 'success'
run: echo "Deploying frontend"
- name: Deploy backend
if: needs.test-backend.result == 'success'
run: echo "Deploying backend"YAMLInfrastructure as Code
name: Infrastructure Deployment
on:
push:
branches: [main]
paths: ['infrastructure/**']
pull_request:
branches: [main]
paths: ['infrastructure/**']
jobs:
terraform-plan:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./infrastructure
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
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: Terraform Init
run: terraform init
- name: Terraform Format Check
run: terraform fmt -check
- name: Terraform Plan
id: plan
run: |
terraform plan -no-color -out=tfplan
terraform show -no-color tfplan > plan.txt
- name: Comment PR with plan
if: github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('infrastructure/plan.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\`\`\`\n${plan}\n\`\`\``
});
terraform-apply:
needs: terraform-plan
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
defaults:
run:
working-directory: ./infrastructure
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
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: Terraform Init
run: terraform init
- name: Terraform Apply
run: terraform apply -auto-approveYAMLConclusion
GitHub Actions is a powerful platform that can automate virtually any software development workflow. From simple CI/CD pipelines to complex multi-environment deployments, GitHub Actions provides the flexibility and power needed to streamline your development process.
Key takeaways:
- Start simple and gradually add complexity
- Use reusable workflows and actions to avoid duplication
- Follow security best practices for secrets and permissions
- Monitor and debug your workflows for optimal performance
- Leverage the community marketplace for common tasks
Remember that GitHub Actions is continuously evolving, so stay updated with the latest features and best practices through the official documentation and community resources.
Happy automating! 🚀
Discover more from Altgr Blog
Subscribe to get the latest posts sent to your email.
