CI/CD with GitHub Actions: A Practical Intro

Automate tests, linting and deploys with one YAML file — and never merge broken code again.

CI/CD is the practice of running your tests and deploys automatically on every push. GitHub Actions makes it a single YAML file in your repo. Once it's green, you can merge with confidence.

Your first workflow

yaml
# .github/workflows/ci.yml
name: CI
on: [push, 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 -q
      - run: ruff check .

Cache dependencies

yaml
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip
      - run: pip install -r requirements.txt

The cache: pip line caches your packages between runs, cutting minutes off every build.

Deploy on merge

yaml
  deploy:
    needs: test          # only runs if tests pass
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        env:
          RENDER_DEPLOY_HOOK: ${{ secrets.RENDER_DEPLOY_HOOK }}
        run: curl -s "$RENDER_DEPLOY_HOOK"

Secrets — never in code

Store tokens in Settings → Secrets and variables → Actions, and reference them as ${{ secrets.NAME }}. They're injected only at runtime and never printed in logs.

What to run

  • Tests, on every push.
  • A linter and formatter check.
  • A type check (mypy / tsc).
  • A build step that proves the app compiles.
  • A deploy, gated on main.
Keep CI fast — under a few minutes. A slow pipeline is a pipeline people start ignoring.

CI is the cheapest insurance you can buy. The first time it catches a bug you'd have shipped, it pays for itself.

Your move

Want us to build this for you?

We turn tutorials like these into production systems every day. Tell us what you need.

Start a project →