Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 9 min read

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.

Free download · 368-page PDF

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.