Skip to main content

CI/CD: Its Place in Public Health Software Teams

·7 mins

Introduction #

A public health unit (PHU) builds a dashboard to track water test results. It works well - until someone updates the code and breaks a key feature. The fix takes hours, and no one’s quite sure what changed.

These kinds of setbacks are common. Public health teams often work under pressure, with limited time and technical support. But the tools we use to manage code - how we write it, review it, and deploy it - can make a big difference.

CI/CD (Continuous Integration and Continuous Deployment) and platforms like GitHub help teams avoid these pitfalls. They support collaboration, catch errors early, and make it easier to maintain reliable software over time.

What is CI/CD? #

Without automation, software workflows rely heavily on memory and manual checks. This can work, but it can be time-consuming and prone to human error, especially for complex projects. Many of these development tasks are routine and rule-based, making them well-suited to automation.

CI/CD is a set of practices that automates parts of the software development lifecycle. In plain terms:

  • Continuous Integration (CI) means automatically checking code when it has changed - running tests to catch issues before they reach production and enforcing style or other rules.
  • Continuous Deployment (CD) refers to automatically releasing code once it passes those checks, whether that’s publishing a dashboard or updating a model.

Though standard practice in software engineering for years, CI/CD is increasingly relevant to public health teams who write and share code. The reproducibility and quality control they offer can help our teams work consistently across contributors.

The Role of Platforms #

Platforms like GitHub host code and track changes while supporting collaboration through pull requests and reviews. They also integrate with CI/CD tools, making automation straightforward to set up and maintain.

Several of the benefits this can bring to PHUs, such as transparency and reproducibility, are elaborated on in Innovation in the Open.

Making it Practical: Pre-commit and GitHub Actions #

This section introduces two tools that make CI/CD practical for small teams: pre-commit and GitHub Actions. You don’t need a DevOps background to use them, just a shared codebase and a few minutes to set up.

Pre-commit checks code before it is committed. It takes care of local code hygiene and is easy to set up. You can use it to format code, catch syntax errors, or block commits that don’t meet project standards.

A few options for local setup:

  • pre-commit - install via pip or conda
  • prek - a compatible alternative; install globally with uv tool install prek (docs)

GitHub Actions runs workflows in the cloud. It can lint code, run tests, compile projects, or send alerts - triggered by events like a push or pull request.

Together, these tools help teams enforce quality, supporting better habits and clearer code. They’re flexible enough for Python scripts, R projects, or even SQL workflows. And they integrate well with Git, which most teams already use.

Example Workflow #

Let’s say your team maintains a Python script within a GitHub repository that cleans and summarizes water test results. You want to make sure the code is clean and easy to review.

Here’s how a simple CI/CD setup might look:

Local Checks with Pre-commit #

To get started with local checks, you can install pre-commit and add a config file to your repo. For example, to set up a config file with linting for python and JSON:

# .pre-commit-config.yaml
repos:
  - repo: https://GitHub.com/astral-sh/ruff-pre-commit
    rev: v0.13.0
    hooks:
      - id: ruff-check
        args: [--fix]  # Lint and auto-fix
      - id: ruff-format # Format code like black
  - repo: https://GitHub.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: pretty-format-json
        args: [--autofix]
        files: \.json$

Then to set up these hooks in .git/hooks, run:

pre-commit install
Note: Pre-commit hooks are local. Each contributor needs to run pre-commit install after cloning the repository, and hooks can be bypassed with git commit –no-verify. This is why GitHub Actions is still valuable - it enforces the same checks in a shared environment, regardless of local setup.

Now, every time you try to commit code, pre-commit runs these tools: pretty-format-json (cleans up your JSON files) and Ruff (attempts automatic fixes for Python code errors and project-defined style issues).

If the code doesn’t pass, the commit is blocked until it’s fixed. For example, say your commit contains the following Python file:

import json,os

def load_data(path):
  with open(path) as f:
    data=json.load(f)
  return data

def process(data):
  result = [x for x in data if x['value']>10]
  return result

And a JSON file:

{ "name":"Guelph", "value": 12, "status":"ok"}

Both files break the formatting rules defined in the pre-commit files. Once the pre-commit hooks are run, the commit will be blocked and the issues auto-fixed (if able). The resulting Python file will then be:

import json
import os


def load_data(path):
    with open(path) as f:
        data = json.load(f)
    return data


def process(data):
    result = [x for x in data if x["value"] > 10]
    return result

With the new JSON file:

{
  "name": "Guelph",
  "value": 12,
  "status": "ok"
}

These cleaned files can then be re-added and successfully committed. By blocking commits that violate the pre-commit rules, the codebase can be kept clean without needing manual review.

Though this scenario targets code linting, there are many additional hooks available - both from the pre-commit-hooks repo and other sources - that offer additional cleaning and error checking capabilities.

GitHub Actions for Cloud Automation #

GitHub Actions runs workflows in response to events like push/pull requests or scheduled jobs. It’s useful for enforcing standards and running tests on code before it reaches your remote repository.

Here’s a simple workflow that runs Ruff and pytest:

# .GitHub/workflows/lint-and-test.yml
name: Lint and Test

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install ruff pytest
      - name: Run Ruff
        run: ruff check . --fix
      - name: Run Tests
        run: pytest
Warning: The –fix flag tells Ruff to auto-correct issues, but in a CI environment those changes are made to a temporary runner - they aren’t committed back to your repository. If Ruff fixes something during a workflow run, the pipeline may still pass while your branch retains the original unformatted code. For CI, consider dropping –fix and letting the workflow fail explicitly, so contributors know to fix and re-push.

This workflow ensures that every change is linted and tested before it’s merged. If something breaks, GitHub shows a clear error message.

Best Practices #

CI/CD works best when it’s part of a broader culture of care - care for code, for data, and for the people who use both. These practices help teams get the most out of automation:

  1. Start Small: Begin with formatting and linting. Add tests and deployment steps gradually, as your team gets comfortable.

  2. Keep Configs in Version Control: Store your .pre-commit-config.yaml and GitHub Actions workflows in the repo. This makes them visible, editable, and consistent across machines.

  3. Use Descriptive Failures: Make sure your tools give clear messages when something breaks. A cryptic error slows everyone down. A helpful one teaches.

  4. Protect Main Branches: Use branch protection rules to require passing checks before merging. This prevents broken code from reaching production.

  5. Avoid Over-Automation: Not everything needs a hook or a workflow. Automate what’s repetitive or error-prone. Leave room for judgment and context.

  6. Review Periodically: As your team grows or your tools change, revisit your CI/CD setup. What helped last year might now be a bottleneck - or a blind spot.

Conclusion #

CI/CD and modern development platforms aren’t just for tech companies. They’re practical tools that help public health teams write better code and work together more effectively. The goal of CI/CD isn’t perfect pipelines, but dependable ones. For PHUs, where code often supports real-world decisions, this reliability matters.

Start with tools that have real impact for you and your team. Use pre-commit to catch and fix issues before code is committed and GitHub Actions to run checks and tests automatically when code changes.

Add a .pre-commit-config.yaml to your repo. Set up a basic workflow in .GitHub/workflows. Keep configs in version control, protect your main branch, and review your setup as your team grows.

These steps take minutes to set up and save hours of troubleshooting, helping your team keep code clean and catch issues early.

This kind of automation doesn’t replace testing or peer review. But it clears away the small stuff, and that’s time better spent responding to public needs and building tools that can be depended on.

Author
MASc ยท Current Team Member
Tia is a Data Science Fellow at Wellington-Dufferin-Guelph Public Health. With a background in Systems Design Engineering focused on Medical AI, Tia is passionate about applying technology to address real-world health problems at any scale. Her graduate work has involved both fundamental and applied AI research, collaborating with clinicians to develop AI systems to improve clinical workflows.