CI artifact collection (RUNNER_WORKSPACE_DIR + workspace mount)
Audience: operators deploying the containerized Vetrix CI worker
(appmerc_vetrixworker / vetrix-worker image, compose --target worker) who
expect pipeline artifacts — declared as artifacts.paths in vetrix-ci.yml
(e.g. coverage-summary.json) — to be collected and retrievable after a job runs.
If a job's
artifacts.pathsproduce zero stored artifacts (the pipeline Coverage Report shows "No coverage data" even though thecoveragejob succeeds), the runner has nowhere on the host to collect the files from. This doc is the remediation. When a job declaresartifacts.pathsbut no host-side workspace is available, the worker emits a WARN (see "Diagnosing" below) so the failure mode is observable instead of a silent no-op.
Why this matters
The worker runs each pipeline job in a Docker container, then — on job success —
copies the files matching the job's declared artifacts.paths and records each as
a retrievable artifact (GET /api/v1/repos/<owner>/<repo>/pipelines/<id>/artifacts/<name>).
Collection reads from the host side of a per-job workspace, not from inside the job container. So two things must be true on the deployed runner:
RUNNER_WORKSPACE_DIRis set — a host directory under which the worker provisions one per-job workspace dir and bind-mounts it into the job container at/workspace(the job's working directory andCI_PROJECT_DIR). When this is empty, no workspace is mounted (legacy / dev mode) and there is no host path for the runner to collect artifacts from — collection is skipped.- That host directory is a real, persistent, writable mount — for the containerized worker it must be bind-mounted from the host into the worker container, owned by the worker's runtime identity (see "Worker compose" below).
A job writes its artifacts (e.g. coverage-summary.json) into /workspace; because
/workspace is the container side of the host per-job dir, those files land on the
host, where the runner globs them after the job exits.
Files written outside
/workspaceare not collected.artifacts.pathsentries are file globs relative to the workspace root (/workspace). A glob that resolves to a directory is skipped — only regular files are uploaded. Make the job write its artifacts into the working directory.
Diagnosing: the missing-workspace WARN
The worker emits a loud WARN in its error.log whenever a job declares
artifacts.paths but no host-side workspace is available to collect them from. A
misconfigured deployment that lacks this workspace records zero artifacts — the
cause of an empty pipeline Coverage Report in production.
WARN in worker error.log (RUNNER_LOG_DIR=/var/log/vetrix-worker) |
Meaning | Fix |
|---|---|---|
artifact collection: job declared artifact paths but no workspace locator is wired; nothing collected — check the runner deployment (RUNNER_WORKSPACE_DIR + workspace mount) |
No workspace locator is wired (typically RUNNER_WORKSPACE_DIR unset). |
Set RUNNER_WORKSPACE_DIR and mount it (below); redeploy. |
artifact collection: job declared artifact paths but no host-side workspace is available; nothing collected — check the runner deployment (RUNNER_WORKSPACE_DIR + workspace mount) |
A locator is wired but it reports no bound workspace for this job. | Confirm the host workspace dir exists, is mounted into the worker, and is writable by the worker identity. |
artifact collection: no files matched declared path |
Workspace is wired, but the glob matched nothing — the job wrote the file elsewhere, or did not produce it. | Have the job write the artifact into the working directory (/workspace); confirm the path/glob. |
The first two WARNs point at this doc as the remediation. The third means the workspace is wired correctly and the problem is in the job, not the deployment.
A job that declares no
artifacts.pathsdoes not warn — the well-known coverage-summary auto-collect is best-effort by convention, not a misconfiguration. But it still needs a workspace: with noRUNNER_WORKSPACE_DIRthere is nothing to auto-collect either.
Wiring it on the worker
Environment
# worker service (e.g. appmerc_vetrixworker) in the deployment compose
environment:
- RUNNER_WORKSPACE_DIR=/var/lib/vetrix/runner-ws # host dir for per-job workspaces
When set, the worker logs worker: workspace root "…" and
worker: pipeline artifact collection enabled at startup. When unset it
logs worker: RUNNER_WORKSPACE_DIR not set; jobs will run without a workspace mount
— that line in your boot log is the signature of this misconfiguration.
Optionally set
RUNNER_REPOS_ROOTalongside it to have the executor populate each job's workspace with the repo source at the build ref viagit archive. It is not required for artifact collection —RUNNER_WORKSPACE_DIRalone is enough for a job to write artifacts into/workspaceand have them collected.RUNNER_COVERAGE_SUMMARY_FILE(defaultcoverage-summary.json) selects the well-known file auto-collected from the workspace root.
Worker compose (containerized worker)
The directory in RUNNER_WORKSPACE_DIR is a path inside the worker container,
so it must be backed by a host bind mount owned by the worker's runtime identity
(${DOCKERUSERID}:${DOCKERGROUPID} — the same user: pin discussed in
worker-docker-socket.md):
appmerc_vetrixworker:
user: "${DOCKERUSERID}:${DOCKERGROUPID}" # REQUIRED — must own the mount
environment:
- RUNNER_WORKSPACE_DIR=/var/lib/vetrix/runner-ws
volumes:
- ./site/vetrix/data/runner-ws:/var/lib/vetrix/runner-ws
The host dir (./site/vetrix/data/runner-ws) must exist and be writable by
${DOCKERUSERID}:${DOCKERGROUPID}. If the worker cannot write it the worker fatals
at startup and never polls — see the user: discussion in
worker-docker-socket.md.
Verify
# 1. Worker booted with a workspace root and collection enabled:
docker compose logs appmerc_vetrixworker | grep -E "workspace root|artifact collection enabled"
# Absence (or "RUNNER_WORKSPACE_DIR not set …") = not wired.
# 2. No collection WARNs after a run that declares artifacts.paths:
docker compose exec appmerc_vetrixworker grep "artifact collection" /var/log/vetrix-worker/error.log
# 3. End-to-end: trigger a pipeline whose coverage job writes coverage-summary.json,
# then confirm the artifact is retrievable and the Coverage Report panel populates:
curl -s "https://<vetrix-host>/api/v1/repos/<owner>/<repo>/pipelines/<id>/artifacts" \
-H "Authorization: Bearer <token>"
# Expect a non-empty array including "coverage-summary.json" (not []).
Worker predating artifact collection
If the deployed worker image predates artifact collection + upload, wiring
RUNNER_WORKSPACE_DIR alone is not enough — the collection code is not in the image.
Redeploy a current worker image and set RUNNER_WORKSPACE_DIR + the mount above;
the redeploy is the remediation, the env/mount is the prerequisite.
See also: runners.md (runner deployment),
worker-docker-socket.md (worker Docker-socket access
and the load-bearing user: pin), and
pipeline-variables.md (per-branch CI/CD variables).