Passing Values Between GitLab CI Jobs With dotenv Reports
Need a build job to hand an image tag or version to a deploy job? GitLab's artifacts:reports:dotenv passes dynamic variables between jobs cleanly. Here's the pattern.
- #gitlab-cicd
- #ai
- #artifacts
- #variables
- #dotenv
Sooner or later a pipeline needs to compute something in one job and use it in another. A build job generates an image tag from the commit and date; a deploy job three stages later needs that exact tag. Writing it to a file and re-parsing it is fragile, and you can’t just export a variable across jobs because each job runs in its own environment. The clean answer is artifacts:reports:dotenv — GitLab reads a dotenv file your job produces and turns each line into a real CI variable in downstream jobs. Here’s the pattern and the sharp edges.
The core mechanism
A job writes KEY=value lines to a file and declares it as a dotenv report. Downstream jobs that depend on it receive those keys as variables.
build:
stage: build
script:
- export IMAGE_TAG="$CI_COMMIT_SHORT_SHA-$(date +%s)"
- echo "IMAGE_TAG=$IMAGE_TAG" >> build.env
- docker build -t "$REGISTRY/app:$IMAGE_TAG" . && docker push "$REGISTRY/app:$IMAGE_TAG"
artifacts:
reports:
dotenv: build.env
Now $IMAGE_TAG exists in jobs that need this one:
deploy:
stage: deploy
needs: ["build"]
script:
- helm upgrade app ./chart --set image.tag="$IMAGE_TAG"
The deploy job sees IMAGE_TAG set to whatever build computed. No file shuffling, no re-deriving the value and risking a mismatch.
The dependency rule that trips people up
A downstream job only inherits dotenv variables if it actually depends on the producing job — via needs: or, in stage order, via dependencies:. If deploy doesn’t list build in needs:, it won’t see IMAGE_TAG, and you’ll get an empty variable with no error. The job just deploys app: with a blank tag, which fails confusingly later.
deploy:
needs:
- job: build
artifacts: true
Being explicit with needs: and artifacts: true makes the dependency unambiguous. When a dotenv variable comes through empty, the dependency wiring is the first thing to check.
Forwarding to child pipelines
Dotenv variables can flow into downstream child or multi-project pipelines too. A trigger job that produces a dotenv report passes those variables to the downstream pipeline, which is how you parameterize a child pipeline with values computed at runtime:
build-and-trigger:
stage: build
script:
- echo "VERSION=$(./compute-version.sh)" >> trigger.env
artifacts:
reports:
dotenv: trigger.env
run-downstream:
stage: deploy
needs: ["build-and-trigger"]
trigger:
project: ops/deployer
The downstream pipeline receives VERSION, subject to your trigger:forward settings. This is far cleaner than hardcoding values or making the child re-compute them.
Watch the precedence and the secrets
Two cautions. First, dotenv variables have a defined precedence relative to other variable sources — a project CI/CD variable of the same name can win or lose depending on scope, so don’t name a dotenv key the same as an existing variable unless you’ve checked which wins. Second, don’t put secrets in dotenv reports. The report is stored as an artifact; treat its contents as readable by anyone with artifact access. Pass an image tag or a version, never a token.
Let AI write the producer/consumer pair — and the empty-value check
The mechanics are simple but the failure mode (silent empty variable) is annoying, so I have AI generate the matched pair plus a guard:
Prompt: “Write a GitLab CI build job that computes a semver-ish IMAGE_TAG, writes it to a dotenv report, and a deploy job in a later stage that consumes it via needs. Add an early check in the deploy job that fails fast with a clear message if IMAGE_TAG is empty, so a broken dependency doesn’t deploy a blank tag.”
The guard is the valuable bit:
Output (excerpt):
deploy: needs: [{ job: build, artifacts: true }] script: - test -n "$IMAGE_TAG" || { echo "IMAGE_TAG empty — check needs: on build"; exit 1; } - helm upgrade app ./chart --set image.tag="$IMAGE_TAG"
That one test -n line converts a silent, confusing deploy-with-blank-tag into a loud, obvious failure pointing straight at the dependency wiring. Verify the real pipeline carries the value through — don’t assume. For reusable versions, see the downstream dotenv variable passing prompt and the wider GitLab CI/CD category.
The bottom line
artifacts:reports:dotenv is the right way to pass a computed value — an image tag, a version, a deployment URL — from one GitLab job to another. Write KEY=value lines, declare the report, and make sure the consuming job genuinely depends on the producer via needs:, or the variable arrives empty with no warning. Guard against that empty value with an early check, keep secrets out of the report, and mind the precedence against same-named variables. It’s a small, sturdy primitive that replaces a lot of fragile file-passing hacks.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.