Deploy a workflow
In this guide you will use the scripts/deploy.py CLI shipped with the workflow template to bundle one (or every) workflow in your repo and register it with the UniteLabs platform.
Prerequisites
- A repo cloned (or generated) from the workflow template. Each top-level workflow directory (
w<NN>-<name>/, for examplew02-liquid-handling/) is a standalone workflow package with its ownpyproject.tomldeclaring[project].name,[project].version, and[tool.unitelabs.workflow]metadata. uv(latest) and Python 3.12+.- UniteLabs API credentials:
BASE_URL,AUTH_URL,CLIENT_ID,CLIENT_SECRET.
Store your credentials
Copy .env.example to .env at the repo root and fill in the four values:
BASE_URL=https://api.<your-tenant>.unitelabs.io/
AUTH_URL=https://auth.<your-tenant>.unitelabs.io/realms/<tenant-id>/protocol/openid-connect/
CLIENT_ID=<your-client-id>
CLIENT_SECRET=<your-client-secret>
scripts/deploy.py loads .env automatically when run locally. CI/CD pipelines set the same four variables as platform secrets — see CI/CD for workflows.
.env is in the template's .gitignore. Never commit credentials.What the deploy script does
scripts/deploy.py is a single, self-contained file. It declares its own dependencies (requests, python-dotenv) inline, so uv run scripts/deploy.py works from a fresh clone without an install step. There is no pyproject.toml at the repo root.
For each workflow you deploy, the script:
- Finds the workflow. It scans the repo for directories that match
w<NN>-<name>/and contain apyproject.toml, then picks the one you asked for by its slug. The slug is the workflow's unique short name: the[project].namefrom itspyproject.toml, identical to the directory name (for examplew02-liquid-handling). - Builds a bundle. It creates a zip containing the unchanged workflow directory, plus a copy of
shared/at the top level of the zip.shared/is the repo's single library package: it sits next to the workflows and holds the code they all import. Because it travels inside the bundle,import shared.steps...works on the platform exactly as it does locally. - Collects the dependencies. The workflow and
shared/each declare their dependencies in their ownpyproject.toml. The script merges the two lists and drops duplicates. If both declare the same package, the workflow's version wins. The platform installs the runtime environment from this merged list. - Authenticates against the platform with the OAuth2 client credentials from your
.env: a machine-to-machine login with client ID and secret, no browser involved. - Creates or updates the platform record. It looks up the workflow by its
display_name. If a record exists, the script updates it; if not, it creates a new one. If several records share the same display name, the script stops instead of guessing, so it never overwrites the wrong workflow. - Restores soft-deleted records. Deleting a workflow in the UI only disables it. On redeploy the script re-enables the record and refreshes its description and tags, so you don't end up with a hidden duplicate.
Commands
All five forms are mutually exclusive; pick whichever fits your need.
Deploy a single workflow
uv run scripts/deploy.py w02-liquid-handling
The positional argument is the workflow's slug — [project].name in its pyproject.toml.
Deploy every workflow
uv run scripts/deploy.py --all
Deploy only what changed
uv run scripts/deploy.py --changed-from origin/main
Runs git diff --name-only <ref>..HEAD and deploys only the workflows whose directory was touched. Use it locally to preview what a merge would redeploy, or in CI on push-to-main (see CI/CD for workflows).
shared/ or to scripts/deploy.py itself marks every workflow as changed, because shared/ is copied into every bundle and the script builds every bundle. This rule is implemented in one place, the helper function affected_workflows() inside the script.Deploy a tagged release
uv run scripts/deploy.py --git-tag w02-liquid-handling/v1.2.0
The tag combines the workflow's slug and its version, separated by /v. The script splits the tag, checks that the workflow's [project].version matches 1.2.0, and adds v1.2.0 to the platform tags. If the pyproject version and the tag disagree, the script refuses to deploy. The version in pyproject.toml is the one that counts.
Print without deploying
uv run scripts/deploy.py --list
uv run scripts/deploy.py --list --changed-from origin/main
Prints the selected slugs (one per line) and exits before loading .env or authenticating. CI pipelines (or you, locally) can use this to sanity-check the selection without needing credentials.
Channels: DEV / STG / PRD
uv run scripts/deploy.py w02-liquid-handling --channel dev
uv run scripts/deploy.py --all --channel stg
uv run scripts/deploy.py --git-tag w02-liquid-handling/v1.2.0 --channel prd
--channel layers on top of any of the forms above. It prepends [DEV] /[STG] /[PRD] to the platform display_name and adds the channel name as a platform tag. The result: a single tenant can host parallel DEV, STG, and PRD records of the same workflow as distinct platform entries. Running a workflow against real instruments on DEV therefore cannot disturb the record production uses.
The channel-to-trigger mapping is established by CI; locally you'll usually omit --channel (deploys land as untagged "main" records) or pass --channel dev when iterating against a shared DEV tenant. The full mapping is in CI/CD for workflows.
Extra platform tags
uv run scripts/deploy.py w02-liquid-handling --tag prod --tag stable
--tag/-t is repeatable and stacks with --git-tag's version label and --channel's channel tag. Use it for ad-hoc labels (a feature flag, a customer name) without bumping the workflow's version.
Release a workflow
Each workflow ships independently. To release w02-liquid-handling v1.2.0:
- Bump the version. Edit
w02-liquid-handling/pyproject.tomland set[project].version = "1.2.0". - Commit:
git add w02-liquid-handling/pyproject.toml git commit -m "release: w02-liquid-handling v1.2.0" - Tag and push:
git tag w02-liquid-handling/v1.2.0 git push origin main --tags
The tag push triggers deploy-prd in CI — see CI/CD for workflows. You can also run the release locally:
uv run scripts/deploy.py --git-tag w02-liquid-handling/v1.2.0 --channel prd
<slug>/v<X.Y.Z>, because the slug inside the tag is what tells the pipeline which of the repo's workflows to release:w02-liquid-handling/v1.2.0deploysw02-liquid-handlingat version 1.2.0 and leaves every other workflow in the repo untouched.v1.2.0matches no rule. Nothing is deployed and no pipeline runs.
Verify
Open the UniteLabs Workflows page and confirm the workflow appears with the expected [CHANNEL] prefix, version, and tags.
The script's terminal output shows what landed on the platform: display name, resolved entrypoint, bundle size, dependency count, and whether the record was created or updated.
Next steps
- Set up CI/CD — wire
scripts/deploy.pyinto GitHub Actions or GitLab CI with the three-channel (dev/stg/prd) model. - Trigger a workflow run — confirm the deployment end-to-end.
AGENTS.md— authoring rules for adding new workflows to the template.