[bldr]docs
Concepts

Deployments

A declared step you invoke against what a build produced.

A deployment is a step you declare in build code and invoke on demand. It names a function and the options that function takes.

scope.addDeployment("staging", "deployment/local.shell/v0.0.0", {
    commands: ["./deploy.sh"],
});

Running one

bldr build --deploy site/staging --name "deploy staging"   # after a build
bldr deploy <CID>                                          # on its own
bldr deployments                                           # past runs
bldr deployments get <ID>

Either way you get the deployment's step tree — live while it runs, and a record you can read afterwards.

The step model

A deployment is a step function. Before anything runs, the daemon publishes the plan: every block of a composite (sequence, parallel, try/catch/finally, dag) and every leaf action (one function call) as a node of the deployment's tree, labelled in human terms — push registry.example.com/app:1.2.0, apply 2 resources → bldr-test, wait job bldr-test/suite-…. Each step then moves through

pending → running → succeeded | failed | skipped

with its start and end time, its duration and, once finished, its output (the function's result block) or its error. A step that the plan decided not to run — a sequence step after a failure, a catch whose try succeeded — ends skipped, with the reason; a step abandoned when its block timed out ends cancelled. A block's status is derived from its children, and a block's only error text is a reference to the step that failed: the tree shows where.

Steps report progress as a fraction with a unit and a sentence — an image push says 3 of 5 layers, 41.2 of 68.3 MiB, a Job wait 142 s of 3600 s against its deadline — and the whole deployment's bar is derived from its actions, weighted equally. Every block takes a timeoutSecs; so do k8s.apply and k8s.job.wait. A step with a timeout carries its deadline, and the bar is drawn against it.

Actions report what a person wants to inspect:

  • oci.image.push — one sub-step per layer (digest, size, pushed or already present), and as output the reference, the manifest digest, the layer count and the bytes pushed and skipped.
  • k8s.apply — one sub-step per resource (Kind/namespace/name, whether it was created, configured or unchanged, and the server's resourceVersion), with the rendered YAML it applied stored as a blob the console opens.
  • k8s.job.wait — the Job's conditions as they change, the pod's phase, and the followed container log published on the step as soon as the follow starts, so bldr logs follow <stream> and the console show the lines while the Job runs.

Reading it

In the console, a deployment's page is its step tree: a header with the overall status, one progress bar and the elapsed time; then the blocks and actions, each row with its status, label, duration and — while it runs — a slim bar. Blocks fold; parallel branches read as parallel; a try block labels its phases. Clicking a step opens the inspector, a side panel with an overview (status, times, function, the options it was given), the step's output (its result, the per-layer table of a push, the per-resource list and YAML of an apply, the conditions of a wait) and its log. The tree shows labels, status and durations; everything else is one click away.

On the command line, bldr deployments get <ID> prints the same tree once, and bldr deploy / bldr deployments watch render it live. A build that ran a deployment (bldr build --deploy <filter>) links to it from its deploy step, and the deployment links back to the build.

Composite deployments

deployment/composite/v0.0.0 runs other deployments as one deployment. Its options are one block, and a block's steps are child deployments ({ function, options }) or further blocks written inline — so the three forms nest, and each block is a deployment with its own node in the one task tree. The helpers on Deployment spell the blocks:

import { Deployment } from "bldr";

const shell = (cmd: string) => ({
    function: "deployment/local.shell/v0.0.0",
    options: { commands: [cmd] },
});

scope.addDeployment(
    "release",
    Deployment.COMPOSITE_FUNCTION,
    Deployment.tryCatch({
        try: Deployment.sequence([
            shell("./stage.sh"),
            Deployment.parallel([shell("./push-a.sh"), shell("./push-b.sh")]),
            shell("./switch-traffic.sh"),
        ]),
        catch: shell("./rollback.sh"),
        finally: shell("./notify.sh"),
    }),
);

The blocks and what each one does:

  • { sequence: [...] } — the steps run one after another. The first failure stops the sequence; the later steps end skipped.
  • { parallel: [...] } — the steps run concurrently and the block waits for every one of them. It fails if any step failed; every failed step is visible in the tree, not just the first.
  • { try, catch?, finally? } — try runs; catch runs only when try failed (otherwise it ends skipped); finally runs in every case, after try or after catch, and also when catch itself failed. A failed try fails the block — catch reacts to the failure (a rollback, a notification), it does not undo it. When try succeeded, a finally failure fails the block. The block succeeds only when try and finally both did.
  • { nodes: [{ name, function, options, deps }] } — a DAG: the nodes run one at a time in an order that respects deps, and the first failure stops it. Reach for parallel when branches should run at the same time.

Every block takes an optional timeoutSecs. When it passes, the block fails, and whatever was still running or waiting in it ends cancelled.

A block is plain data, so the same shape works without the helpers:

scope.addDeployment("release", "deployment/composite/v0.0.0", {
    sequence: [
        { function: "deployment/local.shell/v0.0.0", options: { commands: ["echo prepare"] } },
        { parallel: [
            { function: "deployment/local.shell/v0.0.0", options: { commands: ["echo a"] } },
            { function: "deployment/local.shell/v0.0.0", options: { commands: ["echo b"] } },
        ] },
    ],
});

Deployment.composite(block) is the authored form — a Deployment value a pipeline step can link to. bldr/api declares composite-blocks-demo and composite-try-demo as runnable examples.

That is worth reaching for when a release is several steps with real ordering between them, and not worth it when it is one command.

Why declare it here

A deployment declared beside the build that produces the artifact cannot disagree with it about which version is live. The alternative, handing an artifact to a separate system, means two places that both believe they know what is deployed.

On this page