Skip to main content

CI/CD integration

Outerbounds integrates seamlessly to CI/CD systems. Read this comprehensive blog post about the topic.

GitOps for Outerbounds

The following diagram illustrates a typical CI/CD pattern:

overview of GitOps with Outerbounds

  1. The user experiments and prototypes code on a cloud workstation or locally on a laptop. Crucially, the user is able to test their code at scale quickly and autonomously in to the Outerbounds cluster.

  2. When the code seems to work adequately, they commit it and open a pull request. They authenticate with the CI/CD system using their personal credentials.

  3. The CI/CD system, e.g. GitHub Actions, CircleCI can be configured to launch a test suite automatically when a pull request is opened. The CI/CD system submits workloads to Outerbounds authenticating as a machine user.

  4. After tests pass, a human reviewer reviews the pull request. The reviewer may tag the pull request as approved and a corresponding Metaflow tag can be applied to tests as well, signalling a successful PR.

  5. After the PR has been approved, the CI/CD system deploys the flow either as a new production version or as a new @project variant, running concurrently with the production version so its performance can be evaluated live.

Supported CI/CD Platforms

Outerbounds supports all major CI/CD platforms through OIDC-based authentication:

  • GitHub Actions - Native OIDC support
  • GitLab CI/CD - Native OIDC support via id_tokens
  • Azure DevOps - Azure AD federation
  • CircleCI - OIDC token support

Each platform uses a similar pattern:

  1. Configure a machine user in Outerbounds - see Programmatic access via machine users for per-provider claim fields.
  2. Set up OIDC authentication in your CI/CD config (YAML examples below).
  3. Use obproject-deploy to deploy your project.

Verifying your deploy

You'll know obproject-deploy succeeded end-to-end when a workflow run for your flow carries the auto-tags applied during deploy:

from metaflow import Flow

# Replace with your project / branch / flow name
run = next(Flow('my_project.prod.myflow').runs())
print(run.tags)
# Expect: 'commit-hash:<sha>', a provider-named run-id tag like
# 'obproject-deploy-gh-action-run:12345', and any standard Metaflow tags.

If you don't see the auto-tags, the deploy didn't complete - check the CI job's logs for the obproject-deploy step and verify the machine user authenticated successfully against auth.<your-platform>.

Machine-user naming convention

The YAML examples below derive the machine-user name from your project name:

PROJECT_NAME=$(yq .project obproject.toml)
CICD_USER="${PROJECT_NAME//_/-}-cicd" # underscores → hyphens

Two implications:

  • The Outerbounds machine user must be created with the hyphenated form (e.g., my-project-cicd, not my_project-cicd). The _- normalization in the YAML exists so a project named my_project resolves to a machine user named my-project-cicd.
  • If you want a different name (e.g., a team-shared machine user across multiple projects), set cicd_user in obproject.toml:
project = "my_project"
cicd_user = "team-shared-cicd"

The example YAMLs read this via yq ".cicd_user // \"$DEFAULT\"" so an explicit value takes precedence over the derived default.

outerbounds service-principal-configure flag reference

The auth command takes a different flag per provider:

ProviderFlagNotes
GitHub Actions--github-actionsReads $ACTIONS_ID_TOKEN_REQUEST_TOKEN / $ACTIONS_ID_TOKEN_REQUEST_URL automatically.
GitLab CI--jwt-token "$OUTERBOUNDS_ID_TOKEN"Token issued by GitLab's id_tokens: keyword.
CircleCI--jwt-token "$CIRCLE_OIDC_TOKEN_V2"Requires a CircleCI context (can be empty) on the workflow.
Azure DevOps--jwt-token "$idToken"$idToken is exposed by the AzureCLI@2 task when addSpnToEnvironment: true.
IAM-backed(none - IAM credentials in the environment)See Identity by IAM.

In all cases the command also takes --name <machine-user>, --deployment-domain <platform>, and --perimeter <perimeter>.

Using Outerbounds with GitHub Actions

This video and the accompaniying repository demonstrates the key workflows in practice:

Follow instructions in this repository to set up OBP work with GitHub Actions.

Example GitHub Actions workflow

name: Deploy Project
on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
id-token: write
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: pip install outerbounds ob-project-utils pyyaml

- name: Configure Outerbounds
run: |
PROJECT_NAME=$(yq .project obproject.toml)
PLATFORM=$(yq .platform obproject.toml)
CICD_USER="${PROJECT_NAME//_/-}-cicd"
outerbounds service-principal-configure \
--name $CICD_USER \
--deployment-domain $PLATFORM \
--perimeter default \
--github-actions

- name: Deploy Project
run: obproject-deploy

Using Outerbounds with GitLab CI/CD

GitLab CI/CD supports OIDC authentication via the id_tokens keyword. Use the example below as a starting template.

Important

The aud value in id_tokens must match your Outerbounds platform URL (e.g., https://my-company.outerbounds.com). This cannot be dynamically read from obproject.toml because GitLab evaluates id_tokens at pipeline creation time, before any scripts run. Update this value when setting up a new project.

Example .gitlab-ci.yml

stages:
- deploy

deploy:
stage: deploy
image: python:3.12
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"

id_tokens:
OUTERBOUNDS_ID_TOKEN:
aud: https://my-company.outerbounds.com # Update to your platform URL

script:
- wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
- chmod +x /usr/local/bin/yq
- pip install outerbounds ob-project-utils pyyaml

- PROJECT_NAME=$(yq .project obproject.toml)
- PLATFORM=$(yq .platform obproject.toml)
- CICD_USER="${PROJECT_NAME//_/-}-cicd"
- |
outerbounds service-principal-configure \
--name $CICD_USER \
--deployment-domain $PLATFORM \
--perimeter default \
--jwt-token $OUTERBOUNDS_ID_TOKEN

- obproject-deploy

artifacts:
paths:
- deployment_summary.md
when: always

Using Outerbounds with Azure DevOps

Azure DevOps supports OIDC through Azure AD federation.

Example azure-pipelines.yml

trigger:
- main

pr:
- main

pool:
vmImage: ubuntu-latest

steps:
- checkout: self

- task: AzureCLI@2
displayName: 'Configure Outerbounds'
inputs:
azureSubscription: 'your-azure-connection' # Replace with your service connection
addSpnToEnvironment: true
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq

PROJECT_NAME=$(yq .project obproject.toml)
PLATFORM=$(yq .platform obproject.toml)
CICD_USER="${PROJECT_NAME//_/-}-cicd"

pip install outerbounds ob-project-utils pyyaml
outerbounds service-principal-configure \
--name $CICD_USER \
--deployment-domain $PLATFORM \
--perimeter default \
--jwt-token $idToken

- script: obproject-deploy
displayName: 'Deploy Project'
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

Using Outerbounds with CircleCI

CircleCI supports OIDC tokens for secure authentication.

Example .circleci/config.yml

version: 2.1

jobs:
deploy:
docker:
- image: cimg/python:3.12
steps:
- checkout
- run:
name: Install dependencies
command: |
wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq
pip install outerbounds ob-project-utils pyyaml
- run:
name: Configure Outerbounds
command: |
PROJECT_NAME=$(yq .project obproject.toml)
PLATFORM=$(yq .platform obproject.toml)
CICD_USER="${PROJECT_NAME//_/-}-cicd" # normalize _ → - to match machine-user naming convention
outerbounds service-principal-configure \
--name $CICD_USER \
--deployment-domain $PLATFORM \
--perimeter default \
--jwt-token $CIRCLE_OIDC_TOKEN_V2
- run:
name: Deploy Project
command: obproject-deploy

workflows:
deploy:
jobs:
- deploy:
context: [OIDC_CONTEXT] # CircleCI context with OIDC enabled
filters:
branches:
only: [main]
tip

Don't hesitate to contact support on Slack if you need help setting up GitOps effectively in your environment.