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.
| Use | Start | Example |
|---|---|---|
| Data refresh | A schedule, for example 02:00 UTC every day | Download a dataset, check it and load it into a database. |
| Model fetch and process | A call from your code | Fetch a model from Hugging Face with HF_TOKEN, then convert or evaluate it. |
| Event-driven work | A webhook call from another system | Rebuild a search index when a content system sends an event. |
| Release | After a test pipeline ends with success | Copy a tested image to your container registry. |
| Tests | A call, or a schedule | Run the tests of a repository on each matrix value. |
| Part | Description | See |
|---|---|---|
| Definition and versions | The JSON that describes the pipeline. Each change is a new numbered version that never changes. | Definitions and versions |
| Jobs and steps | The units of work, their order, conditions, matrix and runtime. | Jobs and steps |
| Triggers and inputs | What starts a run, and the values that a run takes. | Triggers and inputs |
| Runs | One execution of one version, with its state and the log of each job. | Runs |
| Secrets, registries and environments | Credentials, image pushes and the services that jobs reach. | Secrets, registries and environments |
| Code | How 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
A start request reads one stored version. It checks the inputs and the secrets, and expands the matrix into jobs.
The platform records the run as
queuedand answers202at once. If the version cannot run, the call fails with422and records nothing.A worker takes the run. Each job starts when the jobs in its
needshave ended, with up to 8 jobs of a run at the same time.Each job runs in a new sandbox. Its steps run in order in that sandbox, each in its own shell, so
cdandexportdo not carry to the next step. Its output goes to the log of the job.When the last job ends, the run ends as
success,failureorcancelled. 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 state | Meaning |
|---|---|
queued | The run is recorded. No job has started. |
running | The platform runs the jobs. |
success | All jobs ended without failure. |
failure | A job failed, or the run could not be prepared. detail gives the cause. |
cancelled | The run was cancelled. |
Job state | Meaning |
|---|---|
pending | The job waits for its needs. |
running | The job runs. sandbox names its sandbox. |
success | All steps exited with code 0. |
failure | A step failed, or the job did not start. failedStep, exitCode and detail give the cause. |
skipped | The if condition was false, or a needed job did not succeed. |
cancelled | A cancel stopped the job. |
Access
A pipeline belongs to an owner: you, or a team. The owner of a pipeline also owns its runs.
| Action | Personal pipeline | Team pipeline |
|---|---|---|
| Create, change, delete, rotate the webhook | You | A team admin |
| Read definitions, versions and runs | You | An active team member |
| Start and cancel a run | You | An 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.
| Item | Limit |
|---|---|
| Pipelines for one owner | 50 |
| Versions of one pipeline | 100 |
| Size of a definition | 64 KiB |
| Jobs of a pipeline | 50 |
| Steps of a job | 100 |
| Jobs of one run at the same time | 8 |
| Job time limit | timeoutMinutes, 1 to 1440, default 60 |
| Combinations of a matrix | 64 |
| Webhook call | 256 KiB body, and 30 calls in one minute |
Chain of after triggers | 5 pipelines deep |
| Run records and job logs | Kept for 90 days |
Where to go next
- Definitions and versions: change a pipeline and go back to an earlier version.
- Triggers and inputs: start runs on a schedule, from a webhook or after another pipeline.
- Runs: pin a version, follow logs and cancel a run.
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
- Access: you. For a team, an admin.
- References: Pipeline definition,
POST /v1/pipelines/{owner}, SDKpipelines.create.
Procedure
Write this definition to the file
nightly-report.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 }}" } } ] } }}Create the pipeline from the file.
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"])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);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)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(" ")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")))));kloudlite pipelines create --owner acme --file nightly-report.json
Close-out
Show the pipelines of the owner.
print(kl.pipelines.list(owner="acme"))console.log(await kl.pipelines.list({ owner: "acme" }));list, err := kl.Pipelines.List("acme")if err != nil { log.Fatal(err)}fmt.Printf("%+v\n", list)p kl.pipelines.list(owner: "acme")System.out.println(kl.pipelines().list("acme"));kloudlite pipelines list --owner acmeMake sure that
nightly-reportshowslatestVersion1and the schedule0 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
- Access: you. For a team, an active member.
- Conditions: the pipeline
nightly-reportexists. See Create a pipeline. - References:
POST /v1/pipelines/{owner}/{pipeline}/runs, SDKpipelines.run,Run.follow,Run.wait, Runs.
Procedure
Start a run of the newest version.
run = kl.pipelines.run(owner="acme", pipeline="nightly-report")print(run.id, run.state)const run = await kl.pipelines.run({ owner: "acme", pipeline: "nightly-report" });console.log(run.id, run.state);run, err := kl.Pipelines.Run(kloudlite.RunPipeline{Owner: "acme", Pipeline: "nightly-report"})if err != nil { log.Fatal(err)}fmt.Println(run.ID, run.State)run = kl.pipelines.run(owner: "acme", pipeline: "nightly-report")puts run.id, run.statevar run = kl.pipelines().run(new Kloudlite.RunPipeline().owner("acme").pipeline("nightly-report"));System.out.println(run.id() + " " + run.state());RUN=$(kloudlite pipelines run acme/nightly-report --json | jq -r .id)Read the log of the job
fetchwhile it runs.for chunk in run.follow(job="fetch"): print(chunk, end="")for await (const chunk of run.follow({ job: "fetch" })) process.stdout.write(chunk);err = run.Follow(kloudlite.FollowLogs{Job: "fetch"}, func(chunk string) bool { fmt.Print(chunk) return true})if err != nil { log.Fatal(err)}run.follow(job: "fetch") { |chunk| print chunk }run.follow("fetch").forEach(System.out::print);kloudlite pipelines logs acme nightly-report "$RUN" fetch --followWait for the run to end. The call returns the final state.
print(run.wait())console.log(await run.wait());state, err := run.Wait(kloudlite.WaitRun{})if err != nil { log.Fatal(err)}fmt.Println(state)puts run.waitSystem.out.println(run.waitFor());kloudlite pipelines wait-run acme nightly-report "$RUN"
Close-out
- Make sure that the final state is
success. The log offetchshows thestarsandissuescounts. - If the state is
failure, readdetailof the run andfailedStepof each failed job. See Troubleshooting.
Troubleshooting
| Symptom | Possible cause | Corrective action |
|---|---|---|
422 with pipeline nightly-report is invalid: and a list of lines | The 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 lines | The 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 exists | The 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 first | The owner is at the limit. | Delete a pipeline that is not needed. |
403 with only a team admin can change team pipelines | You are a member but not an admin of the team. | Ask a team admin to make the change. |
503 with webhooks need an object store | The 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 found | The 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 paused | A team admin paused your membership. | Ask a team admin to unpause you. |
The run stays queued | No 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 22 | A 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. |