Everyone Can Learn Program
中文 Start learning
Specialty · DevOpsLesson 4 of 5Free to read

CI with GitHub Actions · DevOps: Containers, CI and Kubernetes

How do teams test and deploy every change automatically?

Course contents

Why you need it

Continuous integration (CI) runs the tests on every push and pull request, so a broken change is caught in minutes, not by a customer. Continuous delivery (CD) then deploys what passed. GitHub Actions is a common way to set this up, and its YAML files are something you'll read and edit in almost every project.

The intuition

A workflow lives in .github/workflows/ci.yml:

name: CI
on:                                # when it runs
  push:
    branches: [main]
  pull_request:
jobs:
  test:                            # a job runs on a fresh machine
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4  # a ready-made action
      - run: pytest                # a shell command
  deploy:
    needs: test                    # only after test succeeds
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh
        env:
          TOKEN: ${{ secrets.DEPLOY_TOKEN }}
  • Jobs run in parallel unless needs says one must wait for another. If a needed job fails, the waiting job is skipped.
  • Steps in a job run in order. uses runs an action; run runs a command.
  • Secrets are stored in the repository settings and read with ${{ secrets.NAME }}. They are hidden in logs. Never write a token into the file itself: everyone who can read the repository can read it.

Definition

CI / CD

Continuous integration: test every change automatically. Continuous delivery/deployment: release what passed automatically.

Job

A list of steps that runs on its own fresh machine. Jobs run in parallel unless linked with needs.

Secret

A value stored in the repository settings and given to workflows as ${{ secrets.NAME }}, hidden from logs.

Examples

Test, then deploy

Run it in the lesson

Run it to see the triggers and the job order.

name: CI
on:
  push:
    branches: [main]
  pull_request:
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: pytest
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy.sh
        env:
          TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Step by step
  1. L2Runs on pushes to main and on every pull request.
  2. L10Each job starts on an empty machine: first get the code.
  3. L13with: passes settings to an action. Quote versions so 3.10 doesn't become 3.1.
  4. L17deploy waits for test, and is skipped if test fails.
  5. L18Pull requests test, but only main deploys.
  6. L24The token comes from the repository's secret settings, not from this file.

Common mistakes

Deploying without needs

Without needs: test, deploy runs at the same time as the tests and ships even if they fail.

Tokens in the workflow file

Anyone who can read the repository (and its history, forever) can use it. Put it in secrets and rotate any token that was ever committed.

Unpinned versions

uses: some/action@main can change under you. Pin a version tag, and quote numbers like "3.10" in YAML.

Quick quiz

1. Two jobs, `lint` and `test`, have no `needs`. How do they run?

  1. AAt the same time, on separate machines
  2. Blint first, because it's written first
  3. COne after the other on the same machine
Show the answer

Answer: A

Jobs are parallel by default; needs creates the order.

2. Where should a deploy token go?

  1. AIn the repository's secrets, read as ${{ secrets.NAME }}
  2. BIn the workflow file
  3. CIn the README so the team can find it
Show the answer

Answer: A

Secrets are encrypted, hidden in logs and not part of the code.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictWhich job runs when?

    How do these five jobs run?

  2. ModifyA safer pipeline

    Two problems: deploy runs even when test fails, and the deploy token is written into the file. Make deploy wait for test, and pass the token from the secret DEPLOY_TOKEN as an environment variable TOKEN instead (the script reads $TOKEN, so the run line becomes just ./deploy.sh).

  3. WriteCI for a Node project

    Write a workflow named CI that runs on pull requests and on pushes to main, with two jobs on ubuntu-latest:

    • test: check out the code (actions/checkout@v4), set up Node (actions/setup-node@v4 with node-version: "20"), then run npm ci and npm test
    • build: only after test; check out the code, run npm ci, then npm run build

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.

Terms in this lesson

TermChineseDefinition
CI/CD持续集成/持续交付Test every change automatically, and release what passes.
GitHub ActionsGitHub ActionsGitHub's CI/CD service, configured with YAML workflows.
Secret (CI)密钥(CI)A protected value given to workflows, hidden in logs.