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:

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.
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.
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.
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.
After the PR has been approved, the CI/CD system deploys the flow either as a new production version or as , 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:
- Configure a machine user in Outerbounds - see Programmatic access via machine users for per-provider claim fields.
- Set up OIDC authentication in your CI/CD config (YAML examples below).
- Use
obproject-deployto 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, notmy_project-cicd). The_→-normalization in the YAML exists so a project namedmy_projectresolves to a machine user namedmy-project-cicd. - If you want a different name (e.g., a team-shared machine user across multiple projects), set
cicd_userinobproject.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:
| Provider | Flag | Notes |
|---|---|---|
| GitHub Actions | --github-actions | Reads $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.
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]
Don't hesitate to contact support on Slack if you need help setting up GitOps effectively in your environment.