Continuous integration and delivery (CI/CD)
Lesson 3 of 4
3Continuous integration and delivery (CI/CD)
The big picture"Did you run the tests?" is a question everyone forgets sometimes. CI answers it automatically for every push, and CD takes what passed and delivers it to production with the same steps every time.
What the picture shows: A CI/CD pipeline: each stage blocks the next if it fails. Whatever reaches the deploy passed everything.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
Like a factory line: each part passes a dimensions station, then a function test, then packaging. A faulty part is rejected at the first station and never reaches the customer. CI/CD is that line for code.
GitHub Actions: a YAML file in .github/workflows/ triggered by events (push, pull_request). The file holds jobs that run in parallel or in sequence (needs), and each job has steps (uses for a ready action, run for a command). A matrix tries several versions. Caching (actions/cache or cache: pip) speeds up installs. Secrets come from secrets.*, with least privilege (permissions:). Branch protection: required checks pass and one review before merging. CD: after merging to main, an image tagged with the commit is built and deployed, with environments and a manual approval for production if needed. And pin the versions of third-party actions.
Before You Start
🔀Git and GitHub
Branches and pull requests.
🧪Tests
The code quality track.
🐳Docker
The containers and deployment track.
The Core Idea
1Your first workflow
One file that lints and tests every push and pull request on two Python versions:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: pip
- run: pip install -r requirements.txt
- run: ruff check .
- run: pytest -q|| true or continue-on-error: true. A check that cannot fail protects nothing.2From CI to CD
A deploy job that runs only after the tests pass and only on main, taking its secrets from GitHub:
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- run: docker build -t registry.example.com/cafe-api:${{ github.sha }} .
- run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login registry.example.com -u ci --password-stdin
- run: docker push registry.example.com/cafe-api:${{ github.sha }}
- run: ./scripts/deploy.sh ${{ github.sha }}github.sha ties each deployed version to a specific commit, so rolling back is redeploying an earlier sha.3Protecting the main branch
In the repository settings: Branches, then a rule for main: require a pull request, require the test check to pass, require one review, and block direct pushes. Now no untested code enters main, not even yours.
Worked example
Problem: CI takes 14 minutes, so the team ignores it and merges before it finishes. Measurement: 9 minutes installing dependencies, 4 of sequential integration tests. Fix: cache: pip cuts the install to 40 seconds, the tests split into two parallel jobs, and linting runs first to fail within seconds. Result: 4 minutes, and the team waits for the result.
Myth vs Reality
| Myth | Reality |
|---|---|
| CI is only for big teams. | A solo developer forgets to run tests too. One file is enough. |
| Automatic deploys are dangerous. | Manual deploys with varying steps are more dangerous. Automatic means the same steps every time, with checks. |
Test your understanding
Where does a GitHub Actions workflow file go?
GitHub reads YAML files only from this folder.
How do you make the deploy job wait for the tests to pass?
needs makes it start only after the named job succeeds.
What does branch protection with a required check give you?
The main branch always stays in a healthy state.
Quick review cards
Check Your Understanding
Why put the fastest check first?
So an obvious mistake fails in seconds instead of after the long tests.
Where do you put the deploy key?
In repository or environment secrets, read with secrets.NAME.
What is the risk of an unpinned third-party action?
Its contents may change and run code you never reviewed with your repository's permissions.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Official docs2
Done with this lesson?
Sign in to save your progress.