Skip to Content
32 CheatsheetsGithub ActionsGithub Actions Cheatsheet

GitHub Actions Cheatsheet

Table of Contents

  1. GitHub Actions Basics
  2. Workflow Syntax
  3. Events & Triggers
  4. Jobs & Steps
  5. Actions (Marketplace)
  6. Secrets & Variables
  7. Matrix Builds
  8. Reusable Workflows
  9. Composite Actions
  10. Runners
  11. Security & Best Practices
  12. Advanced Patterns
  13. Migration from Jenkins
  14. Interview Scenarios

GitHub Actions Basics

1. Quick Start - First Workflow

# .github/workflows/hello-world.yml name: Hello World on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Run a one-line script run: echo "Hello, World!" - name: Run a multi-line script run: | echo "Add other steps here" echo "Each line runs in the same shell"

2. Basic CI/CD Workflow

# .github/workflows/ci.yml name: CI on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up 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 application run: npm run build - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-artifacts path: dist/

Workflow Syntax

3. Complete Workflow Structure

name: Complete Example # Workflow triggers on: push: branches: [ main ] paths: - 'src/**' - '!src/docs/**' pull_request: branches: [ main ] schedule: - cron: '0 0 * * *' # Daily at midnight workflow_dispatch: inputs: environment: description: 'Environment to deploy' required: true default: 'staging' type: choice options: - staging - production # Global environment variables env: NODE_VERSION: '20' APP_NAME: 'myapp' # Default settings defaults: run: shell: bash working-directory: ./src # Permissions permissions: contents: read packages: write issues: write jobs: build: name: Build Application runs-on: ubuntu-latest # Job-level environment variables env: BUILD_ENV: production # Job outputs outputs: version: ${{ steps.version.outputs.version }} # Timeout timeout-minutes: 30 # Run only if condition met if: github.event_name == 'push' steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get version id: version run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT - name: Build run: npm run build env: # Step-level environment variable API_URL: https://api.example.com

4. Workflow Commands

steps: - name: Set outputs run: | echo "version=1.0.0" >> $GITHUB_OUTPUT echo "status=success" >> $GITHUB_OUTPUT - name: Set environment variable run: echo "BUILD_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - name: Add to PATH run: echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Create summary run: | echo "## Build Summary" >> $GITHUB_STEP_SUMMARY echo "- Version: 1.0.0" >> $GITHUB_STEP_SUMMARY echo "- Status: ✅ Success" >> $GITHUB_STEP_SUMMARY - name: Group logs run: | echo "::group::My Group" echo "Inside the group" echo "::endgroup::" - name: Warning and error run: | echo "::warning::This is a warning" echo "::error::This is an error" - name: Debug message run: echo "::debug::Debug information" - name: Mask value run: echo "::add-mask::$SECRET_VALUE"

Events & Triggers

5. Common Event Triggers

# Push to specific branches on: push: branches: - main - 'releases/**' branches-ignore: - 'feature/**' tags: - 'v*' paths: - 'src/**' - '**.js' paths-ignore: - 'docs/**' - '**.md' # Pull requests on: pull_request: types: [opened, synchronize, reopened] branches: [ main ] # Issues on: issues: types: [opened, labeled] # Schedule (cron) on: schedule: - cron: '30 5 * * 1-5' # Weekdays at 5:30 AM # Manual trigger on: workflow_dispatch: inputs: logLevel: description: 'Log level' required: true default: 'warning' type: choice options: - info - warning - debug environment: description: 'Environment' required: false type: string # Workflow call (reusable) on: workflow_call: inputs: environment: required: true type: string secrets: token: required: true # Multiple events on: [push, pull_request, workflow_dispatch]

6. Activity Types

on: pull_request: types: - opened - synchronize - reopened - closed - labeled - unlabeled on: issue_comment: types: [created, edited, deleted] on: release: types: [published, created, edited] on: workflow_run: workflows: ["CI"] types: [completed] branches: [main]

Jobs & Steps

7. Job Dependencies

jobs: build: runs-on: ubuntu-latest steps: - run: echo "Building..." test: needs: build runs-on: ubuntu-latest steps: - run: echo "Testing..." deploy: needs: [build, test] runs-on: ubuntu-latest steps: - run: echo "Deploying..."

8. Conditional Jobs

jobs: build: runs-on: ubuntu-latest steps: - run: echo "Always runs" deploy-staging: if: github.ref == 'refs/heads/develop' needs: build runs-on: ubuntu-latest steps: - run: echo "Deploy to staging" deploy-production: if: github.ref == 'refs/heads/main' needs: build runs-on: ubuntu-latest steps: - run: echo "Deploy to production"

9. Using Containers

jobs: build: runs-on: ubuntu-latest # Job container container: image: node:20 env: NODE_ENV: production volumes: - my_volume:/volume_mount options: --cpus 2 --memory 4g # Service containers services: postgres: image: postgres:14 env: POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:7 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 6379:6379 steps: - uses: actions/checkout@v4 - run: npm test

10. Strategy - Parallel Jobs

jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false max-parallel: 4 matrix: node: [16, 18, 20] os: [ubuntu-latest, windows-latest, macos-latest] include: - node: 20 os: ubuntu-latest experimental: true exclude: - node: 16 os: macos-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - run: npm test

Actions (Marketplace)

11. Essential Actions

# Checkout code - uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive token: ${{ secrets.GITHUB_TOKEN }} # Setup languages - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - uses: actions/setup-python@v5 with: python-version: '3.11' cache: 'pip' - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' cache: 'maven' - uses: actions/setup-go@v5 with: go-version: '1.21' cache: true # Docker - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v5 with: context: . push: true tags: ghcr.io/${{ github.repository }}:latest cache-from: type=gha cache-to: type=gha,mode=max # Artifacts - uses: actions/upload-artifact@v4 with: name: my-artifact path: | dist/ !dist/**/*.map retention-days: 5 - uses: actions/download-artifact@v4 with: name: my-artifact path: ./artifacts # Cache - uses: actions/cache@v4 with: path: | ~/.npm ~/.cache key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- # GitHub CLI - uses: github/gh-actions@v1 with: command: pr list --state open
# Code quality - uses: github/super-linter@v5 env: DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: sonarsource/sonarcloud-github-action@master env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Security scanning - uses: aquasecurity/trivy-action@master with: image-ref: myimage:latest format: 'sarif' output: 'trivy-results.sarif' - uses: github/codeql-action/init@v3 with: languages: javascript, python # Deployment - uses: azure/webapps-deploy@v2 with: app-name: 'my-app' publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/my-role aws-region: us-east-1 # Notifications - uses: slackapi/slack-github-action@v1 with: payload: | { "text": "Build ${{ github.run_number }} completed" } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Secrets & Variables

13. Using Secrets

jobs: deploy: runs-on: ubuntu-latest steps: # Access secret - name: Use secret run: echo "Token is ${{ secrets.API_TOKEN }}" env: TOKEN: ${{ secrets.API_TOKEN }} # Never do this (secrets will be masked) - name: Don't print secrets run: echo "${{ secrets.API_TOKEN }}" # Will show ***

14. Environment Variables

# Repository variables (Settings > Secrets and variables > Actions > Variables) # Environment variables # Secrets env: # Global GLOBAL_VAR: 'global' jobs: build: runs-on: ubuntu-latest env: # Job level JOB_VAR: 'job' steps: - name: Use variables env: # Step level STEP_VAR: 'step' run: | echo "Global: $GLOBAL_VAR" echo "Job: $JOB_VAR" echo "Step: $STEP_VAR" echo "Variable: ${{ vars.MY_VARIABLE }}" echo "Secret: ${{ secrets.MY_SECRET }}" # Precedence: Step > Job > Workflow > Repository Variable

15. Environments

jobs: deploy: runs-on: ubuntu-latest environment: name: production url: https://production.example.com steps: - name: Deploy run: | echo "Deploying to production" echo "URL: ${{ secrets.PRODUCTION_URL }}" env: # Environment-specific secrets API_KEY: ${{ secrets.API_KEY }}

Matrix Builds

16. Simple Matrix

jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node: [16, 18, 20] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - run: npm test

17. Advanced Matrix

jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] node: [16, 18, 20] include: # Add extra combinations - os: ubuntu-latest node: 20 experimental: true coverage: true - os: macos-latest node: 20 exclude: # Remove specific combinations - os: windows-latest node: 16 continue-on-error: ${{ matrix.experimental || false }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - run: npm test - if: matrix.coverage run: npm run coverage

Reusable Workflows

18. Reusable Workflow Definition

# .github/workflows/reusable-deploy.yml name: Reusable Deploy 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: "Deployment URL" value: ${{ jobs.deploy.outputs.url }} jobs: deploy: runs-on: ubuntu-latest environment: ${{ inputs.environment }} outputs: url: ${{ steps.deploy.outputs.url }} steps: - uses: actions/checkout@v4 - name: Deploy id: deploy run: | echo "Deploying version ${{ inputs.version }} to ${{ inputs.environment }}" echo "url=https://${{ inputs.environment }}.example.com" >> $GITHUB_OUTPUT env: DEPLOY_TOKEN: ${{ secrets.deploy-token }}

19. Calling Reusable Workflow

# .github/workflows/main.yml name: Main Workflow on: [push] jobs: build: runs-on: ubuntu-latest steps: - run: echo "Build step" deploy-staging: needs: build uses: ./.github/workflows/reusable-deploy.yml with: environment: staging version: '1.0.0' secrets: deploy-token: ${{ secrets.STAGING_TOKEN }} deploy-production: needs: deploy-staging uses: ./.github/workflows/reusable-deploy.yml with: environment: production version: '1.0.0' secrets: deploy-token: ${{ secrets.PRODUCTION_TOKEN }}

Composite Actions

20. Composite Action

# .github/actions/setup-app/action.yml name: 'Setup Application' description: 'Setup Node.js and install dependencies' inputs: node-version: description: 'Node.js version' required: false default: '20' cache: description: 'Enable caching' required: false default: 'true' outputs: cache-hit: description: 'Whether cache was hit' value: ${{ steps.cache.outputs.cache-hit }} runs: using: 'composite' steps: - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} - name: Cache dependencies if: inputs.cache == 'true' id: cache uses: actions/cache@v4 with: path: node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - name: Install dependencies if: steps.cache.outputs.cache-hit != 'true' shell: bash run: npm ci - name: Display version shell: bash run: node --version

21. Using Composite Action

jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup application uses: ./.github/actions/setup-app with: node-version: '20' cache: 'true' - run: npm run build

22. Node.js Build Pipeline Composite Action

# .github/actions/nodejs-build/action.yml name: 'Node.js Build Pipeline' description: 'Complete Node.js CI/CD pipeline with build, test, lint, and security scanning' inputs: node-version: description: 'Node.js version' required: false default: '20' run-tests: description: 'Run tests' required: false default: 'true' run-lint: description: 'Run linting' required: false default: 'true' run-security-scan: description: 'Run security scanning' required: false default: 'true' registry-url: description: 'npm registry URL for publishing' required: false default: 'https://registry.npmjs.org' outputs: build-status: description: 'Build status (success/failure)' value: ${{ steps.build.outcome }} test-results: description: 'Test results path' value: ${{ steps.test.outputs.results-path }} coverage-percentage: description: 'Test coverage percentage' value: ${{ steps.test.outputs.coverage }} artifact-name: description: 'Build artifact name' value: 'build-${{ github.sha }}' runs: using: 'composite' steps: - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} cache: 'npm' registry-url: ${{ inputs.registry-url }} - name: Install dependencies shell: bash run: npm ci - name: Run linting if: inputs.run-lint == 'true' shell: bash run: | npm run lint || echo "::warning::Linting found issues" - name: Build application id: build shell: bash run: | npm run build echo "Build completed successfully" - name: Run tests if: inputs.run-tests == 'true' id: test shell: bash run: | npm test -- --coverage --ci --reporters=default --reporters=jest-junit COVERAGE=$(node -e "const c=require('./coverage/coverage-summary.json');console.log(c.total.lines.pct)") echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT echo "results-path=test-results/junit.xml" >> $GITHUB_OUTPUT - name: Security audit if: inputs.run-security-scan == 'true' shell: bash run: | npm audit --audit-level=moderate || echo "::warning::Security vulnerabilities found" npx audit-ci --moderate || echo "::error::Critical vulnerabilities detected" - name: Upload test results if: inputs.run-tests == 'true' && always() uses: actions/upload-artifact@v4 with: name: test-results-${{ github.sha }} path: | test-results/ coverage/ retention-days: 30 - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: build-${{ github.sha }} path: | dist/ build/ retention-days: 7 - name: Generate build summary shell: bash run: | echo "## Build Summary" >> $GITHUB_STEP_SUMMARY echo "- Node.js: ${{ inputs.node-version }}" >> $GITHUB_STEP_SUMMARY echo "- Build: ✅ Success" >> $GITHUB_STEP_SUMMARY if [ "${{ inputs.run-tests }}" == "true" ]; then echo "- Tests: ✅ Passed" >> $GITHUB_STEP_SUMMARY echo "- Coverage: ${{ steps.test.outputs.coverage }}%" >> $GITHUB_STEP_SUMMARY fi if [ "${{ inputs.run-lint }}" == "true" ]; then echo "- Lint: ✅ Checked" >> $GITHUB_STEP_SUMMARY fi if [ "${{ inputs.run-security-scan }}" == "true" ]; then echo "- Security: ✅ Scanned" >> $GITHUB_STEP_SUMMARY fi

23. Docker Build/Push Composite Action

# .github/actions/docker-build-push/action.yml name: 'Docker Build and Push' description: 'Build, scan, and push Docker images with security scanning and SBOM generation' inputs: image-name: description: 'Docker image name' required: true registry: description: 'Container registry (ghcr.io, docker.io, etc.)' required: false default: 'ghcr.io' tags: description: 'Image tags (comma-separated)' required: false default: 'latest' dockerfile-path: description: 'Path to Dockerfile' required: false default: './Dockerfile' context: description: 'Build context path' required: false default: '.' push: description: 'Push image to registry' required: false default: 'true' scan: description: 'Run security scanning' required: false default: 'true' platforms: description: 'Target platforms (e.g., linux/amd64,linux/arm64)' required: false default: 'linux/amd64' build-args: description: 'Build arguments (key=value, one per line)' required: false default: '' outputs: image-tag: description: 'Full image tag with registry' value: ${{ steps.meta.outputs.tags }} digest: description: 'Image digest' value: ${{ steps.build.outputs.digest }} scan-results: description: 'Security scan results file' value: 'trivy-results.sarif' runs: using: 'composite' steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: install: true driver-opts: | network=host - name: Log in to Container Registry if: inputs.push == 'true' uses: docker/login-action@v3 with: registry: ${{ inputs.registry }} username: ${{ github.actor }} password: ${{ github.token }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ inputs.registry }}/${{ inputs.image-name }} tags: | type=raw,value=${{ inputs.tags }} type=sha,prefix={{branch}}- type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - name: Build and push Docker image id: build uses: docker/build-push-action@v5 with: context: ${{ inputs.context }} file: ${{ inputs.dockerfile-path }} platforms: ${{ inputs.platforms }} push: ${{ inputs.push }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max build-args: ${{ inputs.build-args }} provenance: true sbom: true - name: Run Trivy vulnerability scanner if: inputs.scan == 'true' uses: aquasecurity/trivy-action@master with: image-ref: ${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.tags }} format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH,MEDIUM' exit-code: '0' - name: Upload Trivy results to GitHub Security if: inputs.scan == 'true' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif' category: 'container-scan' - name: Generate SBOM if: inputs.push == 'true' shell: bash run: | docker buildx imagetools inspect ${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.tags }} --format '{{json .SBOM}}' > sbom.json echo "✅ SBOM generated: sbom.json" - name: Upload SBOM if: inputs.push == 'true' uses: actions/upload-artifact@v4 with: name: sbom-${{ github.sha }} path: sbom.json retention-days: 90 - name: Generate Docker build summary shell: bash run: | echo "## Docker Build Summary" >> $GITHUB_STEP_SUMMARY echo "- Image: \`${{ inputs.registry }}/${{ inputs.image-name }}\`" >> $GITHUB_STEP_SUMMARY echo "- Tags: \`${{ inputs.tags }}\`" >> $GITHUB_STEP_SUMMARY echo "- Digest: \`${{ steps.build.outputs.digest }}\`" >> $GITHUB_STEP_SUMMARY echo "- Platforms: \`${{ inputs.platforms }}\`" >> $GITHUB_STEP_SUMMARY if [ "${{ inputs.push }}" == "true" ]; then echo "- Pushed: ✅ Yes" >> $GITHUB_STEP_SUMMARY else echo "- Pushed: ❌ No (dry run)" >> $GITHUB_STEP_SUMMARY fi if [ "${{ inputs.scan }}" == "true" ]; then echo "- Security Scan: ✅ Completed" >> $GITHUB_STEP_SUMMARY fi echo "- SBOM: ✅ Generated" >> $GITHUB_STEP_SUMMARY

24. Using Production Composite Actions Together

name: Complete CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: # Node.js build and test build: runs-on: ubuntu-latest outputs: coverage: ${{ steps.nodejs-build.outputs.coverage-percentage }} artifact: ${{ steps.nodejs-build.outputs.artifact-name }} steps: - uses: actions/checkout@v4 - name: Build Node.js application id: nodejs-build uses: ./.github/actions/nodejs-build with: node-version: '20' run-tests: 'true' run-lint: 'true' run-security-scan: 'true' - name: Check coverage threshold run: | COVERAGE=${{ steps.nodejs-build.outputs.coverage-percentage }} if (( $(echo "$COVERAGE < 80" | bc -l) )); then echo "::error::Coverage $COVERAGE% is below 80% threshold" exit 1 fi # Docker build and push docker: needs: build runs-on: ubuntu-latest if: github.event_name == 'push' permissions: contents: read packages: write security-events: write steps: - uses: actions/checkout@v4 - name: Download build artifacts uses: actions/download-artifact@v4 with: name: ${{ needs.build.outputs.artifact }} path: dist/ - name: Build and push Docker image id: docker-build uses: ./.github/actions/docker-build-push with: image-name: ${{ env.IMAGE_NAME }} registry: ${{ env.REGISTRY }} tags: | ${{ github.ref_name }} ${{ github.sha }} dockerfile-path: './Dockerfile' context: '.' push: 'true' scan: 'true' platforms: 'linux/amd64,linux/arm64' build-args: | BUILD_DATE=${{ github.event.head_commit.timestamp }} VCS_REF=${{ github.sha }} VERSION=${{ github.ref_name }} - name: Image deployed run: | echo "✅ Image pushed: ${{ steps.docker-build.outputs.image-tag }}" echo "📦 Digest: ${{ steps.docker-build.outputs.digest }}" # Deploy to staging (on develop branch) deploy-staging: needs: [build, docker] runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' environment: name: staging url: https://staging.example.com steps: - name: Deploy to Kubernetes run: | kubectl set image deployment/app \ app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop \ --namespace=staging # Deploy to production (on main branch) deploy-production: needs: [build, docker] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' environment: name: production url: https://production.example.com steps: - name: Deploy to Kubernetes run: | kubectl set image deployment/app \ app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ --namespace=production

Runners

25. GitHub-Hosted Runners

jobs: build: # Linux runs-on: ubuntu-latest # ubuntu-22.04 runs-on: ubuntu-20.04 # Windows runs-on: windows-latest # windows-2022 runs-on: windows-2019 # macOS runs-on: macos-latest # macos-12 runs-on: macos-13 runs-on: macos-14 # M1 # Larger runners (Team/Enterprise) runs-on: ubuntu-latest-4-cores runs-on: ubuntu-latest-8-cores runs-on: ubuntu-latest-16-cores

26. Self-Hosted Runners

jobs: build: # Single label runs-on: self-hosted # Multiple labels (all must match) runs-on: [self-hosted, linux, x64] # Custom labels runs-on: [self-hosted, gpu, production]

27. Self-Hosted Runner Setup

# Download mkdir actions-runner && cd actions-runner curl -o actions-runner-linux-x64-2.311.0.tar.gz -L \ https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz # Configure ./config.sh --url https://github.com/owner/repo --token YOUR_TOKEN # Run ./run.sh # Install as service sudo ./svc.sh install sudo ./svc.sh start sudo ./svc.sh status # Kubernetes (via Actions Runner Controller) kubectl apply -f https://github.com/actions-runner-controller/actions-runner-controller/releases/download/v0.27.4/actions-runner-controller.yaml

Security & Best Practices

28. Security Best Practices

# Minimal permissions permissions: contents: read packages: write jobs: build: runs-on: ubuntu-latest # Job-specific permissions permissions: contents: read pull-requests: write steps: # Pin actions to commit SHA - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1 # Use dependabot to keep actions updated # Don't expose secrets in logs - name: Use secret safely env: API_TOKEN: ${{ secrets.API_TOKEN }} run: | curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com # Validate inputs - name: Validate input if: github.event_name == 'workflow_dispatch' run: | if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Invalid version format" exit 1 fi

29. OIDC Authentication (AWS)

jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-1 - name: Deploy to S3 run: | aws s3 sync ./dist s3://my-bucket/

30. Code Scanning

name: CodeQL on: push: branches: [ main ] pull_request: branches: [ main ] schedule: - cron: '0 0 * * 1' # Weekly jobs: analyze: runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: matrix: language: [ 'javascript', 'python' ] steps: - uses: actions/checkout@v4 - uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - uses: github/codeql-action/autobuild@v3 - uses: github/codeql-action/analyze@v3

Advanced Patterns

31. Conditional Steps

steps: # Run on specific branch - name: Deploy to production if: github.ref == 'refs/heads/main' run: echo "Deploying to production" # Run on success - name: Success step if: success() run: echo "Previous steps succeeded" # Run on failure - name: Failure step if: failure() run: echo "A step failed" # Always run - name: Cleanup if: always() run: echo "Always runs" # Run if cancelled - name: Cancelled step if: cancelled() run: echo "Workflow was cancelled" # Complex conditions - name: Complex condition if: | github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip ci]') run: echo "Complex condition met"

29. Dynamic Matrix

jobs: setup: runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - id: set-matrix run: | echo 'matrix={"include":[{"project":"A","config":"Debug"},{"project":"B","config":"Release"}]}' >> $GITHUB_OUTPUT build: needs: setup runs-on: ubuntu-latest strategy: matrix: ${{ fromJson(needs.setup.outputs.matrix) }} steps: - run: echo "Building ${{ matrix.project }} in ${{ matrix.config }}"

33. Approval Gates

jobs: build: runs-on: ubuntu-latest steps: - run: echo "Building..." deploy: needs: build runs-on: ubuntu-latest environment: name: production # Required reviewers configured in environment settings steps: - run: echo "Deploying to production"

34. Workflow Artifacts Between Jobs

jobs: build: runs-on: ubuntu-latest steps: - run: echo "Build artifact" > artifact.txt - uses: actions/upload-artifact@v4 with: name: my-artifact path: artifact.txt test: needs: build runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: my-artifact - run: cat artifact.txt

35. Cancel Previous Runs

name: CI on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: runs-on: ubuntu-latest steps: - run: echo "Only latest run continues"

36. Job Summaries

steps: - name: Create summary run: | echo "## Test Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Test | Result |" >> $GITHUB_STEP_SUMMARY echo "|------|--------|" >> $GITHUB_STEP_SUMMARY echo "| Unit | ✅ Pass |" >> $GITHUB_STEP_SUMMARY echo "| Integration | ✅ Pass |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Coverage: 85%" >> $GITHUB_STEP_SUMMARY

Migration from Jenkins

37. Jenkins vs GitHub Actions

# Jenkins Declarative Pipeline pipeline { agent any stages { stage('Build') { steps { sh 'npm install' sh 'npm run build' } } stage('Test') { steps { sh 'npm test' } } } }
# GitHub Actions Equivalent name: CI on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm install - run: npm run build test: needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm install - run: npm test

38. Key Differences

FeatureJenkinsGitHub Actions
ConfigurationJenkinsfile.github/workflows/*.yml
Agentsnodes/agentsrunners
Parallelparallel {}strategy.matrix or parallel jobs
Shared Libraries@Library()Reusable workflows / Composite actions
Credentialscredentials()secrets / vars
ArtifactsarchiveArtifactsupload-artifact / download-artifact
Environmentenvironment env / environments
Approvalinput environment protection
Triggerstriggers on:

39. Migration Checklist

✅ Pre-Migration: - [ ] Inventory Jenkins pipelines - [ ] Identify shared libraries - [ ] Document credentials/secrets - [ ] Map Jenkins plugins to Actions - [ ] Identify self-hosted runner needs ✅ Migration: - [ ] Create .github/workflows directory - [ ] Convert Jenkinsfile to workflow YAML - [ ] Migrate secrets to GitHub Secrets - [ ] Convert shared libraries to reusable workflows - [ ] Set up runners (if needed) - [ ] Configure branch protection rules - [ ] Set up environments and approvals ✅ Post-Migration: - [ ] Test workflows thoroughly - [ ] Monitor execution times - [ ] Optimize runner selection - [ ] Review security settings - [ ] Train team on GitHub Actions - [ ] Decommission Jenkins pipelines

40. GitHub Actions Importer

# Install GitHub CLI brew install gh # Install Actions Importer extension gh extension install github/gh-actions-importer # Configure gh actions-importer configure # Select: Jenkins # Provide: Jenkins URL, username, token, GitHub token # Dry-run migration gh actions-importer dry-run jenkins --source-url https://jenkins.example.com/job/MyJob # Migrate pipeline gh actions-importer migrate jenkins --source-url https://jenkins.example.com/job/MyJob \ --target-url https://github.com/owner/repo # Audit (analyze all pipelines) gh actions-importer audit jenkins --output-dir ./audit-results

Interview Scenarios

Scenario 1: Multi-Environment Deployment

name: Deploy on: push: branches: [main, develop] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build Docker image run: docker build -t myapp:${{ github.sha }} . - name: Push to registry run: | echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin docker tag myapp:${{ github.sha }} ghcr.io/${{ github.repository }}:${{ github.sha }} docker push ghcr.io/${{ github.repository }}:${{ github.sha }} deploy-staging: needs: build if: github.ref == 'refs/heads/develop' runs-on: ubuntu-latest environment: name: staging url: https://staging.example.com steps: - name: Deploy to staging run: | kubectl set image deployment/myapp \ myapp=ghcr.io/${{ github.repository }}:${{ github.sha }} \ -n staging deploy-production: needs: build if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: name: production url: https://www.example.com steps: - name: Deploy to production run: | kubectl set image deployment/myapp \ myapp=ghcr.io/${{ github.repository }}:${{ github.sha }} \ -n production kubectl rollout status deployment/myapp -n production

Scenario 2: Monorepo with Path Filtering

name: Monorepo CI on: push: paths: - 'services/**' jobs: detect-changes: runs-on: ubuntu-latest outputs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} api: ${{ steps.filter.outputs.api }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v2 id: filter with: filters: | frontend: - 'services/frontend/**' backend: - 'services/backend/**' api: - 'services/api/**' build-frontend: needs: detect-changes if: needs.detect-changes.outputs.frontend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: cd services/frontend && npm ci && npm run build build-backend: needs: detect-changes if: needs.detect-changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: cd services/backend && npm ci && npm run build build-api: needs: detect-changes if: needs.detect-changes.outputs.api == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: cd services/api && npm ci && npm run build

Scenario 3: Release Automation

name: Release on: push: tags: - 'v*' jobs: create-release: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Generate changelog id: changelog run: | CHANGELOG=$(git log $(git describe --tags --abbrev=0 HEAD^)..HEAD --oneline) echo "changelog&lt;<EOF" >> $GITHUB_OUTPUT echo "$CHANGELOG" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Create Release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref_name }} release_name: Release ${{ github.ref_name }} body: | ## Changes ${{ steps.changelog.outputs.changelog }} draft: false prerelease: false build-and-publish: needs: create-release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build artifacts run: npm run build - name: Upload Release Asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ needs.create-release.outputs.upload_url }} asset_path: ./dist/app.zip asset_name: app-${{ github.ref_name }}.zip asset_content_type: application/zip

GitHub Actions CLI (gh)

41. GitHub CLI Workflow Commands

# List workflows gh workflow list # View workflow gh workflow view ci.yml # Run workflow gh workflow run ci.yml # Run with inputs gh workflow run deploy.yml -f environment=production -f version=1.0.0 # View workflow runs gh run list --workflow=ci.yml # View specific run gh run view 12345 # Watch run gh run watch # Download artifacts gh run download 12345 # Cancel run gh run cancel 12345 # Re-run workflow gh run rerun 12345 # View logs gh run view 12345 --log

Performance Optimization

42. Caching Strategies

# npm - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' # Custom cache - uses: actions/cache@v4 with: path: | ~/.npm ~/.cache node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- # Docker layer caching - uses: docker/build-push-action@v5 with: cache-from: type=gha cache-to: type=gha,mode=max

43. Optimizing Workflow Execution

# Use concurrency to cancel outdated runs concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true # Use path filters to skip unnecessary runs on: push: paths: - 'src/**' - '!src/docs/**' # Use job outputs to skip dependent jobs jobs: check: outputs: should-deploy: ${{ steps.check.outputs.deploy }} steps: - id: check run: echo "deploy=true" >> $GITHUB_OUTPUT deploy: needs: check if: needs.check.outputs.should-deploy == 'true' steps: - run: echo "Deploying" # Parallelize independent jobs (don't use needs unless required)

Total Commands: 120+ GitHub Actions operations
Focus: Jenkins migration, enterprise patterns, security

Last updated on