What GitHub Actions is and why teams adopt it
GitHub Actions is the automation engine inside GitHub. A workflow is a YAML file in .github/workflows/ that reacts to an event such as a push, a pull request, a release or a schedule, and runs jobs on a runner. Because it sits next to the code, the pull request page becomes the single place where reviewers see tests, scans and deployment status.
Teams adopt it for three reasons: there is no separate CI server to maintain, identity and permissions come from GitHub itself, and the marketplace saves time on common tasks. As a GitHub Actions consulting team, we see the same pattern repeatedly. The first workflow takes an afternoon. The hundredth, copied from the previous ninety-nine, is where cost, security and maintenance problems appear. Our work is mostly about that second phase.
How we structure workflows in production
Reusable workflows over copy and paste
We keep build, test, scan and deploy logic in a central repository as reusable workflows, and each application repository calls them with a few inputs. A fix to the standard then reaches every service through a version bump.
# .github/workflows/ci.yml in an application repository
name: ci
on:
pull_request:
push:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build:
uses: your-org/platform-workflows/.github/workflows/build-scan.yml@v3
with:
image-name: payments-api
secrets: inherit
Two details matter here. concurrency cancels superseded runs on the same branch, which saves minutes and avoids confusing results. The top-level permissions block drops the default token to read-only, and each job then asks for only what it needs.
Environments, approvals and promotion
Deployments run in GitHub Environments such as staging and production. Production requires reviewers, can be limited to the main branch, and holds its own cloud role. The same artefact is promoted between environments instead of being rebuilt, so what you tested is what you ship. We cover the wider pattern in our environment promotion gates and approvals work.
Caching and matrix builds
Caching dependencies and Docker layers is the biggest single speed gain for most repositories. Matrix builds fan tests out across versions or shards. Both are covered in parallel test execution and matrix builds and build caching strategy.
Security and access
Most GitHub Actions incidents come from three places: long-lived secrets, over-privileged tokens and untrusted third-party code.
- OIDC instead of stored keys. The job exchanges a GitHub-issued token for short-lived AWS or Azure credentials. The cloud trust policy names the repository and the environment, so a fork or an unrelated branch cannot assume the role.
- Least-privilege
GITHUB_TOKEN. Set the organisation default to read-only and grantid-token: writeorpackages: writeper job. - Pinned actions. Reference third-party actions by commit SHA and restrict the allowed list in organisation settings.
- Careful triggers.
pull_request_targetandworkflow_runrun with more privilege thanpull_request. Never check out and execute untrusted fork code in those contexts. - Secrets scope. Environment-level secrets with required reviewers, and a secrets manager such as Vault or AWS Secrets Manager for anything sensitive. See CI/CD secrets management with Vault.
Cost and capacity
GitHub-hosted runners are billed by the minute, with different multipliers per operating system and size, so the cheapest minute is the one you never run. Our usual order of attack is: cancel redundant runs, use path filters so documentation changes do not trigger full builds, cache aggressively, split slow suites sensibly, and only then consider bigger or self-hosted runners.
Self-hosted runners make sense for private network access, specialised hardware or sustained high volume. On Kubernetes, a runner controller such as actions-runner-controller can scale ephemeral pods on demand, so each job starts clean. Budget for the operating work: patching images, handling scale-up latency and isolating untrusted code. We would rather you keep hosted runners than run a poorly isolated fleet.
Day-2 operations
- Observability. Track queue time, run duration and failure rate per workflow. A flaky test that fails five per cent of runs costs far more than its fix.
- Updates. Dependabot can raise pull requests for action versions, so upgrades are small and reviewed.
- Ownership. CODEOWNERS on the workflows directory means changes to deployment logic need a platform review.
- Notifications. Failures and deployments post to chat, covered in deployment notifications to Slack and Teams.
Failure modes we design around
| Failure mode | What causes it | What we do |
|---|---|---|
| Leaked cloud key | Static credentials stored as secrets | OIDC and short-lived roles |
| Supply chain compromise | Actions referenced by mutable tag | SHA pinning, allow list, Dependabot |
| Runaway bill | No concurrency control, no path filters | Cancel in progress, filters, caching |
| Slow feedback | One long serial job | Matrix and sharding, better caching |
| Drift between repos | Copied YAML | Central reusable workflows with versions |
When not to use GitHub Actions
If your source of truth is GitLab or Azure DevOps, use the pipeline engine that lives there; moving code just to change CI rarely pays off. If you need to build in a fully air-gapped network or you depend on years of bespoke plugins, Jenkins may stay the pragmatic choice. For Kubernetes delivery we usually pair Actions for CI with ArgoCD or Flux for deployment, so the cluster pulls its desired state instead of CI pushing credentials into it.
Working with us
We build and repair pipelines under our CI/CD pipeline service, usually starting with a short audit. If you are leaving Jenkins, our Jenkins to Actions migration plan shows how we phase it. To see how your delivery practice compares, try the DevOps maturity assessment or book a consultation.
Official documentation: docs.github.com/en/actions