Quick Answer

Put a YAML file in .github/workflows and GitHub runs it on every push. Start with just your test suite, add branch protection so failures block merges, and keep secrets in repository settings rather than in the file.

CI and CD, distinguished

Continuous integration means every change is automatically built and tested when pushed. That is the part with the highest value and the lowest cost, and it is where to start.

Continuous delivery means changes that pass are automatically prepared for release. Continuous deployment goes further and releases them without a human.

Most student projects should do CI and stop there. Automatic deployment on every merge is a good way to publish a broken site quickly when your test coverage is thin.

The value of CI is specific: tests run on a clean machine, not yours. That catches the classic failure where code works locally because of a file, an environment variable or an installed package that exists only on your laptop — see virtual environments and pip.

A workflow, line by line

Create .github/workflows/tests.yml:

name: Tests

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.13"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run tests
        run: python -m pytest -q
  • on — when to run. Here, pushes to main and every pull request.
  • runs-on — a fresh virtual machine, discarded afterwards.
  • actions/checkout — clones your repository. Without it the machine is empty; forgetting it is the most common first mistake.
  • run — shell commands, exactly as you would type them.

Push this and the Actions tab shows the run. If any command exits non-zero, the job fails and the commit is marked with a red cross.

Making it genuinely useful

Test several versions at once with a matrix:

    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]

Three parallel jobs, one per version. Useful for libraries; unnecessary for an application pinned to one runtime.

Cache dependencies, or every run reinstalls everything. setup-python and setup-node both accept a cache option, which often turns a two-minute install into a few seconds.

Add linting as a separate step so formatting problems are caught without argument in review.

Keep it fast. A pipeline taking fifteen minutes stops being waited for, and people merge without checking. Under five minutes is a reasonable target for a student project.

Secrets and permissions

Never put credentials in the workflow file — it is committed and public in a public repository.

Add them under repository settings, then reference them:

      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
        run: ./deploy.sh

GitHub masks secret values in logs, so an accidental print shows asterisks. That masking is not a licence to be careless, but it prevents the most common leak.

Two cautions. Workflows triggered by pull requests from forks do not receive secrets by default — deliberately, since anyone can open a pull request. And pin third-party actions to a version rather than a moving branch, because you are executing someone else's code on a machine holding your secrets.

Make it matter: branch protection

CI that reports failures nobody has to act on is decoration. The step that makes it real is branch protection.

In repository settings, require status checks to pass before merging into main. Now a pull request with failing tests cannot be merged — the button is disabled.

For a group project this is the single most valuable setting available. It ends the situation where someone pushes broken code on Friday and the team discovers it on Monday, and it removes the social awkwardness of enforcing standards — the rule is automatic and applies to everyone.

Combine with requiring a review, and you have essentially the workflow professional teams use. Being able to say you set that up on a college project is a genuinely good interview answer, because most graduates have never used CI at all.

A status badge in the README is a small extra touch that signals the project is maintained — see what recruiters look at on your GitHub.

Frequently Asked Questions

Is GitHub Actions free? Free for public repositories, and private repositories get a monthly allowance of minutes. Student projects almost never exceed the free tier.
Why does my workflow fail when tests pass locally? Usually something present on your machine and not on the runner — an installed package missing from requirements.txt, an environment variable, or a file not committed. That is precisely the class of bug CI exists to catch.
What is the difference between continuous delivery and deployment? Delivery prepares every passing change for release but a human decides when to ship. Deployment releases automatically with no human step.
Should my student project deploy automatically? Start with tests only. Automatic deployment without solid test coverage is an efficient way to publish a broken site. Add it once you trust the suite.
How do I stop broken code being merged? Enable branch protection and require the status check to pass. The merge button is then disabled until CI is green, which is what turns CI from advisory into effective.