GitLab CI/CD variables aren’t just global settings; you can precisely control their availability across your projects and groups, which is way more powerful than most people realize.
Let’s see this in action. Imagine you have a group called my-company and you want to provide a common Docker registry URL and credentials to all projects within that group.
Here’s how you’d set a group-level variable:
- Navigate to your group (
my-company). - Go to Settings > CI/CD.
- Expand the Variables section.
- Click Add variable.
- For Key, enter
DOCKER_REGISTRY_URL. - For Value, enter
registry.my-company.com. - Check Protect variable if you only want it available on protected branches.
- Check Mask variable if it’s sensitive (though for a URL, this is less common).
- Click Add variable.
Now, in any project within the my-company group, you can access DOCKER_REGISTRY_URL directly in your .gitlab-ci.yml file:
build_image:
stage: build
script:
- docker build -t $DOCKER_REGISTRY_URL/my-app:$CI_COMMIT_SHORT_SHA .
- echo "Building image for $DOCKER_REGISTRY_URL"
This build_image job will automatically pick up the DOCKER_REGISTRY_URL from the group.
You can also override or add project-specific variables. Let’s say one project, my-app, needs a different registry for testing.
- Navigate to the
my-appproject. - Go to Settings > CI/CD.
- Expand the Variables section.
- Click Add variable.
- For Key, enter
DOCKER_REGISTRY_URL. - For Value, enter
test-registry.my-company.com. - Click Add variable.
In this scenario, the my-app project’s CI jobs will use test-registry.my-company.com, while other projects in the my-company group will continue to use registry.my-company.com. This is because project-level variables take precedence over group-level variables with the same key.
The hierarchy is: Project > Group > Instance (global).
This scoping is crucial for managing secrets and configuration across your organization. Instead of scattering identical variables across dozens or hundreds of projects, you define them once at the group level. This significantly reduces duplication and makes updates much simpler. If your Docker registry URL changes, you update it in one place (the group variable), and all affected projects automatically inherit the change.
When you define a variable, you have a few options:
- Key: The name of the variable (e.g.,
API_KEY). - Value: The actual secret or configuration string.
- Type:
Variable(default) orFile. File type variables are useful for certificates or key files, where the content is the file’s content. - Protect variable: If checked, the variable is only exposed to jobs running on protected branches or tags. This is critical for sensitive data like deployment credentials.
- Mask variable: If checked, GitLab will attempt to mask the variable’s value in job logs. This is effective for strings that don’t contain whitespace. Note that it’s not foolproof; if the value contains whitespace, it won’t be masked. You can also define custom regex patterns for masking.
The real power comes when you combine this with environment-specific variables. You can define a variable at the group level, say DATABASE_URL, and then within each project, override it for specific environments like staging or production.
deploy_production:
stage: deploy
script:
- echo "Deploying to production using $DATABASE_URL"
environment:
name: production
url: https://my-app.com
If DATABASE_URL is set at the project level for the production environment, that value will be used. If not, it will fall back to the project-level default, then group-level default, and finally instance-level default.
The order of precedence for variables is crucial to understand:
- Project-level variables (including environment-specific ones)
- Group-level variables
- Instance-level (global) variables
- CI/CD variables defined in
.gitlab-ci.yml(these have the lowest precedence, meaning they can be overridden by any other type)
A common pitfall is forgetting that variables defined directly in your .gitlab-ci.yml file are the least prioritized. This means if you set MY_VAR: "default" in your .gitlab-ci.yml and then define MY_VAR: "production" at the group level, the group-level variable will win. If you need a .gitlab-ci.yml variable to always take precedence, you must explicitly mark it as such, but this is rarely the desired behavior.
Understanding this scoping and precedence allows you to build highly organized and secure CI/CD pipelines, moving away from brittle, project-by-project configurations towards a centralized, manageable system.
The next thing you’ll want to explore is how to use these variables with GitLab’s approval rules for more granular control over deployments.