Skip to main content

Run your GitLab CI pipeline locally

Loom runs an existing .gitlab-ci.yml on your machine without a copy, a generator, or hand-written pipeline variables. This guide takes a repository that already has a GitLab pipeline from install to a passing local run.

1) Install Loom​

Follow the release-install procedure and confirm the binary:

loom version

The steps below describe current repository source. The pinned installation example is an older release; verify loom context init --help exists in the version you installed, or build current source.

Jobs with an image: need a reachable Docker daemon; jobs without one run on your host shell. See Docker provider and Host provider.

2) Generate a pipeline context​

GitLab evaluates workflow:rules and rules: against predefined variables that only exist on a runner. loom context init derives them from your checked-out branch and writes a loom.gitlab-pipeline-context.v1 file:

loom context init --as push --name main

This creates .loom/contexts/main.json with CI_PIPELINE_SOURCE=push, the branch, default branch, HEAD and parent commit hashes, and a changes block that lets rules:changes resolve through git. Use --as mr for a merge request pipeline against the default branch. Re-run with --force after new commits; the file pins commit hashes. The full field list is in the GitLab pipeline context contract.

3) Validate​

loom check --context main

Without --workflow, Loom discovers .gitlab-ci.yml at the repository root and infers the GitLab format from its content. --context main is the short name for .loom/contexts/main.json. A passing check prints loom check passed plus any non-blocking GL_LIMIT_* warnings; blocking diagnostics carry a path:line:col location and, where Loom knows one, a hint: line with the fix. The keyword compatibility matrix lists every keyword and diagnostic code.

4) Inspect the plan​

loom compile --context main > plan.json

The plan report goes to stdout as JSON; stderr ends with a one-line summary such as 7 jobs selected, 0 skipped, state executable. A non_executable state means a capability is missing locally: runner tags go into the context's runner_tags, and privileged services need explicit consent at run time.

5) Run​

loom run --context main

Jobs run in stage order with the same selection as the check. Grant a privileged service (for example Docker-in-Docker) explicitly:

loom run --context main --privileged-service docker

Execution records land in .loom/.runtime/runs/<run_id>/state.jsonl and events.jsonl; read them with loom logs <run_id> --state and loom logs <run_id> --json. loom check writes validation receipts separately. See Runtime logs and Run evidence and receipts.

after_script​

Loom executes GitLab after_script after a started primary phase succeeds, fails with an ordinary command error, or is canceled. It uses a fresh shell in the workspace root with the declared job environment. Shell exports, aliases, and working-directory changes from before_script or script do not carry over; files do. CI_JOB_STATUS is success, failed, or canceled for this phase. Defaults and extends apply. A job's [] clears inherited commands; null discards the parent value and permits a selected default to apply again.

Finalization has a five-minute budget. Set RUNNER_AFTER_SCRIPT_TIMEOUT to a positive Go duration such as 2m to override it. An enclosing deadline still bounds the phase. Jobs canceled before primary execution, provider/start failures, and exhausted enclosing deadlines do not start finalization.

An ordinary after_script exit failure or its own timeout remains visible in phase records and does not replace the primary job outcome. Operational errors such as output capture or container cleanup failure still fail the run. Cache and artifact collection follows finalization, so it includes files written by this phase; cancellation does not guarantee publication. Native Loom workflows retain their existing strict finalization outcome and 30-second budget.

6) Stop repeating flags​

Persist the workflow, format, and context in .loom/config.yml:

defaults:
workflow: .gitlab-ci.yml
format: gitlab
context: main

After that, bare loom check, loom compile, and loom run use the same inputs; flags and the LOOM_WORKFLOW, LOOM_FORMAT, and LOOM_CONTEXT variables still win. See Runtime config syntax.

Colima and other VM-based daemons​

Loom does not read docker context; point it at the daemon socket:

export DOCKER_HOST=unix://$HOME/.colima/default/docker.sock

The default ephemeral_volume workspace mode works with any daemon. If you switch to runtime.docker.workspaceMount: bind_mount, the VM must share the directory that holds Loom's workspace snapshots; Loom probes this before the first job and fails fast with a message naming the daemon. Setting TMPDIR under $HOME is the usual fix. See bind_mount on VM-based daemons.

Next steps​