I've been using GitHub Actions since its November 2019 general availability. In that year, it's become the go-to CI/CD platform for GitHub-hosted code. What's behind this rapid adoption, and are there any limitations to this new darling of DevOps?

GitHub Actions' tightest integration is with GitHub events: push, pull_request, issue, release, and dozens of others trigger workflows automatically. I've set up workflows that run CI on every PR, security scans on every push, and create releases when a tag is pushed. The integration also automates project management by labelling issues and requesting reviews. It's a significant improvement over the webhook configuration and credential management required to connect an external CI/CD system to GitHub.

One practical pain point I hit early on was the default concurrency limit of 20 jobs per repository on the hosted runners. When a large monorepo started spawning 30 matrix builds for each PR, the queue would back up and PR checks took over an hour. The workaround was to split the matrix into two separate workflow files and to request a higher quota from GitHub support. In parallel I measured that a typical Node.js test suite on a hosted Ubuntu runner costs roughly $0.008 per minute, so a 10‑minute job adds up quickly across dozens of PRs. For teams that run heavy integration tests we often move those jobs to self‑hosted runners with dedicated hardware to keep the bill in check.

The Actions marketplace has thousands of community and vendor actions for common CI/CD tasks. You can find actions for setting up language runtimes, running security scans, deploying to cloud providers, and publishing packages. However, the quality varies wildly. High-quality actions from verified publishers like HashiCorp, Azure, AWS, and Docker are stable and well-maintained. On the other hand, community actions require pinning to specific commit hashes rather than tags to prevent supply chain attacks.

Supply‑chain hygiene became a daily checklist after we pulled in a community action that later added a transitive dependency on an outdated OpenSSL version. To avoid repeat incidents we started pinning actions to a full SHA‑256 commit hash and added a step that runs Snyk test on the action's source directory. We also limited the default GITHUB_TOKEN permissions to read‑only for most workflows and only elevated to write when a job needed to push a tag. This granular secret handling prevented a mis‑configured workflow from accidentally publishing a package to npm.

GitHub's hosted runners are sufficient for most CI workloads, but self-hosted runners offer more flexibility. I've set up self-hosted runners on-premises infrastructure, high-performance build machines, and environments that need specific software or network access. The self-hosted runner lifecycle integrates with Kubernetes via the actions-runner-controller for ephemeral, autoscaling runner pools.

Observability of the runner fleet is something you only notice when a node disappears during a long build. We instrumented the actions‑runner‑controller with a Prometheus exporter that reports runner registration, busy state, and exit codes. When a runner crashed due to an out‑of‑memory OOM kill, the metric spiked and an alert fired in PagerDuty, allowing us to replace the node before developers saw a timeout. The trade‑off is the extra Helm chart and RBAC rules you have to maintain, but the reduction in flaky builds was worth it.

As I assess GitHub Actions in 2020, it's clear that it lacks some enterprise features that Azure DevOps has. Specifically, it's missing structured deployment environments with multi-approver workflows, YAML pipeline reuse across repositories, and integration with enterprise identity management. However, the trajectory is clear: GitHub Actions is GitHub's primary CI/CD investment, and these gaps are closing through 2021-2022.