How to Pass Build Metadata Between GitLab Jobs with dotenv Artifacts
Pass computed values like version tags, package paths, and review URLs between GitLab CI/CD jobs with dotenv report artifacts. Learn when to use them, how `needs` and `dependencies` affect inheritance, and where the security limits really are.
By Network Nuts · Published 2026-10-15
A common GitLab CI/CD problem appears right after your first useful pipeline: one job computes something valuable, but later jobs need the same value without recomputing it. Typical examples include a generated version string, a package filename, a review URL, or a build number derived from the branch and pipeline ID.
GitLab documents dotenv report artifacts for exactly this handoff pattern. A job writes variables to a .env-style file, saves that file with artifacts:reports:dotenv, and later jobs receive those values as CI/CD variables through the documented dotenv variables workflow. GitLab also makes two operating rules clear: dotenv variables are for later job execution, and they are not available during rules evaluation.
This guide is for GitLab users who already have a working runner and want a clean, official way to move small computed values through a pipeline without hardcoding them. The examples below are lab examples only. Review them before using them in a shared or production pipeline.
When dotenv artifacts are the right tool
Use a dotenv artifact when the producer job creates small key-value metadata that later jobs should consume as environment variables. GitLab's dotenv variables guide shows this pattern for passing computed values between stages, setting dynamic environment URLs, and sharing values with downstream pipelines.
Good candidates include:
- generated version tags
- artifact filenames
- deployment URLs
- image tags
- package coordinates
Do not use this pattern for secrets. GitLab explicitly warns not to place credentials, API keys, or tokens in dotenv files because pipeline users can access the contents. If a value is sensitive, keep it in masked or protected CI/CD variables or another approved secret workflow instead, not in a dotenv artifact. See the dotenv variables guide and job artifacts documentation.
Prerequisites
Before using this pattern, make sure you have:
- a project with GitLab CI/CD enabled
- a runner that can execute ordinary shell commands
- permission to edit
.gitlab-ci.yml - a producer job that can write a plain-text
.envfile
Also follow GitLab's documented dotenv file rules from the dotenv variables guide:
- one
NAME=valuepair per line - UTF-8 encoding only
- no empty lines
- no comments
- no multiline values
- no special-character escaping expectations
GitLab's docs currently show a 5 KB maximum dotenv file size in the dotenv requirements, and GitLab Self-Managed administrators can change that limit or set it to 0 to disable it under CI/CD limits. If your data is JSON, multiline text, or anything more complex than a few scalar values, use a regular file artifact instead of dotenv.
A minimal working pipeline
The following example computes three values once and reuses them later:
VERSION_TAGPACKAGE_FILEREVIEW_URL
stages:
- prepare
- test
- package
- deploy
prepare_meta:
stage: prepare
script:
- mkdir -p dist
- VERSION_TAG="${CI_COMMIT_REF_SLUG}-${CI_PIPELINE_IID}"
- PACKAGE_FILE="dist/${CI_PROJECT_NAME}-${CI_COMMIT_SHORT_SHA}.txt"
- REVIEW_URL="https://${CI_PROJECT_PATH_SLUG}-${CI_PIPELINE_IID}.example.test"
- printf 'VERSION_TAG=%s\n' "$VERSION_TAG" > build.env
- printf 'PACKAGE_FILE=%s\n' "$PACKAGE_FILE" >> build.env
- printf 'REVIEW_URL=%s\n' "$REVIEW_URL" >> build.env
- printf 'demo artifact for %s\n' "$VERSION_TAG" > "$PACKAGE_FILE"
artifacts:
access: developer
expire_in: 1 day
paths:
- dist/
reports:
dotenv: build.env
unit_test:
stage: test
needs:
- job: prepare_meta
artifacts: true
script:
- test -n "$VERSION_TAG"
- test -f "$PACKAGE_FILE"
- echo "Testing package built for $VERSION_TAG"
package_info:
stage: package
needs:
- job: prepare_meta
artifacts: true
script:
- echo "Package file is $PACKAGE_FILE"
- ls -l dist/
deploy_review:
stage: deploy
needs:
- job: prepare_meta
artifacts: true
- job: unit_test
artifacts: false
script:
- echo "Deploying $VERSION_TAG"
- echo "Review URL: $REVIEW_URL"
The key line is reports: dotenv: build.env. GitLab's artifacts reports reference documents the dotenv report type, while the dotenv variables guide explains how later jobs receive those values. The producer job also stores dist/ as a normal artifact so later jobs can verify the file path they received.
How this pipeline behaves
In prepare_meta, the job calculates the values once, writes them to build.env, and uploads that file as a dotenv report artifact.
By default, GitLab says a job receives dotenv variables from all jobs in earlier stages. But once a job uses needs, dotenv inheritance narrows to the jobs listed in needs. That is why every consumer in this example explicitly names prepare_meta.
The deploy_review job shows another useful refinement: unit_test is needed for execution order, but its artifacts are not needed, so artifacts: false avoids an unnecessary artifact download. That behavior is documented in the needs reference and the broader CI/CD YAML syntax reference.
Expected lab result:
unit_testsees a populatedVERSION_TAGunit_testfinds the file referenced byPACKAGE_FILEpackage_infolists the generated file underdist/deploy_reviewprints the same version tag and review URL created inprepare_meta
If test -n "$VERSION_TAG" fails, the usual cause is that the consumer job is not actually inheriting the producer's dotenv artifact.
Four mistakes that cause most dotenv pipeline failures
1. Expecting dotenv variables to work in rules
This is the most common mistake. GitLab is explicit in the dotenv variables guide: dotenv variables can be used in job scripts and in runner-side expansion contexts, but not in rules. If you need to decide whether a job exists, base that decision on predefined variables or other pipeline-creation-time inputs instead.
2. Forgetting that needs changes inheritance
Without needs, later jobs can receive dotenv values from earlier stages automatically. After you introduce needs, only the listed jobs supply dotenv variables. If a job suddenly stops seeing VERSION_TAG, check whether the producer job was omitted from needs. GitLab calls this out in both the dotenv guide and the needs documentation.
3. Treating artifacts:access as a secret-protection feature
Dotenv files are still artifacts, and GitLab documents artifacts:access for controlling who can download job artifacts from the GitLab UI or API. In the example above, access: developer is a reasonable lab default for non-secret metadata.
But GitLab also documents an important limitation: artifacts:access only affects UI and API downloads. CI/CD jobs using job tokens can still access artifacts with the runner API regardless of that setting. If you need tighter job-token-based restriction, GitLab points you to the project's CI/CD visibility setting for project-member-only access. Even then, the safer rule does not change: never store secrets in dotenv artifacts.
4. Keeping metadata artifacts longer than necessary
GitLab documents artifacts:expire_in in the YAML syntax reference. For short-lived build metadata, storing artifacts for a day or a week is usually enough. This matters in busy repositories where even tiny artifacts accumulate over time.
What if the producer job is conditional?
If the producer job sometimes does not exist because of rules, a downstream needs relationship can fail pipeline creation. GitLab's needs documentation covers this with optional: true.
deploy_review:
needs:
- job: prepare_meta
artifacts: true
optional: true
That pattern is useful when metadata should exist only in specific pipeline types, such as merge request pipelines.
When to use dependencies instead
If you want a later-stage job to inherit dotenv variables from only one earlier producer, but you do not need DAG-style early execution, dependencies can be simpler than needs. GitLab documents this directly in the dotenv variables guide.
prepare_meta:
stage: prepare
script:
- echo "BUILD_VERSION=v1.0.0" > build.env
artifacts:
reports:
dotenv: build.env
package_info:
stage: package
dependencies:
- prepare_meta
script:
- echo "$BUILD_VERSION"
Expected result:
package_inforuns in normal stage orderBUILD_VERSIONis available in the job script- no other earlier job artifacts are inherited unless listed
Use needs when you want faster execution or cross-stage DAG behavior. Use dependencies when you mainly want tighter artifact inheritance in a normal staged pipeline.
Cleanup
This lab does not create cloud resources, but it does create pipeline artifacts and a demo file under dist/.
Cleanup is simple:
- remove the demo jobs from
.gitlab-ci.ymlif you no longer need the lab - let the artifact expire naturally with
expire_in - delete the test branch if you created one only for this exercise
If you adapt this pattern to real review environments or deployments, add a proper stop or teardown job for the environment itself. The dotenv file should carry metadata, not become your only cleanup mechanism.
Final thoughts
For GitLab pipelines, dotenv artifacts are the cleanest official solution when one job computes small, non-secret values that later jobs must reuse. The operating rules are straightforward: keep the file simple, keep it small, do not put secrets in it, remember that rules cannot see dotenv values, and be explicit with needs or dependencies so inheritance stays predictable.
If you want structured hands-on practice with pipelines, runners, security scans, registries, DAG execution, caching, and Kubernetes delivery, see Automating CI/CD with GitLab.