> ## Documentation Index
> Fetch the complete documentation index at: https://anaconda.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Project lifecycle

export const GCell = ({children, className}) => <div className={`grid-table-cell ${className || ""}`} role="cell">
    {children}
  </div>;

export const GTH = ({children, className}) => <div className={`grid-table-th ${className || ""}`} role="columnheader">
    {children}
  </div>;

export const GRow = ({children}) => <div className="grid-table-row" role="row">{children}</div>;

export const GBody = ({children}) => <div className="grid-table-body" role="rowgroup">{children}</div>;

export const GHead = ({children}) => <div className="grid-table-head" role="rowgroup">{children}</div>;

export const GTable = ({children, className, cols}) => <div className={`grid-table not-prose overflow-hidden rounded-2xl ${className || ""}`} style={{
  "--grid-table-cols": cols
}} role="table">
    {children}
  </div>;

<Note>
  This page assumes familiarity with [Project Structure](/docs/platform/guides/projects/project-structure) and [CI/CD integration](/docs/platform/guides/deploy/cicd-integration).
</Note>

When you deploy a project with `obproject-deploy`, several resources are created on the platform. Understanding what gets created, and how to inspect or remove it, is essential for managing feature branches, cleaning up after experiments, and building custom deployment pipelines.

## What a deploy creates

`obproject-deploy` reads your `obproject.toml` and processes the project directory in four stages:

<GTable cols="17% 48% 35%">
  <GHead>
    <GRow>
      <GTH>Stage</GTH>
      <GTH>What it does</GTH>
      <GTH>Resources created</GTH>
    </GRow>
  </GHead>

  <GBody>
    <GRow>
      <GCell>**1. Assets**</GCell>
      <GCell>Registers data and model assets from the `data/` and `models/` directories</GCell>
      <GCell>Data assets, model assets</GCell>
    </GRow>

    <GRow>
      <GCell>**2. Flows**</GCell>
      <GCell>Deploys each flow under `flows/` to Argo Workflows</GCell>
      <GCell>Workflow templates, CronWorkflows (for `@schedule`), Sensors (for `@trigger`)</GCell>
    </GRow>

    <GRow>
      <GCell>**3. Apps**</GCell>
      <GCell>Deploys app capsules from `deployments/`</GCell>
      <GCell>App capsules</GCell>
    </GRow>

    <GRow>
      <GCell>**4. Metadata**</GCell>
      <GCell>Registers a flowproject spec describing all deployed resources</GCell>
      <GCell>Flowproject metadata record</GCell>
    </GRow>
  </GBody>
</GTable>

Each resource is scoped to a **project** and **branch**. The branch is derived from your git ref (example: `main` becomes the production branch, `feature-v2` becomes a test branch).

### Branch naming

Branch names are normalized during deployment:

* `-` and `/` characters are replaced with `_`
* The result is lowercased

For example, git branch `feature/add-scoring` becomes `feature_add_scoring`.

Workflow template IDs follow a specific format where underscores are stripped entirely:

```text theme={null}
{project}.{metaflow_branch}.{flow_name}
```

For a project named `fraud_detection` on branch `main` with a flow called `TrainFlow`:

```text theme={null}
frauddetection.prod.trainflow
```

For a test branch `feature-v2`:

```text theme={null}
frauddetection.test.feature_v2.trainflow
```

The `prod` / `test.{branch}` prefix is Metaflow's `@project` branch convention.

### Deployment lineage tags

Every flow deployed by `obproject-deploy` 0.2.35 or later is automatically tagged with the commit it was built from and the CI run that deployed it. These tags attach to the Argo workflow template and propagate to every run, so you can trace any running workflow back to a specific commit and CI build.

What you'll see on each deployed run:

* `commit-hash:<sha>`: the source commit being deployed (always).
* `merge-commit-hash:<sha>`: only on PR builds where CI synthesizes a merge commit distinct from the source.
* A provider-named CI run tag, such as `obproject-deploy-gh-action-run:12345` for GitHub Actions, `obproject-deploy-circleci-run:678` for CircleCI, etc.

```python theme={null}
from metaflow import Flow
run = next(Flow('fraud_detection.prod.trainflow').runs())
print(run.tags)
# {'commit-hash:abc1234...', 'obproject-deploy-gh-action-run:25842584990', ...}
```

To find every run deployed from a specific commit:

```python theme={null}
for r in Flow('fraud_detection.prod.trainflow').runs(tags='commit-hash:abc1234...'):
    print(r.id, r.created_at)
```

The feature is on by default. To disable, add to `obproject.toml`:

```toml theme={null}
[deploy.tags]
auto = false
```

See [Project utilities API](/docs/platform/guides/projects/utilities-api) for the full tag schema and per-provider sourcing.

## Controlling what gets deployed

By default, `obproject-deploy` deploys all flows, apps, and assets on every branch. This can lead to unnecessary app deployments on feature branches that persist after merge. Two mechanisms let you control this.

### Per-component branch filtering

Place an `obproject_deploy.toml` in any `deployments/<app>/` or `flows/<flow>/` directory to declare which branches should deploy that component:

```toml theme={null}
# deployments/my-dashboard/obproject_deploy.toml
[deploy]
branches = ["main", "release/*"]
```

Glob patterns are supported. When a branch doesn't match, the component is skipped with a message:

```text theme={null}
Skipping app 'my-dashboard' (branch 'feature_foo' not in ['main', 'release/*'])
```

When no `obproject_deploy.toml` is present, the component deploys on all branches (backward compatible). On non-main branches, an info message suggests adding the file.

Your CI workflow stays simple; just call `obproject-deploy`, and the filtering happens automatically based on the config files checked into the repo.

<Tip>
  **Recommended setup.**

  Add `obproject_deploy.toml` with `branches = ["main"]` to each app in `deployments/`. This prevents app proliferation across feature branches while still deploying flows everywhere so that you can test workflow changes. Combine with a [teardown job](#automating-teardown-in-cicd) to clean up branch resources after merge.
</Tip>

### CLI flags

For ad-hoc control without config files, use these flags:

```sh theme={null}
obproject-deploy --skip-apps      # skip all app deployments
obproject-deploy --skip-flows     # skip all flow deployments
obproject-deploy --skip-assets    # skip asset registration
```

## Inspecting deployed resources

Use the `outerbounds flowproject` CLI to inspect what's currently deployed.

### List workflow templates

```sh theme={null}
outerbounds flowproject list-templates --id fraud_detection/main
```

This queries Argo directly for templates matching the project and branch annotations, so it reflects the actual cluster state regardless of what the metadata record says.

### View metadata

```sh theme={null}
outerbounds flowproject get-metadata --id fraud_detection/main | jq .
```

The metadata record contains the full deployment spec: workflows, assets, apps, and their configurations as registered by the last `obproject-deploy` run.

## Tearing down a branch

When a feature branch is merged or abandoned, use `teardown-branch` to clean up all its deployed resources:

```sh theme={null}
# Preview what will be deleted
outerbounds flowproject teardown-branch --id fraud_detection/feature-v2 --dry-run

# Execute the teardown
outerbounds flowproject teardown-branch --id fraud_detection/feature-v2 --yes
```

Teardown deletes resources in this order:

1. **Workflow templates**: cascade-deletes associated CronWorkflows and Sensors
2. **Data assets**
3. **Model assets**
4. **Apps** (capsules)
5. **Flowproject metadata**

If any individual deletion fails, the command continues with remaining resources and reports errors at the end with a non-zero exit code.

To prune individual assets without tearing down the whole branch (for instance, orphan IDs left after a rename), use `prj.asset.delete_data_asset()` from a flow step, as described in [Project assets](/docs/platform/guides/projects/project-assets#deleting-individual-assets).

### Promoting assets before teardown

Teardown deletes **asset metadata** (the catalog entries), not the underlying data. If a feature branch trained a model or produced a dataset you want to keep on `main`, you need to **promote** those assets before tearing down the branch.

`promote_assets()` reads every asset on the source branch, takes the latest instance of each, and re-registers it on the target branch with the same blob references, annotations, and tags. The underlying S3 objects are not copied; only the metadata pointer is created.

```python theme={null}
from obproject.assets import promote_assets

# Promote all assets from feature branch to main
result = promote_assets('my_project', source='feature-v2', target='main')
# result = {"promoted": [...], "errors": [...]}

# Promote with aliases carried forward
result = promote_assets('my_project', source='feature-v2', target='main',
                        with_aliases=True)
```

You can also promote individual assets or specific instances:

```python theme={null}
# Promote only the classifier model
promote_assets('my_project', source='feature-v2', target='main',
               asset='classifier', kinds=['models'])

# Promote a specific aliased instance
promote_assets('my_project', source='feature-v2', target='main',
               asset='classifier', instance='@validated')
```

Promoted instances include lineage annotations (`promoted_from_branch`, `promoted_from_instance`) so you can trace where the production asset originated. With `with_aliases=True`, any aliases set on the source branch (such as `@champion` or `@validated`) are recreated on the target branch pointing to the promoted instance.

<Tip>
  **When to promote vs. re-run.**

  **Promote** when training is expensive (large models, GPU-hours) and you want the exact same weights in production. **Re-run** when training is cheap and you want full reproducibility guarantees from the main branch code. Most projects use a mix: promote models, re-run data pipelines.
</Tip>

### Automating teardown in CI/CD

Add a teardown step to your CI/CD pipeline when branches are deleted or PRs are closed. If you want to preserve assets, add a promote step before teardown:

```yaml expandable theme={null}
# GitHub Actions example
on:
  pull_request:
    types: [closed]
    branches: [main]
  delete:
    branches-ignore: [main]

jobs:
  promote-and-teardown:
    if: >
      (github.event_name == 'delete') ||
      (github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true)
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Configure the platform
      run: |
        # ... authentication setup (see CI/CD integration) ...

    - name: Promote assets
      if: github.event_name == 'pull_request'
      run: |
        SOURCE=${{ github.head_ref }}
        TARGET=${{ github.event.pull_request.base.ref }}
        PROJECT=$(yq .project obproject.toml)
        python3 -c "
        from obproject.assets import promote_assets
        result = promote_assets('$PROJECT', source='$SOURCE', target='$TARGET',
                                with_aliases=True)
        for p in result['promoted']: print(f'Promoted {p[\"kind\"]}/{p[\"name\"]}')
        "

    - name: Teardown branch
      run: |
        BRANCH=${{ github.head_ref || github.event.ref }}
        PROJECT=$(yq .project obproject.toml)
        outerbounds flowproject teardown-branch \
          --id "$PROJECT/$BRANCH" --yes -o json
```

See [ob-project-asset-promotion](https://github.com/outerbounds/ob-project-asset-promotion) for a complete working example.

## Building a custom deploy pipeline

If you need more control than `obproject-deploy` provides, you can use the `outerbounds flowproject` commands directly. This is useful when you want to:

* Deploy a subset of flows or assets
* Integrate with a non-standard CI/CD system
* Add custom validation steps between deploy stages

### Registering metadata

After deploying flows and assets through your own tooling, register the metadata so the platform knows what's deployed:

```sh expandable theme={null}
outerbounds flowproject set-metadata '{
  "project": "fraud_detection",
  "branch": "main",
  "workflows": [
    {"flow_template_id": "frauddetection.prod.trainflow"}
  ],
  "data": [
    {"id": "training_data"}
  ],
  "models": [
    {"id": "fraud_classifier"}
  ]
}'
```

### Deleting metadata only

If you manage resource lifecycle separately and only need to clean up the metadata record:

```sh theme={null}
outerbounds flowproject delete-metadata --id fraud_detection/feature-v2 --yes
```

<Note>
  This does not touch workflow templates, assets, or apps.
</Note>
