You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GitLab CI执行Docker Push随机报权限拒绝问题求助

GitLab CI Docker Push随机权限拒绝问题(并发Job触发)

问题场景

使用自托管GitLab或gitlab.com搭配自托管Runner,通过以下.gitlab-ci.yml配置构建并推送镜像:

build-prod:
  stage: build
  before_script:
    - cat $PROD_BACKEND_TG_ENV_FILE >> .env
    - docker login ${CI_REGISTRY} --username ${CI_REGISTRY_USER} --password ${CI_REGISTRY_PASSWORD}
  script:
    - make build-image
    - make push-image
  only:
    - prod
  tags:
    - builder-vm

当合并请求合并触发流水线,且同一Runner同时处理两个推送镜像的Job时,会随机出现如下权限拒绝错误,重新运行Job即可恢复:

$ make push-image
docker push git.optochka.com:5050/fmcg/backend-tg:prod
The push refers to repository [git.optochka.com:5050/fmcg/backend-tg]
8d4aa99cfdfc: Preparing
45e1587c781c: Preparing
016b1f09ba3b: Preparing
b1859dd19446: Preparing
4978e78c2de8: Preparing
365ccd918307: Preparing
1bba629c69e9: Preparing
139c1270acf1: Preparing
4693057ce236: Preparing
365ccd918307: Waiting
1bba629c69e9: Waiting
139c1270acf1: Waiting
4693057ce236: Waiting
denied: requested access to the resource is denied
make: *** [Makefile:10: push-image] Error 1
Cleaning up project directory and file based variables
00:00
ERROR: Job failed: exit status 1

原因分析

如果你的Runner使用的是Shell Executor,多个并发Job会共享Runner主机的Docker上下文(包括~/.docker/config.json中的登录凭证)。当两个Job同时执行docker login和docker push时,后一个Job的登录操作会覆盖前一个Job的认证信息,导致前一个Job推送镜像时使用了错误的(或已被覆盖的)凭证,最终触发权限拒绝错误。

解决方案

1. 改用Docker-in-Docker(DinD)隔离环境

使用Docker Executor让每个Job在独立的Docker容器中运行,各自维护独立的Docker登录状态,彻底避免凭证冲突。

修改后的CI配置:

build-prod:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - cat $PROD_BACKEND_TG_ENV_FILE >> .env
    - docker login ${CI_REGISTRY} --username ${CI_REGISTRY_USER} --password ${CI_REGISTRY_PASSWORD}
  script:
    - make build-image
    - make push-image
  only:
    - prod
  tags:
    - builder-vm-dind # 对应配置了DinD的Runner标签

同时需要在Runner的config.toml中配置:

[[runners]]
  name = "DinD Runner"
  url = "https://your-gitlab-instance/"
  token = "your-runner-token"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "docker:latest"
    privileged = true # 必须开启,支持DinD
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0

2. 为Shell Executor配置独立Docker目录

通过设置DOCKER_CONFIG环境变量,让每个Job使用自己的临时Docker配置目录,避免共享凭证文件。

修改后的CI配置:

build-prod:
  stage: build
  before_script:
    - export DOCKER_CONFIG=$(mktemp -d) # 创建临时目录存储当前Job的Docker配置
    - cat $PROD_BACKEND_TG_ENV_FILE >> .env
    - docker login ${CI_REGISTRY} --username ${CI_REGISTRY_USER} --password ${CI_REGISTRY_PASSWORD}
  script:
    - make build-image
    - make push-image
  only:
    - prod
  tags:
    - builder-vm
  after_script:
    - rm -rf $DOCKER_CONFIG # 清理临时目录

3. 限制Runner并发数

如果暂时无法调整Executor或配置,可直接限制Runner同一时间只处理一个Job,避免并发冲突。在Runner的config.toml中修改:

concurrent = 1 # 全局并发数,或针对单个runner设置limit=1
[[runners]]
  # ...其他配置
  limit = 1 # 该Runner最多同时执行1个Job

此方法会降低Runner利用率,仅适合资源有限或临时应急场景。

内容的提问来源于stack exchange,提问作者Sherzod

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 06:47:06