quickstart →

GitHub Actions workflows, from basic to complex

A hands-on GitHub Actions series that builds workflows up from a minimal example to advanced CI/CD pipelines, one concept at a time.

Some GitHub Actions workflows can look really complex, but this is often the result of incremental changes over time, which added more and more capabilities to the workflow.

With this series we’ll build up workflows starting from the most basic examples, to really complex and powerful ones. Hopefully this helps you understand GitHub Actions better.

Articles in this series#

Introduction What GitHub Actions is and how a workflow file works: on, jobs, runs-on and steps, with a first workflow you can copy, run, and extend. Triggers Every common GitHub Actions trigger: push and pull_request with branch, path and tag filters, activity types, schedule cron, and workflow_dispatch inputs. pull_request vs pull_request_target pull_request vs pull_request_target: which one gets secrets and a write token, the pwn request risk, 2025-2026 changes, and the safe split pattern. runs-on How runs-on picks a runner: GitHub-hosted labels, self-hosted label arrays, runner groups, and choosing the runner from a matrix or an expression. Jobs and Steps Jobs run in parallel on separate runners, steps run in order on one. How needs chains jobs, whether GitHub Actions has stages, and parallel steps. Conditions How if works on jobs and steps: the implicit success(), always() vs !cancelled(), failure(), running after a skipped job, and an expression cookbook. Concurrency Cancel superseded runs with cancel-in-progress, serialize deploys, and queue every run with queue: max. Concurrency groups and gotchas, with examples. Environment Variables Every default GitHub Actions environment variable, how to set env at workflow, job and step level, GITHUB_ENV, GITHUB_PATH and vars. Passing Data Between Jobs Pass a value or a file between steps and jobs: $GITHUB_OUTPUT, job outputs with needs, $GITHUB_ENV, artifacts, matrix outputs and size limits. The Matrix Strategy Run one job across OS, version and architecture combinations with strategy.matrix: include and exclude, fail-fast, max-parallel, dynamic JSON matrices. Caching Dependencies Cache npm, pip, Go, Gradle and other dependencies in GitHub Actions with actions/cache or setup-* built-in caching, and make restores faster. Cache Keys & Scopes Build a cache key with hashFiles, fall back with restore-keys, and learn cache immutability, branch scope, who can write the cache and the 10 GB limit. Docker Layer Caching Speed up Docker builds in GitHub Actions by caching layers with Buildx: the gha, registry, inline and local cache backends compared, with YAML examples. Environments GitHub environments: environment secrets and variables, required reviewers, branch rules and wait timers, deployment URLs, and deployment: false. Triggering a Workflow from Another Workflow Start one GitHub Actions workflow from another: workflow_call, workflow_dispatch with gh, workflow_run, repository_dispatch, and the GITHUB_TOKEN rule. Debugging Debug a failing workflow: ACTIONS_STEP_DEBUG and runner debug logs, re-run with debug logging, gh run view, actionlint, act, and SSH into the runner. No Space Left on Device Fix "No space left on device" in GitHub Actions: see what fills the disk, free space on ubuntu-latest, use /mnt, and get a runner with a bigger disk. Larger Runners GitHub larger runners: 4 to 96 vCPU Linux, Windows, arm64 and GPU sizes, per-minute prices, which plans get them, setup, and cheaper options. GPU Runners Run GitHub Actions jobs on a GPU: GitHub's T4 runner and its limits, self-hosted GPU runners, and EC2 GPU runners with RunsOn, with prices and examples. Self-hosted Runners Set up a self-hosted GitHub Actions runner step by step: register, run as a service, go ephemeral, keep it updated, and choose an autoscaler. Actions Runner Controller Install Actions Runner Controller (ARC) with Helm: the scale set controller, GitHub App auth, runner scale sets with labels, and the limits to know.