Operate and improve CI/CD

Control minutes, storage, and runners

Measure job duration, matrix value, cache hit rate, artifact retention, and runner exposure.

CI/CD spends money in three places: runner minutes, artifact storage, and operator time debugging slow pipelines.

Self-hosted runners add a fourth cost: a persistent machine that untrusted fork code might reach.

A powerful self-hosted runner feels cheaper than paid minutes until you account for patching, disk, and incident response when a fork job escapes your network boundary.

Profile before you cache more. Sometimes the slow step is an integration test that should move to a scheduled workflow, not run on every docs edit.

Profile the workflow

Open the Actions usage view for your repository. Sort jobs by duration. The top job is your first optimization target.

Ask of each job:

  • does this need to run on every push?
  • does this matrix cell change a release decision?
  • can lint and test share setup via parallel jobs instead of one long serial job?

Remove a redundant matrix axis before you add another cache.

Bound stored data

Artifacts accumulate quietly:

- uses: actions/upload-artifact@v4
  with:
    name: test-report
    path: reports/
    retention-days: 7

Fourteen days of Playwright videos on every pull request adds up. Keep what you need for debugging, not everything forever.

Log cache hit rate from actions/setup-node or your cache action. A cache that never hits is complexity without payoff.

Isolate self-hosted runners

Fork pull requests should not land on a self-hosted runner with access to internal networks unless you designed that boundary on purpose.

Use GitHub-hosted runners for untrusted code. Reserve self-hosted runners for trusted branches or internal repos.

Scheduled workflows still consume minutes. Audit cron jobs quarterly and delete ones nobody reads.

Try this on your own project: produce a one-page report listing each job, average minutes, artifact retention, and whether fork PRs can reach it.

Lesson completed