Skip to content

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 to main.
  • 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 on main are 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 in mise.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 if mise run format produced 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 is published.
  • jobs.pages: Builds the documentation with mise run docs and deploys it with the official Pages pipeline.
    • permissions: Grants pages: write and id-token: write, which the Pages deployment requires.
    • environment: github-pages: Deploys into the protected github-pages environment and exposes the published URL.
    • actions/configure-pagesactions/upload-pages-artifactactions/deploy-pages: The three official steps that configure Pages, package the docs/ folder as an artifact, and deploy it—no gh-pages branch involved.
  • jobs.image: Builds and publishes the Docker image.
    • permissions: Grants packages: write, allowing it to publish to the GitHub Container Registry (ghcr.io).
    • docker/login-action: Logs into the container registry using the automatically provided GITHUB_TOKEN.
    • docker/build-push-action: Builds the image, tags it with latest and 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 concurrency key is essential for managing workflow runs efficiently, preventing race conditions, and saving resources by canceling outdated jobs.
  • Leverage the GitHub CLI: Use the gh command-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.

Additional Resources