When your Go project depends on an internal library living in a private GitLab repository, a plain go get will fail — GitLab doesn’t know who is asking, and Go doesn’t know the repository is private. Fortunately, a handful of configuration steps sorts everything out, both locally and in CI.
1. Tell Go the module is private
By default, the Go toolchain tries to resolve modules through the public proxy (proxy.golang.org) and checksum database (sum.golang.org). For private repositories, you need to bypass both.
Set the GOPRIVATE environment variable to match your GitLab group or project:
export GOPRIVATE=gitlab.com/yourgroup/*
In a GitLab CI pipeline, declare it as a CI/CD variable so every job inherits it automatically:
variables:
GOPRIVATE: gitlab.com/yourgroup/*
This single variable instructs the Go toolchain to skip the proxy and the checksum database for any module whose path matches the pattern, and to fetch it directly from the source.
2. Allow CI job token access on the private module’s project
The consuming project’s pipeline uses a CI_JOB_TOKEN to authenticate. For that token to be accepted by the repository hosting the private module, you need to explicitly grant it access.
In the project that hosts the private module, navigate to:
Settings → CI/CD → Job token permissions
From there:
- Enable Allow access to this project with a CI/CD job token
- Add the group or specific project that needs to consume the module
This is a GitLab security boundary: no project can pull your private module via job token unless you explicitly allowlist it here.
3. Configure .netrc authentication in your pipeline
Even with GOPRIVATE set and job token permissions granted, the go toolchain still needs to present credentials when it clones the repository over HTTPS. The standard way to do this is through a .netrc file.
Add a before_script to your job (or globally) to generate it on the fly using the ephemeral CI_JOB_TOKEN:
before_script:
- |
echo -e "machine gitlab.com\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > ~/.netrc
Git (and by extension the Go toolchain) will automatically pick up ~/.netrc when authenticating against gitlab.com. The token is ephemeral and scoped to the running job, so there is no long-lived secret to manage.
4. Pushing a Docker image to the GitLab registry (bonus)
If your pipeline also builds and pushes a Docker image to the GitLab Container Registry, a CI_JOB_TOKEN alone is sufficient — no additional configuration needed:
build-image:
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
The predefined variables CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, and CI_REGISTRY are automatically injected by GitLab for every pipeline, so no personal access token is required for registry pushes within the same GitLab instance.
Putting it all together
Here is a minimal but complete .gitlab-ci.yml that ties everything together:
variables:
GOPRIVATE: gitlab.com/yourgroup/*
before_script:
- |
echo -e "machine gitlab.com\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > ~/.netrc
build:
image: golang:1.22
script:
- go mod download
- go build ./...
build-image:
image: docker:24
services:
- docker:dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
Summary
| Step | Where | What to do |
|---|---|---|
Set GOPRIVATE | Consumer project | Match your private module’s path |
| Enable job token access | Private module’s project | Settings → CI/CD → Job token permissions |
Write .netrc | Consumer pipeline | Inject credentials via before_script |
| Push Docker image | Consumer pipeline | Use built-in CI_REGISTRY_* variables |
Three configuration points, and your Go pipeline can seamlessly consume private internal libraries just like any public module.