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
loom contextfor every flag of the generatorloom checkfor the receipt and diagnostic format- GitLab keyword compatibility for what runs, what is diagnosed, and what is preserved