Build, test, and lint the URL shortener on every push.
Cloud & DevOps Module 2
Without CI, the team becomes the test suite — manually verifying changes before every merge.
Hierarchy:
.github/workflows/Key idea: jobs are isolated from each other; steps inside a job are not.
workflow (ci.yml)
├── job: lint
│ ├── step: checkout
│ ├── step: setup-python
│ └── step: ruff check
├── job: test
│ ├── step: checkout
│ ├── step: pytest
└── job: build
├── step: checkout
└── step: docker build
on:on:
push:
branches: [main]
pull_request:
branches: [main]
push triggers on direct commits to main (e.g., after merging a PR)pull_request runs on every commit pushed to a PR targeting mainschedule (cron), workflow_dispatch (manual button), workflow_call (reusable workflows)paths-ignore: ['**.md'] to skip CI for documentation-only edits — free savings on a monorepo.
ubuntu-latest — default; free for public repos, included minutes for private; best price/performancemacos-latest — needed for iOS/macOS builds; 10× cost of Linuxwindows-latest — needed for .NET/Windows testing; 2× cost of LinuxSelf-hosted runners exist for special needs: GPU workloads, access to private networks, compliance requirements. The runner agent connects outbound to GitHub — no inbound ports required.
ubuntu-latest unless you have a concrete reason not to. The cost difference adds up quickly in a matrix.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- run: pip install -r api/requirements.txt
- run: pytest api/tests
actions/checkout — clones the repo onto the fresh runnersetup-python, setup-node, etc.) configures the toolchainneeds: to form a DAG — build only after lint + test passstrategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/setup-python@v5
with: { python-version: "${{ matrix.python-version }}" }
include: to add specific extra combos; exclude: to subtract- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: api/requirements.txt
The cache: pip shortcut on setup-python stores the pip download cache keyed on the hash of your requirements file. On subsequent runs with the same requirements, installation is skipped entirely.
ruff check api worker — style and bug detection (replaces flake8/pylint)ruff format --check api worker — formatting check; fails if files are not auto-formatted (replaces black --check)ruff config that runs in the developer’s editor — no surprises at CI time- name: Install ruff
run: pip install ruff==0.6.9
- name: Lint
run: ruff check cloud-devops/sample-app/api cloud-devops/sample-app/worker
- name: Format check
run: ruff format --check cloud-devops/sample-app/api cloud-devops/sample-app/worker
Pinning ruff==0.6.9 prevents a new ruff release from suddenly failing your build.
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
context: ./api
push: false
tags: api:ci
cache-from: type=gha
cache-to: type=gha,mode=max
docker/setup-buildx-action enables BuildKit features (multi-stage, layer cache export)push: false builds without pushing — validation only; L06 adds the pushtype=gha uses GitHub’s per-repo cache backend — layers survive across runsThe ci.yml pipeline for the URL-shortener sample app:
lint — ruff check + ruff format --check on api + worker; pip cache keyed on requirements filestest — pytest on api/tests + worker/tests; Python 3.11/3.12 matrix; pip cache; fail-fast: falsebuild — Docker buildx for api and worker; GHA layer cache per service; depends on lint + testconcurrency block cancels superseded runs on the same branch — no wasted minutes when you push twice in quick succession.
actions/checkout@main silently changes behavior when GitHub updates the action. Pin to a major version (@v4) at minimum; pin to a SHA for supply-chain paranoia.paths-ignore: ['**.md']. Path filters are cheap to add and free to run.fetch-depth: 0 — the default shallow clone breaks tools that need full git history (changelog generators, git-based versioning)..github/workflows/ — triggers, jobs, steps