Deploy keys and API tokens must never live in the repository, and must not be readable from every branch.
Credentials go in Settings, CI/CD, Variables at project, group or instance level, never in .gitlab-ci.yml. Each variable has options:
- Masked: the value is replaced with
[MASKED]in job logs. Values must meet length and character requirements to be maskable. Masked and hidden variables also can't be revealed in the UI after saving. - Protected: only exposed to pipelines on protected branches or tags. A developer pushing a feature branch can't read production credentials, because a job on that branch simply doesn't receive them.
- Environment scope: only exposed to jobs deploying to a matching environment, such as
production. - Type: File: GitLab writes the value to a temporary file and the variable contains the path. Ideal for kubeconfigs, certificates and service-account JSON.
The job token CI_JOB_TOKEN is generated per job and lets the job access the package registry, container registry and some APIs of permitted projects; it expires when the job ends. Control which other projects accept it with the token access allowlist.
Masking is a safety net, not a guarantee: base64-encoding or printing part of a secret bypasses it. And anyone who can push to a protected branch can exfiltrate protected variables, so protect branches carefully.
Example
deploy:
image: $CI_REGISTRY/acme/tools/kubectl:1.33 # your own tools image
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # protected branch
environment: production
script:
# KUBECONFIG is a protected, File-type variable: its value is a path
- kubectl --kubeconfig "$KUBECONFIG" get nodes
- ./notify.sh --token "$SLACK_TOKEN" # masked: logs show [MASKED]
publish-package:
script:
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file pkg.tgz
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/pkg/1.0.0/pkg.tgz"'Remember
- Secrets live in CI/CD settings, not YAML
- Protected variables reach only protected branches and tags
- File-type variables hold a path to a temporary file
Watch out for
- Leaving a deploy secret unprotected so any branch's pipeline can read it.