5.3. CI/CD Workflows
What is CI/CD?
CI/CD is a cornerstone of modern software development that combines Continuous Integration and Continuous Delivery or Deployment. It automates the process of integrating code from multiple contributors, testing it, and preparing it for release.
- Continuous Integration (CI) is the practice of frequently merging all developers' code changes into a central repository. After each merge, an automated build and a series of automated tests are run to detect integration issues early.
- Continuous Delivery (CD) extends CI by automatically deploying all code changes to a testing and/or production environment after the build stage.
- Continuous Deployment is a step further, where every change that passes all stages of the pipeline is automatically released to customers.
The primary goal is to make software development faster, more reliable, and less error-prone by automating the entire release process.
What is a CI/CD workflow?
A CI/CD workflow, often called a pipeline, is the automated sequence of steps that takes code from a developer's machine to the production environment. This pipeline typically includes stages for building the application, running a comprehensive suite of automated tests (unit, integration, security), and deploying the application. By automating this path, teams can release new features and fixes to users with speed and confidence.
Why are CI/CD workflows essential for MLOps?
In MLOps, CI/CD workflows are critical for managing the complexity of machine learning systems. They provide several key benefits:
- Ensure Code and Model Quality: CI/CD acts as a gatekeeper, enforcing quality standards for both code and models. By running automated checks for code style, typing, security, and test coverage, it prevents regressions and maintains a healthy codebase.
- Automate Repetitive Tasks: Workflows automate tedious but crucial tasks like dependency installation, testing, packaging, and publishing. This frees up AI/ML engineers to focus on higher-value activities like model development and performance tuning.
- Enhance Reproducibility: By codifying the build, test, and deployment process, CI/CD ensures that every version of your ML system is built and deployed in a consistent, reproducible manner. This is vital for tracking experiments and complying with regulatory requirements.
- Improve Collaboration and Visibility: Centralized workflows provide a clear, shared understanding of the project's health. They generate reports on code quality, test results, and deployment status, making it easier for team members to collaborate and maintain high standards.
Which CI/CD solution should you use?
While many CI/CD solutions exist, GitHub Actions is a powerful and convenient choice for projects hosted on GitHub. It is deeply integrated with the GitHub platform, allowing you to build, test, and deploy your code directly from your repository.
To create a workflow, you define a YAML file in the .github/workflows directory of your project. This file specifies the triggers (e.g., a pull request), the jobs to run, and the individual steps within each job.
What are the essential workflows for an MLOps project?
For a typical MLOps project, you should establish two primary workflows: one for verification and another for publication.
Continuous Integration Workflow
This workflow runs on every pull request (and every push to main) to ensure that code changes meet quality standards before being merged. Notice that it runs the exact same mise run tasks you use locally and in your git hooks—there is a single source of truth for what "format", "check", and "test" mean.
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Install mise toolchain
uses: jdx/mise-action@v4
- name: Format sources
run: mise run format
- name: Run checks
run: mise run check
- name: Run tests
run: mise run test
- name: Verify no changes
run: git diff --exit-code
Workflow Breakdown:
name: The workflow's name, "CI," as it appears in the GitHub UI.on: Triggers the workflow on any pull request and on pushes tomain.permissions: Grants the job read-only access by default, following the principle of least privilege.concurrency: Ensures that only one run of this workflow per branch is active at a time. If a new commit is pushed to a branch, the previous run is canceled (but runs onmainare never interrupted).jobs.check.steps: Defines the sequence of steps to execute.actions/checkout@v7: Checks out the repository code.jdx/mise-action@v4: Installs the toolchain pinned inmise.toml(Python, uv, dprint, and the security scanners) and syncs the project. This single step replaces separate "setup Python" and "install uv" actions.mise run format/mise run check/mise run test: Runs the canonical tasks for formatting, static analysis (lint, types, security), and the test suite—identical to what runs locally.git diff --exit-code: Fails the build ifmise run formatproduced any changes, guaranteeing that all committed code is already formatted.
Continuous Deployment Workflow
This workflow is triggered when a new release is published. It handles building and publishing the project artifacts—the documentation site and a Docker image. The documentation deploys through the official GitHub Pages Actions, which upload an artifact and deploy it to the github-pages environment. This is the modern, recommended flow and replaces the legacy approach of pushing to a gh-pages branch.
name: CD
on:
release:
types: [published]
permissions:
contents: read
jobs:
pages:
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Install mise toolchain
uses: jdx/mise-action@v4
- name: Build API documentation
run: mise run docs
- name: Configure Pages
uses: actions/configure-pages@v6
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: docs/
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
image:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Set lower-case image path
run: echo "IMAGE=$(echo "ghcr.io/${{ github.repository }}" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build and push image
uses: docker/build-push-action@v7
with:
context: .
push: true
cache-to: type=gha
cache-from: type=gha
tags: |
${{ env.IMAGE }}:latest
${{ env.IMAGE }}:${{ github.ref_name }}
Workflow Breakdown:
on: Triggers the workflow when a release ispublished.jobs.pages: Builds the documentation withmise run docsand deploys it with the official Pages pipeline.permissions: Grantspages: writeandid-token: write, which the Pages deployment requires.environment: github-pages: Deploys into the protectedgithub-pagesenvironment and exposes the published URL.actions/configure-pages→actions/upload-pages-artifact→actions/deploy-pages: The three official steps that configure Pages, package thedocs/folder as an artifact, and deploy it—nogh-pagesbranch involved.
jobs.image: Builds and publishes the Docker image.permissions: Grantspackages: write, allowing it to publish to the GitHub Container Registry (ghcr.io).docker/login-action: Logs into the container registry using the automatically providedGITHUB_TOKEN.docker/build-push-action: Builds the image, tags it withlatestand the release version, and pushes it. Using a container ensures a consistent, portable environment for running the ML model.
How can you avoid repeating steps in CI/CD workflows?
The best way to follow the DRY (Don't Repeat Yourself) principle in MLOps is to push logic down into your mise tasks rather than duplicating shell commands across workflows. Both the CI and CD examples above start with the same two lines:
- uses: actions/checkout@v7
- uses: jdx/mise-action@v4
A single jdx/mise-action@v4 step reads your mise.toml, installs the pinned toolchain (Python, uv, dprint, gitleaks, trivy, ...), and syncs the project. Because every job then calls high-level tasks like mise run check or mise run docs, there is nothing to re-declare per workflow—the definitions live in one place and are shared with your terminal and git hooks.
For repository-specific step sequences that are not tasks (for example, a bespoke sign-and-attest flow), you can still encapsulate them into a reusable composite action stored under .github/actions, then reference it with - uses: ./.github/actions/<name>. You can also find thousands of pre-built actions on the GitHub Marketplace to integrate with third-party services and streamline your workflows.
What are some best practices for CI/CD in MLOps?
- Automate Everything: Automate all manual steps in your ML lifecycle, including data validation, model training, evaluation, and deployment, to reduce human error and increase velocity.
- Manage Secrets Securely: Use encrypted secrets to store sensitive information like API keys, passwords, and cloud credentials. GitHub Actions provides a secure way to manage secrets at the repository or organization level.
- Master GitHub Actions Syntax: A deep understanding of the workflow syntax, including contexts, expressions, and triggers, will allow you to build highly dynamic and powerful pipelines.
- Use Concurrency Strategically: The
concurrencykey is essential for managing workflow runs efficiently, preventing race conditions, and saving resources by canceling outdated jobs. - Leverage the GitHub CLI: Use the
ghcommand-line tool to interact with your workflows, check run status, and trigger them manually (e.g.,gh workflow run ...), streamlining your development loop. - Implement Branch Protection Rules: Protect your main branch by requiring status checks (like your verification workflow) to pass before pull requests can be merged. This is a critical safeguard for maintaining a stable and deployable project.