Skip to main content

Publish pipelines from Git

A team keeps its pipelines as files in Git and publishes them to a stage with one command. This page walks one example end to end: a bundle with one SQL pipeline that you describe, try in your own development stage, check in a pull request and publish to production, and keep in step with Git afterwards. Everything on this page is built; the last section says what is not.

You need the stardelt package (install and sign in) and, for everything except init and validate, a host and a sign-in: stardelt login, or STARDELT_TOKEN in a CI job.

Your group also needs a pipeline identity: the service identity pipeline-<group> that its pipelines run as, which an administrator creates once per group: a ServicePrincipal named pipeline-<group> with spec.ownerGroup: <group>. Until it is ready, plan and deploy refuse with No principal for this group yet and name the command to start with (make sync-principal NAME=pipeline-<group> GROUP=<group>). In the platform checkout, make demo-access does all of it for the group demo (GROUP= names another one) and also checks that the Catalogs bronze, silver and gold exist.

1. Describe what you publish​

Every key and every command is listed on Bundle reference; this section shows the ones you need first.

A bundle is a directory in Git that says what a team publishes: the pipeline files, the notebooks and SQL they run, and one target per stage (dev, test, prod). init writes a small one that passes validate; neither command needs a host or a sign-in, so a pull request can run them:

stardelt bundle init shop --name shop-etl --group shop # writes bundle.yaml, resources/daily.yaml, sql/daily.sql
stardelt bundle validate shop # checks every target
stardelt bundle validate shop -t prod # checks one
dev (development): daily
prod (production): daily

bundle.yaml names the bundle and its owner group, the variables (${var.catalog} in a pipeline file is replaced per target), the pipeline files (include), the files they read (files), the connections the stage must have (names only, never values) and the targets (shown here in short form; init writes the same in longer form):

bundle: {name: shop-etl, ownerGroup: shop}
variables: {catalog: {default: warehouse}}
include: [resources/*.yaml] # one pipeline per file, the shape `stardelt apply -f` reads
files: [sql/*.sql]
targets:
dev: {mode: development, variables: {catalog: warehouse}}
prod: {mode: production, variables: {catalog: warehouse}, runAs: pipeline-shop}

resources/daily.yaml is one pipeline. Its statement lives in a file of the bundle and is read in when you deploy:

name: daily
sqlFile: sql/daily.sql
params:
catalog: "${var.catalog}"
schedule:
cron: "0 3 * * *"
timezone: UTC

A pipeline can run a notebook instead of SQL: give notebook: {file: notebooks/explore.ipynb} (a file named under files), and deploy publishes it and pins the version; Bundle reference has the details.

validate prints one sentence per problem and exits 2: an unknown key, a variable without a value in some target, a pipeline file that is not a pipeline or has none (or two) of sql, sqlFile and notebook, a sqlFile that is not among the bundle's files, a schedule without both cron and timezone, a production target without runAs: pipeline-<group>, a path that leaves the bundle, a symbolic link, a file over 5 MiB. It checks what needs no cluster; whether the group's pipeline identity is ready is for Nova to say when you plan or deploy.

2. Try it in your own development stage​

plan asks Nova what deploying one target would do, and writes nothing. deploy does it:

stardelt bundle plan shop -t dev
stardelt bundle deploy shop -t dev
{"action": "would-create", "name": "dev-alice-daily"}
{"action": "created", "name": "dev-alice-daily"}

Each pipeline file gets the target's variables, its sqlFile is read into sql, and the bundle's group is its ownerGroup when the file names none. Then Nova checks every pipeline with the rules of a create (your group, the group's pipeline identity, the statement, the schedule). A development target deploys under dev-<your sign-in name>-<pipeline> and without a schedule, so two people can deploy one bundle side by side and nothing runs on its own. Start it yourself and wait for it:

stardelt bundle run shop -t dev
{"action": "ran", "name": "dev-alice-daily", "runId": "manual__2026-11-01T08:00:00.000000+00:00", "state": "success"}

bundle run starts every pipeline the deploy left in your stage, one after the other, and stops at the first that does not succeed (exit code 1, with the failed tasks in the line); --only daily runs one, written without the dev-alice- prefix. To start any single pipeline by its namespace and name, see Run a pipeline and wait for it.

3. Check a change in a pull request​

A pull request job needs two commands. The first needs nothing from the cluster; the second asks Nova and writes nothing:

stardelt bundle validate pipelines/ # no host, no sign-in
stardelt bundle plan pipelines/ -t prod # STARDELT_NOVA_URL and STARDELT_TOKEN set

plan prints one line per pipeline, the same lines as stardelt apply --dry-run: would-create or would-update, with changes (which of statement, schedule, params, options and deployment would change) and, for a scheduled pipeline, the next runs. A pipeline someone changed by hand after its last deploy also carries "modified": true, with one warning on standard error, so the reviewer sees that the deploy would overwrite that change. If Nova refuses any pipeline, nothing would be written: the sentences are shown (prod: daily: you are not a member of the group shop), what passed is still listed, and the command exits 2, so the job fails. The token is one Nova accepts for a member of the bundle's owner group; keep it in the CI system's secret store, never in a file of the repository.

plan names the field that changes; add --diff to see the change itself. For every update it prints, on standard error after the JSON lines, a unified diff of each changed field (the statement line by line, a schedule or parameters as YAML), so the pull request log shows what a reviewer reviews. The JSON lines on standard output stay as they are.

4. Publish to production​

stardelt bundle deploy shop -t prod
{"action": "created", "name": "daily"}

deploy runs the same check as plan first. Only when Nova accepts every pipeline does it create or update them; a refusal anywhere means nothing is written. A production target deploys the pipelines under the names the files give, with their schedules.

Every pipeline is sent with where it came from: the bundle's name, the target and the Git commit of the bundle directory (-dirty is appended when files are uncommitted; outside a Git checkout there is no commit). Nova stamps that on the pipeline, and the pipeline page shows it under Deployed from, with who deployed it and when. A pipeline made in the interface or with stardelt apply has no such stamp. To go back to an earlier state, check out the earlier commit and deploy again.

To read it for the whole bundle at once, stardelt bundle summary shop -t prod prints one line per pipeline the target left on the stage: its name, the Git revision it was deployed from, deployedBy, deployedAt and "modified": true when someone changed it after the deploy. A release job can compare the revision with the commit it just deployed. The command only reads, and a development target shows only your own pipelines.

The same view is in Nova under Build, Deployments: one block per bundle and stage with the commit, who deployed each pipeline and when, and a Changed since deploy badge on a pipeline someone changed afterwards. A pipeline deployed before that check existed shows a dash there, not "unchanged".

5. Keep Git and the stage in step​

Remove what the bundle dropped. --prune (on plan and deploy) also handles the pipelines an earlier deploy of this bundle target stamped and the bundle no longer names: plan lists them as would-delete, and deploy removes them (deleted) only after every create and update succeeded. A pipeline made by hand, or stamped by another bundle or target, is never touched, and a development target removes only your own dev-<your name>- pipelines.

Empty a stage. stardelt bundle destroy shop -t dev lists, as would-delete, every pipeline an earlier deploy of this bundle target put on the stage, and --yes removes them (deleted). In a development target it removes only your own dev-<your name>- pipelines, so you can clean up after a finished branch without touching a colleague's; in prod it removes what the bundle published there. Pipelines made by hand or stamped by another bundle or target stay, and so do the files of the bundle.

Bring in what was made in the interface. stardelt bundle generate NAMESPACE NAME shop writes a pipeline made on the Pipelines page into the bundle in shop: resources/<name>.yaml, and for a statement sql/<name>.sql with sqlFile pointing at it (the bundle's group stands in for ownerGroup). It never overwrites a file, and when it writes nothing it says why in one sentence: the pipeline belongs to another group than the bundle, cannot be changed from a file, or the bundle's include or files would not pick the new file up. Run stardelt bundle validate shop, commit, and the next deploy -t prod updates that pipeline and stamps it, so Deployed from appears and the next change goes through Git. To look at a pipeline without putting it in a bundle, stardelt export NAMESPACE NAME prints it as a file that stardelt apply -f accepts (see Python, command line and agents).

See what was changed by hand. When someone changes a deployed pipeline after its deploy, with the form, the YAML editor or kubectl, its page says Changed since the last deploy under Deployed from. The next deploy of the bundle overwrites that change, so the page tells you whether a deploy would lose work; bundle plan and bundle deploy say the same with "modified": true and a warning on standard error, and deploy still goes ahead. It compares the statement or notebook, the schedule, the parameters and the options; the pause state and the group are not part of it. A pipeline that was deployed before this check existed shows nothing until its next deploy.

For a production stage you usually want more than a warning. Put drift: block on the target in bundle.yaml (prod: {mode: production, runAs: pipeline-shop, drift: block}; the default is drift: warn): then bundle plan and bundle deploy exit with 2 and name every pipeline that was changed after its deploy, a deploy writes nothing, and the plan still lists everything it would do, so a pull request check shows the whole picture before it fails. Look at the change, keep it in the bundle if it is right, and pass --accept-drift to overwrite what is left.

What is not there yet​

  • A record of what was deployed when, and a rollback button: the stamp on each pipeline says which commit it runs; to go back, deploy the earlier commit.
  • A temporary stage per pull request.