LearnCI/CD
CI/CD01. Continuous Integration & Delivery (CI/CD)·EXPERT·8 min read

4. GitHub Actions

GitHub Actions

TL;DR

  • GitHub Actions is CI/CD built into GitHub itself — workflows live as YAML in .github/workflows, triggered by repository events (push, pull_request, schedule, etc.).
  • GitHub-hosted runners are zero-maintenance but can’t reach private networks; self-hosted runners (often via Actions Runner Controller on Kubernetes) fill that gap but require ephemeral configuration to stay secure.
  • pull_request_target is the single most dangerous trigger misconfiguration: it grants full secret access while potentially running untrusted fork code.
  • OIDC federation replaces long-lived cloud credentials stored as secrets with short-lived, per-run tokens — the modern default for cloud deploys.
  • Composite actions and reusable workflows solve duplication across many workflow files, the same way Shared Libraries do for Jenkins and templates do for GitLab CI.
  • Biggest trade-off: because the workflow file lives in the same repo a contributor can open a PR against, the attack surface includes the pipeline definition itself, not just the application code.

Core Concepts

Hosted vs. self-hosted runners

GitHub-hosted runners require zero maintenance (GitHub manages the VMs) but run on public IPs and cannot reach a private VPC natively. Self-hosted runners run wherever they’re deployed, including inside a private network, at the cost of managing that infrastructure.

The private-network requirement is where self-hosted runners earn their operational overhead: integration tests against a staging database in a private VPC cannot use GitHub-hosted runners at all. The correct pattern is a self-hosted runner deployed inside the VPC — it connects outbound to GitHub to poll for jobs, so no inbound firewall port ever needs to open, and it reaches the private database natively. Opening the database to the public internet as a workaround trades a networking inconvenience for a real, unnecessary attack surface.

Scaling self-hosted runners with ARC

A static, permanently-running instance as a self-hosted runner is an anti-pattern — it doesn’t scale with queue depth and accumulates state across every job it ever runs. Actions Runner Controller (ARC) on Kubernetes is the modern answer: runners deploy as pods, scaling up automatically based on queued job signals from GitHub and scaling to zero when idle.

ARC runners should be ephemeral — the pod is destroyed immediately after completing one job. This is a security boundary, not just a scaling detail: a persistent runner that ran a compromised job (a malicious PR, a supply-chain-compromised dependency) can carry a backdoor or leaked credential into every subsequent job scheduled on it. An ephemeral runner gives every job a clean environment.

When a runner queue backs up and ARC isn’t scaling, the most likely causes to check first are GitHub API rate limiting or a failing webhook delivery — ARC relies on this signal to know how many jobs are queued. Also check the HorizontalRunnerAutoscaler’s maxReplicas ceiling and whether the cluster has enough physical nodes to schedule the new pods ARC is trying to create.

Security: OIDC over long-lived credentials

The historical pattern — generating an IAM user access key and storing it as a GitHub secret — carries persistent risk: the key doesn’t expire, and if it leaks (an accidental log, a compromised dependency reading environment variables), it remains valid until someone notices and manually rotates it. OIDC federation replaces this entirely: configure AWS IAM to trust GitHub’s OIDC provider, create an IAM role with a trust policy scoped to a specific repository/branch, and use aws-actions/configure-aws-credentials with role-to-assume in the workflow. GitHub requests a short-lived JWT, presents it to AWS, and AWS grants temporary STS credentials — no long-lived secret exists anywhere in the system to leak.

The pull_request_target trap

pull_request_target runs in the context of the base repository, with full access to repository secrets — unlike pull_request, which runs in the fork’s limited context with no secret access. Using pull_request_target to run untrusted code from a fork (common in “auto-label PRs” or “run tests and comment results” workflows) is a critical, well-documented attack path: a malicious PR can modify the workflow file itself to exfiltrate secrets or compromise a persistent self-hosted runner.

Reducing duplication with composite actions

When the same multi-step sequence (security scanning, build-and-push, notification) repeats across many workflow files, a composite action bundles those steps into a single reusable unit referenced with uses:. This turns “update 20 workflow files identically” into “update one composite action” — the same DRY principle that applies to application code applies just as much to pipeline definitions, which drift out of sync just as easily when duplicated.

Workflow vs. Job vs. Step

Level Scope Runs on Key property
Workflow The whole automation defined by one YAML file Triggered by an event (push, PR, schedule, workflow_dispatch) Can contain multiple jobs
Job A set of steps that run together One runner (fresh VM/pod per job by default) Jobs run in parallel unless needs: specifies order
Step A single command or action invocation Within a job’s runner, sharing filesystem/environment Steps run sequentially within a job

GitHub-Hosted vs. Self-Hosted Runners

Aspect GitHub-Hosted Runners Self-Hosted Runners
Maintenance Zero — GitHub manages the VMs Runner infrastructure managed by the team
Networking Public IPs, cannot reach a private VPC natively Runs inside the VPC, reaches private databases/EKS
Cost Per-minute billing, adds up at scale Team pays for its own compute
Security posture Low risk — ephemeral VMs destroyed after each job High risk if not ephemeral — a compromised job can persist

Jenkins vs GitLab CI vs GitHub Actions

Aspect Jenkins GitLab CI GitHub Actions
Hosting model Self-hosted only SaaS (GitLab.com) or self-managed SaaS (GitHub-hosted) or self-hosted runners
Pipeline definition Jenkinsfile (Groovy) .gitlab-ci.yml (YAML) Workflow YAML in .github/workflows
Native SCM integration None — bolted on via plugins Native — same platform as repo, registry, merge requests Native — same platform as repo
Extensibility model Plugin ecosystem (largest, version-fragile) Built-in features + templates Marketplace actions (versioned, composable)
Duplication mechanism Shared Libraries Templates via include: Composite actions / reusable workflows
Fork PR / untrusted code risk N/A (no native fork-PR concept) Managed via protected branches/variables pull_request_target requires explicit care
Best fit Complex, heterogeneous toolchains needing deep customization Teams already on GitLab wanting one integrated platform Teams already on GitHub wanting the fastest path to CI

Command / Configuration Reference

# .github/workflows/ci.yml — basic workflow/job/step structure
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: go test ./...
# OIDC federation for AWS deploys — no long-lived secret stored
permissions:
  id-token: write   # required to request the OIDC JWT
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/gha-deploy-role
      aws-region: us-east-1
# Composite action reference
steps:
  - uses: ./.github/actions/security-scan   # local composite action
  - uses: my-org/shared-actions/security-scan@v1  # from another repo
# Safer alternative to pull_request_target for fork PRs
on:
  pull_request:          # runs in fork context, no secret access
    types: [opened, synchronize]
# Register a self-hosted runner manually (ARC automates this via CRDs)
./config.sh --url https://github.com/org/repo --token <token>

Common Pitfalls

  • Pitfall: A private database is opened to the public internet to work around GitHub-hosted runners’ lack of VPC access. Why: GitHub-hosted runners run on public IPs and cannot reach private infrastructure. Fix: Deploy a self-hosted runner inside the VPC that connects outbound to GitHub.
  • Pitfall: Long-lived, static self-hosted runners accumulate state and drift across hundreds of jobs. Why: A persistent runner is never reset between jobs. Fix: Run self-hosted runners as ephemeral pods (ARC, one-job-and-destroy).
  • Pitfall: A long-lived AWS access key sits in GitHub secrets long after OIDC federation was available. Why: The key never expires and requires manual rotation to retire. Fix: Migrate to OIDC federation with short-lived, scoped STS credentials.
  • Pitfall: pull_request_target runs tests directly on fork PR code. Why: The trigger grants base-repo secret access to a workflow that may execute attacker-modified code. Fix: Use pull_request for untrusted code, or explicitly checkout only trusted paths when pull_request_target is required.
  • Pitfall: The same CI sequence is copy-pasted across dozens of workflow files and drifts out of sync. Why: No shared, referenced source of truth for the logic. Fix: Extract a composite action or reusable workflow.

Interview Questions

  • “Design a CI pipeline that needs to run integration tests against a private database in a VPC, securely.” — Tests whether self-hosted runners inside the VPC is the correct, immediate answer.
  • “Explain exactly why pull_request_target is dangerous for fork PRs when pull_request is not.” — Tests genuine understanding of the base-repo-context/secret-access distinction, not just “it’s risky.”
  • “A team still uses long-lived IAM keys in GitHub secrets for AWS deploys. Walk through migrating to OIDC.” — Tests whether the full OIDC trust chain (identity provider, trust policy, role assumption) is understood end to end.
  • “What’s the difference between a workflow, a job, and a step?” — Tests basic structural understanding of the execution model.
  • “Compare GitHub Actions to Jenkins for a team that wants to minimize operational overhead.” — Tests awareness of hosting/maintenance trade-offs across tools.
🔒 Locked
Sign in and unlock the full syllabus to keep reading.
Sign in to continue
Knowledge check
6 scenario questions on this topic
Take the quiz →
Related in 01. Continuous Integration & Delivery (CI/CD)
1. CI/CD Fundamentals
EXPERT
2. Jenkins
EXPERT
3. GitLab CI
EXPERT
5. Deployment Strategies
EXPERT