GitLab CI jobs can share variables, not by magic, but by writing them to a file and then declaring that file as an artifact.

Let’s see this in action. Imagine you have a build job that generates a version number and you want your deploy job to use that exact same version number.

stages:
  - build
  - deploy

build_job:
  stage: build
  script:
    - export VERSION_NUMBER=$(date +%Y%m%d%H%M%S)
    - echo "Generated version: $VERSION_NUMBER"
    - echo "VERSION_NUMBER=$VERSION_NUMBER" > build.env # Write to a file
  artifacts:
    reports:
      dotenv: build.env # Declare the file as a dotenv artifact

deploy_job:
  stage: deploy
  needs: ["build_job"] # Ensure build_job runs first
  script:
    - echo "Deploying version: $VERSION_NUMBER" # Access the variable
    - echo "Deploying with configuration from $CI_CONFIG_PATH"

When build_job runs, it sets the VERSION_NUMBER environment variable within its own execution. Crucially, it then writes this variable and its value into a file named build.env. The artifacts:reports:dotenv declaration tells GitLab CI that build.env is not just any file, but a special file containing environment variables that should be made available to downstream jobs.

The deploy_job uses needs: ["build_job"] to ensure it runs only after build_job completes successfully. Because build.env was declared as a dotenv artifact, GitLab CI automatically downloads it and loads its contents as environment variables before deploy_job’s script begins. This means $VERSION_NUMBER is directly available in the deploy_job’s script, holding the value generated by build_job.

This pattern is incredibly useful for passing configuration, generated secrets, or any dynamic values produced in one stage to subsequent stages. The key is understanding that GitLab CI doesn’t have a direct "variable pipeline" mechanism; it leverages its artifact system to achieve this inter-job communication. The dotenv report type is a specific, convenient way to format these shared variables for easy consumption.

The needs keyword is essential here. Without it, deploy_job might run in parallel with build_job or even before it, and the artifact wouldn’t be available. needs establishes a directed acyclic graph (DAG) for your pipeline, ensuring dependencies are met. You can also specify dependencies: ["build_job"] in deploy_job for older GitLab versions or when you need more granular control over which artifacts are downloaded, though needs is generally preferred for its clarity in defining job relationships.

A common pitfall is forgetting to declare the .env file as an artifact. If you just write to build.env but don’t declare it under artifacts:reports:dotenv, the deploy_job will never receive the variables. It’s also important that the filename declared in dotenv exactly matches the filename written to in the script.

You can pass multiple variables by simply adding more lines to the .env file, each in the KEY=VALUE format. GitLab will parse all of them.

The underlying mechanism involves GitLab Runner downloading the specified artifact file to the job’s working directory and then sourcing it (like a shell script) into the job’s environment before the script section executes. This is why you can directly use the variables as if they were defined in the .gitlab-ci.yml file itself.

If your downstream job needs variables from multiple upstream jobs, you can list them all in the needs array, and if each upstream job provides a dotenv artifact, all those variables will be merged into the downstream job’s environment.

The most surprising thing is how this mechanism can be used to inject secrets. If a build job, for instance, generates an API key or a token, it can write it to a .env file and declare it as a dotenv artifact. This key then becomes available to a deploy job without ever needing to be stored in GitLab’s CI/CD variables section or exposed in plain text within the .gitlab-ci.yml file itself, provided the artifact is not publicly accessible.

When you define a dotenv artifact, GitLab CI will automatically make those variables available to subsequent jobs that have needs defined to include the job producing the artifact.

Want structured learning?

Take the full Gitlab course →