CD7: CI pipelines with GitHub Actions
Writing RouteRani's workflow
🎯You'll be able to
Write jobs with a database service, Terraform checks and alert-rule tests, connect them with needs, and limit the deploy job to main.
🏠The everyday picture
An exam has stages: the written paper first, the practical only for those who pass, and the certificate only after both. Nobody gets a certificate because the practical happened to be scheduled earlier.
📘What it really is
-
Service containers: a job can start a database alongside it:
yaml services: postgres: image: postgres:16.15-alpine env: { POSTGRES_USER: routerani, POSTGRES_PASSWORD: routerani, POSTGRES_DB: routerani } ports: ["5432:5432"] options: >- --health-cmd "pg_isready -U routerani" --health-interval 2s --health-retries 20The job waits for the health check, then the tests connect tolocalhost:5432. (A throwaway CI database can use a simple password; it exists only for the job.) -
Infrastructure checks in the same job: ```yaml
- uses: hashicorp/setup-terraform@v4
- name: Terraform format and validate working-directory: infra run: | terraform fmt -check -recursive terraform init -backend=false -input=false terraform validate
-
name: Alert rule tests run: docker run --rm -v "$PWD/monitoring:/w" -w /w --entrypoint promtool prom/prometheus:v3.15.1 test rules alerts.test.yml
``-backend=falseletsvalidate` run without access to real state. -
Deploy only after tests, only from
main: ```yaml deploy: needs: test if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: [self-hosted, routerani-lab] environment: production steps:- uses: actions/checkout@v7
- run: node tools/pipeline.js "1.0.${{ github.run_number }}" --skip-tests ```
needs: testmeans a failing test stops the deploy;- the
if:keeps pull requests and other branches from deploying; github.run_numbergives each run a new number, so each deploy gets a unique version (CD1 Lesson 5);- the deploy runs on a self-hosted runner labelled
routerani-lab, because that's where the practice Docker environment lives. In a cloud setup, it would run on a GitHub-hosted runner with cloud access through OIDC (Lesson 4); -
--skip-testsbecause thetestjob already ran them. -
Matrices run a job several times with different settings (
strategy: matrix: node: [20, 22]), useful for libraries.
📝A worked example
Input: run the Project 2 workflow checks on your computer: cd tests, then node --test --test-name-pattern="workflow|action|deploy runs" pipeline.test.js.
Expected output: on the starter, tests 1 to 4 fail with messages such as pushes should trigger the workflow on main only and
the deploy job must need the test job. After the changes in this module, all four pass. They read the YAML; they don't run it on GitHub.
🌍Where it's used
- Monorepos run different jobs depending on which folders changed.
- Release pipelines chain build, test, staging deploy and production deploy jobs with
needs. - Nightly jobs (
schedule) run slow tests, dependency updates and Terraform drift checks.
📝Case scenario
A food delivery company's deploy job had no needs, so build and deploy jobs started at the same moment. Usually the tests finished first,
but one Friday a slow test run lost the race; the deploy shipped a change whose tests were still running and later failed. Orders from one
city failed for twenty minutes. Adding needs: test made the order explicit.
🖱️Try it
In the starter's workflow, add needs: test and the if: condition to deploy, change on: to pull_request plus push to main, and run
the Project 2 tests 1 and 4. Success: those two pass, and the messages from tests 2 and 3 tell you what's left.
⚠️Common mistakes
- Deploy jobs without
needs. Sign: deploys of untested code when tests are slow. Fix:needs: test. - Deploys from any branch. Sign: a feature branch deployed to production. Fix: an
if:onrefs/heads/main, plus environment branch rules. - Reusing a version number. Sign: two deploys with the same tag. Fix: include the run number or commit in the version.
🛠️For your project
Project 2 test 4 checks the deploy job needs the test job, runs only for main, has an environment and a concurrency group.
✅Quick check
Why does RouteRani's deploy job use --skip-tests?
Show the answer
The test job already ran the tests, and needs makes deploy wait for it.
What does terraform init -backend=false allow?
Show the answer
Running validate without access to the real state.
