Local & air-gapped
Nothing about ci-doctor requires CI, and nothing about it requires the public internet. Both facts come from the same design rule: no runtime downloads, ever.
On your laptop
The fastest way to test a .ci-doctor.yml or reproduce an attribution you disagree with:
# Replay a log you saved from a failed job — no network, no token, no LLM:
ci-doctor analyze ~/Downloads/failing-job.log
# Or hit a live run from your checkout. With no CI_PROJECT_ID / GITHUB_REPOSITORY
# set, the repo is read from your git origin (and it says so in a warning):
ci-doctor analyze 123456Passing a path that exists means no fetch is attempted at all — seeFirst run .
Ship it as an image
Build a self-contained image and push it to an internal registry:
# Build once where there is internet, push to your internal registry:
docker build -t ci-doctor:latest .
docker tag ci-doctor:latest registry.internal.example.com/ci-doctor:latest
docker push registry.internal.example.com/ci-doctor:latest
# The image needs no network at job runtime beyond your GitLab/LLM endpoints.…or as an offline wheel bundle
Where Docker isn't an option:
# Where there IS internet — build a wheel bundle:
pip wheel . -w ./wheels
# Copy ./wheels inside the air-gapped network, then:
pip install --no-index --find-links ./wheels ci-doctorrWhat it still needs to reach
- Your GitLab or GitHub API host — unless you replay a saved log, in which case nothing.
- Your LLM endpoint, if you configured one. Skip it and the deterministic report is the output.
Nothing else. No telemetry, no update checks, no model or rule downloads, no hardcoded hosts. The one thing to avoid in a strict air gap is the litellm backend: it may pull tiktoken, which fetches its vocabulary at runtime.Requirements has the full network story.