Skip to content
DocsAll docs
Console

Pipelines

Automate scheduled, data, event-driven and build work as versioned pipelines whose jobs run in isolated sandboxes.

Last updated

On this page

Description and operation

General

A pipeline automates work that runs without a person at a terminal. It is a definition of jobs, written as JSON data and stored in Kloudlite. Each job runs its commands in its own sandbox, with the packages, image or snapshot that it names. You create, change and start pipelines from the SDK, the CLI or the API. The automation lives in code, like the rest of your platform.

A pipeline starts when you call it, on a schedule, from a webhook call, or after another pipeline ends. Each start makes a run. A run records which version of the definition it used, so you always know what ran.

UseStartExample
Data refreshA schedule, for example 02:00 UTC every dayDownload a dataset, check it and load it into a database.
Model fetch and processA call from your codeFetch a model from Hugging Face with HF_TOKEN, then convert or evaluate it.
Event-driven workA webhook call from another systemRebuild a search index when a content system sends an event.
ReleaseAfter a test pipeline ends with successCopy a tested image to your container registry.
TestsA call, or a scheduleRun the tests of a repository on each matrix value.
PartDescriptionSee
Definition and versionsThe JSON that describes the pipeline. Each change is a new numbered version that never changes.Definitions and versions
Jobs and stepsThe units of work, their order, conditions, matrix and runtime.Jobs and steps
Triggers and inputsWhat starts a run, and the values that a run takes.Triggers and inputs
RunsOne execution of one version, with its state and the log of each job.Runs
Secrets, registries and environmentsCredentials, image pushes and the services that jobs reach.Secrets, registries and environments
CodeHow a job clones a repository with git clone and permissions.git: read.Code in a job

The exhaustive format is in Pipeline definition. The routes are in the pipelines API.

To do these tasks with an AI agent, see Agentic workflows – General.

How a run works

  1. A start request reads one stored version. It checks the inputs and the secrets, and expands the matrix into jobs.

  2. The platform records the run as queued and answers 202 at once. If the version cannot run, the call fails with 422 and records nothing.

  3. A worker takes the run. Each job starts when the jobs in its needs have ended, with up to 8 jobs of a run at the same time.

  4. Each job runs in a new sandbox. Its steps run in order in that sandbox, each in its own shell, so cd and export do not carry to the next step. Its output goes to the log of the job.

  5. When the last job ends, the run ends as success, failure or cancelled. The platform ends every sandbox of the run.

Jobs that do not need each other run in parallel. A job whose needed job failed is skipped, unless its if condition says otherwise. Files do not pass from one job to another: each job has its own sandbox.

States

Run stateMeaning
queuedThe run is recorded. No job has started.
runningThe platform runs the jobs.
successAll jobs ended without failure.
failureA job failed, or the run could not be prepared. detail gives the cause.
cancelledThe run was cancelled.
Job stateMeaning
pendingThe job waits for its needs.
runningThe job runs. sandbox names its sandbox.
successAll steps exited with code 0.
failureA step failed, or the job did not start. failedStep, exitCode and detail give the cause.
skippedThe if condition was false, or a needed job did not succeed.
cancelledA cancel stopped the job.

Access

A pipeline belongs to an owner: you, or a team. The owner of a pipeline also owns its runs.

ActionPersonal pipelineTeam pipeline
Create, change, delete, rotate the webhookYouA team admin
Read definitions, versions and runsYouAn active team member
Start and cancel a runYouAn active team member

The web console shows pipelines, versions and runs, and has no control that changes them. Use the SDK, the CLI or the API for every change.

Quota and limits

The sandboxes of a run count against the quota of the owner of the pipeline.

ItemLimit
Pipelines for one owner50
Versions of one pipeline100
Size of a definition64 KiB
Jobs of a pipeline50
Steps of a job100
Jobs of one run at the same time8
Job time limittimeoutMinutes, 1 to 1440, default 60
Combinations of a matrix64
Webhook call256 KiB body, and 30 calls in one minute
Chain of after triggers5 pipelines deep
Run records and job logsKept for 90 days

Where to go next

Tasks

Create a pipeline

Reason for the task: Store a definition of jobs that the platform runs every night and on request.

Job set-up

Procedure

  1. Write this definition to the file nightly-report.json.

    json
    {  "name": "nightly-report",  "triggers": { "schedule": ["0 2 * * *"] },  "inputs": {    "source": { "default": "https://api.github.com/repos/nixos/nixpkgs" }  },  "jobs": {    "fetch": {      "packages": ["curl", "jq"],      "steps": [        { "name": "download", "run": "curl -fsSL \"$SOURCE\" -o data.json", "env": { "SOURCE": "${{ inputs.source }}" } },        { "name": "summarize", "run": "jq '{stars: .stargazers_count, issues: .open_issues_count}' data.json" }      ]    },    "report": {      "needs": ["fetch"],      "if": "always()",      "steps": [        { "name": "result", "run": "echo \"fetch ended with $RESULT\"", "env": { "RESULT": "${{ needs.fetch.result }}" } }      ]    }  }}
  2. Create the pipeline from the file.

    Python
    import jsonfrom kloudlite import Kloudlitekl = Kloudlite()with open("nightly-report.json") as f:    doc = kl.pipelines.create(owner="acme", definition=json.load(f))print(doc["name"], doc["version"])
    TypeScript
    import { readFileSync } from "node:fs";import { Kloudlite } from "./index.ts";const kl = new Kloudlite();const definition = JSON.parse(readFileSync("nightly-report.json", "utf8"));const doc = await kl.pipelines.create({ owner: "acme", definition });console.log(doc.name, doc.version);
    Go
    kl := kloudlite.New(kloudlite.Options{})raw, err := os.ReadFile("nightly-report.json")if err != nil {    log.Fatal(err)}var d kloudlite.PipelineDefinitionif err := json.Unmarshal(raw, &d); err != nil {    log.Fatal(err)}doc, err := kl.Pipelines.Create("acme", d)if err != nil {    log.Fatal(err)}fmt.Println(doc.Name, doc.Version)
    Ruby
    require "json"require_relative "kloudlite"kl = Kloudlite::Client.newdoc = kl.pipelines.create(owner: "acme", definition: JSON.parse(File.read("nightly-report.json")))puts doc.values_at("name", "version").join(" ")
    Java
    var kl = new Kloudlite();// The definition is JSON already, so send it as the body of the create route.System.out.println(kl.request(new Kloudlite.RawRequest().method("POST").path("/v1/pipelines/acme")    .body(Files.readString(Path.of("nightly-report.json")))));
    CLI
    kloudlite pipelines create --owner acme --file nightly-report.json

Close-out

  1. Show the pipelines of the owner.

    Python
    print(kl.pipelines.list(owner="acme"))
    TypeScript
    console.log(await kl.pipelines.list({ owner: "acme" }));
    Go
    list, err := kl.Pipelines.List("acme")if err != nil {    log.Fatal(err)}fmt.Printf("%+v\n", list)
    Ruby
    p kl.pipelines.list(owner: "acme")
    Java
    System.out.println(kl.pipelines().list("acme"));
    CLI
    kloudlite pipelines list --owner acme
  2. Make sure that nightly-report shows latestVersion 1 and the schedule 0 2 * * *.

Start a run and follow it

Reason for the task: Run the pipeline now, without waiting for its schedule, and read the output of its jobs.

Job set-up

Procedure

  1. Start a run of the newest version.

    Python
    run = kl.pipelines.run(owner="acme", pipeline="nightly-report")print(run.id, run.state)
    TypeScript
    const run = await kl.pipelines.run({ owner: "acme", pipeline: "nightly-report" });console.log(run.id, run.state);
    Go
    run, err := kl.Pipelines.Run(kloudlite.RunPipeline{Owner: "acme", Pipeline: "nightly-report"})if err != nil {    log.Fatal(err)}fmt.Println(run.ID, run.State)
    Ruby
    run = kl.pipelines.run(owner: "acme", pipeline: "nightly-report")puts run.id, run.state
    Java
    var run = kl.pipelines().run(new Kloudlite.RunPipeline().owner("acme").pipeline("nightly-report"));System.out.println(run.id() + " " + run.state());
    CLI
    RUN=$(kloudlite pipelines run acme/nightly-report --json | jq -r .id)
  2. Read the log of the job fetch while it runs.

    Python
    for chunk in run.follow(job="fetch"):    print(chunk, end="")
    TypeScript
    for await (const chunk of run.follow({ job: "fetch" })) process.stdout.write(chunk);
    Go
    err = run.Follow(kloudlite.FollowLogs{Job: "fetch"}, func(chunk string) bool {    fmt.Print(chunk)    return true})if err != nil {    log.Fatal(err)}
    Ruby
    run.follow(job: "fetch") { |chunk| print chunk }
    Java
    run.follow("fetch").forEach(System.out::print);
    CLI
    kloudlite pipelines logs acme nightly-report "$RUN" fetch --follow
  3. Wait for the run to end. The call returns the final state.

    Python
    print(run.wait())
    TypeScript
    console.log(await run.wait());
    Go
    state, err := run.Wait(kloudlite.WaitRun{})if err != nil {    log.Fatal(err)}fmt.Println(state)
    Ruby
    puts run.wait
    Java
    System.out.println(run.waitFor());
    CLI
    kloudlite pipelines wait-run acme nightly-report "$RUN"

Close-out

  1. Make sure that the final state is success. The log of fetch shows the stars and issues counts.
  2. If the state is failure, read detail of the run and failedStep of each failed job. See Troubleshooting.

Troubleshooting

SymptomPossible causeCorrective action
422 with pipeline nightly-report is invalid: and a list of linesThe definition has errors. Each line names the job and the step.Correct each line and send the definition again. See Pipeline definition.
422 with pipeline nightly-report cannot run: and a list of linesThe version cannot be planned: an input or a secret is wrong.Read each line and correct the cause. See Runs.
409 with pipeline nightly-report existsThe owner already has a pipeline with this name.Send the definition as a new version. See Definitions and versions.
409 with acme has 50 pipelines; delete one firstThe owner is at the limit.Delete a pipeline that is not needed.
403 with only a team admin can change team pipelinesYou are a member but not an admin of the team.Ask a team admin to make the change.
503 with webhooks need an object storeThe definition turns on triggers.webhook, and the region has no object store.Turn off the webhook trigger, or ask an administrator to configure the object store.
404 with not foundThe pipeline does not exist, or the owner is not you or a team that you are in.Check the owner and the name.
403 with your access to acme is pausedA team admin paused your membership.Ask a team admin to unpause you.
The run stays queuedNo worker has taken the run yet.Wait, then show the run again.
A job fails with could not create its sandbox: …The owner has no quota for one more sandbox, or the job settings are not valid.Read the text after the colon. See Quota and requests.
A job fails with `download` exited 22A command of the step failed. curl -f exits 22 on an HTTP error.Read the log of the job. Correct the command or the input.