CI/CD with GitHub Actions: Patterns That Work
GitHub Actions has become the default CI/CD platform for most teams, and for good reason. It's deeply integrated with your code, the marketplace has actions for nearly everything, and the free tier is generous. But the difference between a working pipeline and a fast, reliable one is significant.
Speed is the most important quality of a CI pipeline. If CI takes 20 minutes, developers stop running it before pushing. Target under 5 minutes for the critical path, lint, type check, test. Everything else can run in parallel or after merge.
Caching is how you get fast. Cache node_modules with actions/cache keyed on your lockfile hash. Cache Docker layers with docker/build-push-action's built-in cache. Cache Terraform provider plugins. Every minute saved per run multiplies across every PR, every developer, every day.
The job structure matters. Separate lint, type check, test, and build into individual jobs that run in parallel. Use needs to express dependencies, deploy needs build, but lint and test can run simultaneously. This parallel execution can cut pipeline time in half.
For deployment, Workload Identity Federation eliminates service account keys entirely. Instead of storing a GCP service account key as a GitHub secret (which can leak and never expires), WIF lets GitHub authenticate directly with GCP using short-lived tokens. The setup is more complex initially but dramatically more secure.
Branch protection rules enforce CI quality gates. Require status checks to pass before merging. Require PR reviews. Prevent force pushes to main. These rules transform CI from a suggestion into a guarantee.
Reusable workflows (workflow_call trigger) eliminate duplication across repositories. Define your standard CI pipeline once, call it from each repository's workflow file. When you update the shared workflow, every repository benefits. This is particularly valuable for organizations with many microservices.
Matrix builds handle testing across multiple versions or configurations. Test against Node 18 and 20. Run against PostgreSQL 15 and 16. The matrix strategy runs these combinations in parallel without duplicating workflow code.
One pattern I've adopted recently: deploy previews for every PR. A GitHub Actions workflow deploys the PR's branch to a temporary Cloud Run revision with a unique URL. Reviewers can test the actual deployment, not just read the code. The preview is cleaned up when the PR is merged or closed.
Security: never use third-party actions with @main or @latest. Pin to a specific commit SHA. An action author can push malicious code to a tag or branch, pinning to a SHA makes this impossible. Renovate or Dependabot can automate SHA updates for you.