- Published on
GitLab CI Docker 로그인 실패 - 권한·토큰 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
GitLab CI에서 docker login 이 실패하면 파이프라인이 통째로 막히고, 원인도 한 가지가 아니라서 디버깅 시간이 길어지기 쉽습니다. 특히 GitLab Container Registry는 CI_REGISTRY_USER 와 CI_REGISTRY_PASSWORD 가 자동 제공되는 반면, 외부 레지스트리(Docker Hub, ECR, GCR, Harbor 등)는 토큰 정책과 권한 모델이 제각각이라 같은 에러 메시지라도 해결법이 달라집니다.
이 글에서는 실패 유형을 에러 로그 기준으로 분류하고, 권한·토큰·레지스트리 주소·CI 변수 설정·러너(Docker-in-Docker) 구성까지 한 번에 점검할 수 있는 실전 체크리스트와 예시 .gitlab-ci.yml 을 제공합니다.
관련해서 이미지 풀 인증 문제는 쿠버네티스에서도 자주 이어지니, 증상이 비슷할 때는 AKS ImagePullBackOff 401 - ACR 연결 진단도 함께 참고하면 좋습니다. 빌드가 진행되다가 디스크 부족으로 실패한다면 Docker 빌드 no space left 캐시 정리 실전도 같이 확인하세요.
먼저 확인할 것: 실패 로그를 3가지로 분류
GitLab CI 로그에서 docker login 실패는 대체로 아래 3그룹으로 나뉩니다.
1) 인증 정보 자체가 틀림
unauthorized: authentication requireddenied: requested access to the resource is deniedincorrect username or password
대부분 토큰/비밀번호가 틀렸거나, 2FA가 켜져 있는데 비밀번호로 로그인하려고 해서 실패합니다.
2) 레지스트리 주소가 틀림
Error response from daemon: Get ...: dial tcp ...: no such hostx509: certificate signed by unknown authorityserver gave HTTP response to HTTPS client
CI_REGISTRY 혹은 DOCKER_REGISTRY 변수 값이 잘못됐거나, 사설 레지스트리의 TLS 인증서/프로토콜 설정이 맞지 않을 때 발생합니다.
3) 토큰은 맞는데 권한이 없음
denied: requested access to the resource is deniedinsufficient_scope: authorization failed
레지스트리에 로그인은 되지만 푸시 권한이 없는 경우가 여기에 해당합니다. 예를 들어 GitLab 프로젝트/그룹 권한이 Reporter 인데 push 를 시도하거나, Deploy Token이 read_registry 만 있고 write_registry 가 없는 경우가 대표적입니다.
GitLab Container Registry에서 가장 안전한 기본 패턴
GitLab Container Registry를 쓴다면, 가장 단순하고 안전한 방식은 GitLab이 제공하는 내장 변수로 로그인하는 것입니다.
CI_REGISTRY: 레지스트리 호스트CI_REGISTRY_IMAGE: 프로젝트 이미지 경로CI_REGISTRY_USER: 기본적으로gitlab-ci-tokenCI_REGISTRY_PASSWORD:CI_JOB_TOKEN
권장 .gitlab-ci.yml 예시
아래 예시는 비밀번호를 로그에 남기지 않기 위해 --password-stdin 을 사용합니다.
image: docker:27
services:
- name: docker:27-dind
variables:
DOCKER_TLS_CERTDIR: ""
stages:
- build
build_and_push:
stage: build
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
여기서 자주 틀리는 포인트
docker login대상은CI_REGISTRY_IMAGE가 아니라CI_REGISTRY입니다.CI_REGISTRY_IMAGE는registry.gitlab.com/group/project같은 리포지토리 경로이고, 로그인은 호스트(registry.gitlab.com)로 합니다.
실패 원인 1: 2FA 켠 계정에 비밀번호로 로그인
Docker Hub나 GitLab 모두 2FA를 켜면 일반 비밀번호로 레지스트리 인증이 막히는 경우가 많습니다(정책에 따라 다름). 이때는 아래 중 하나로 해결합니다.
- GitLab Container Registry: 가급적
CI_JOB_TOKEN기반(CI_REGISTRY_USER/CI_REGISTRY_PASSWORD) 사용 - 외부 레지스트리: Personal Access Token(PAT) 또는 Access Token 사용
GitLab PAT로 로그인해야 하는 경우
예를 들어 다른 프로젝트의 레지스트리에 푸시해야 해서 CI_JOB_TOKEN 으로 권한이 안 나오는 경우가 있습니다. 이때는 PAT 또는 Deploy Token을 사용합니다.
- PAT:
read_registry,write_registry범위 필요 - Deploy Token:
read_registry,write_registry체크 필요
GitLab UI에서 토큰을 만들 때 권한을 최소로 주되, 푸시가 필요하면 write_registry 는 반드시 포함해야 합니다.
실패 원인 2: 권한 부족(로그인은 되는데 push가 거부됨)
에러 메시지가 denied 로 나오면 토큰이 틀린 것처럼 보이지만, 실제로는 권한 부족인 경우가 많습니다.
체크리스트
- 대상 프로젝트/그룹에서 내 계정 권한이
Developer이상인가 - Deploy Token을 썼다면
write_registry가 켜져 있는가 - GitLab 설정에서
CI_JOB_TOKEN으로 레지스트리 접근이 허용돼 있는가(프로젝트 설정 정책) - 푸시 대상 이미지 경로가 정확한가(다른 네임스페이스로 푸시하고 있지 않은가)
교차 프로젝트 푸시(다른 프로젝트 Registry로 푸시)
CI_JOB_TOKEN 은 기본적으로 “현재 프로젝트” 범위에 묶이는 경우가 많습니다. 다른 프로젝트의 레지스트리에 푸시하려면 아래 중 하나가 필요합니다.
- 대상 프로젝트에서
CI_JOB_TOKEN접근 허용 정책을 열어주기 - PAT 또는 Deploy Token 사용
실패 원인 3: GitLab CI 변수 마스킹/개행 문제로 토큰이 깨짐
GitLab CI Variables에서 토큰을 저장할 때, 다음 문제가 자주 발생합니다.
- 토큰 앞뒤에 공백이 들어감
- 줄바꿈이 포함됨(복사 과정에서)
- 마스킹 규칙 때문에 값이 일부 변형되거나, 로그에서 확인이 불가해 원인 파악이 어려움
안전한 디버깅 방법(값 노출 없이 길이만 확인)
토큰을 그대로 출력하면 보안 사고로 이어질 수 있으니, 길이만 확인합니다.
echo -n "$REGISTRY_TOKEN" | wc -c
또는 공백/개행 제거 후 로그인합니다.
echo -n "$REGISTRY_TOKEN" | tr -d '\n' | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
실패 원인 4: 레지스트리 URL/프로토콜 불일치
사설 레지스트리에서 특히 흔한 케이스입니다.
- 레지스트리는 HTTP인데 클라이언트가 HTTPS로 접속
- 사내 CA 인증서가 러너에 설치되어 있지 않음
- 프록시/방화벽이 TLS를 가로채서 인증서가 바뀜
증상별 힌트
server gave HTTP response to HTTPS client: 레지스트리가 HTTP인데 HTTPS로 접근 중x509: certificate signed by unknown authority: CA 인증서 미신뢰
해결 방향
- 가능하면 레지스트리를 HTTPS로 표준화
- 러너 머신(또는
dind서비스 컨테이너)에 CA 인증서 설치 - 불가피하게 Insecure Registry를 써야 한다면 Docker 데몬 설정에 insecure registry를 추가(권장하지 않음)
GitLab Shared Runner에서는 데몬 설정 변경이 어렵기 때문에, 사설 레지스트리 접근이 필요하면 자체 호스팅 러너를 고려하는 편이 빠릅니다.
실패 원인 5: Docker-in-Docker 구성 문제로 로그인 명령이 다른 데몬에 적용됨
docker login 은 “현재 docker CLI가 연결된 Docker 데몬”에 인증 정보를 저장합니다. GitLab CI에서 dind 를 쓰는 경우, CLI가 dind 데몬을 바라보도록 설정이 맞지 않으면 로그인해도 빌드/푸시 단계에서 인증이 안 된 것처럼 보일 수 있습니다.
안정적인 dind 구성 예시
image: docker:27
services:
- name: docker:27-dind
command: ["--mtu=1460"]
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
before_script:
- docker info
- echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
docker info 가 정상 출력되지 않으면, 그 다음 단계의 로그인/빌드/푸시도 연쇄적으로 실패합니다.
외부 레지스트리별 로그인 패턴(자주 쓰는 형태)
Docker Hub
- 2FA가 켜져 있으면 PAT 사용 권장
echo "$DOCKERHUB_TOKEN" | docker login docker.io -u "$DOCKERHUB_USER" --password-stdin
AWS ECR
ECR은 docker login 비밀번호가 고정 토큰이 아니라, CLI로 매번 발급받는 형태가 일반적입니다.
aws ecr get-login-password --region ap-northeast-2 | docker login "$ECR_REGISTRY" -u AWS --password-stdin
GCR/Artifact Registry
워크로드 아이덴티티/서비스 계정 키 방식에 따라 달라지지만, 핵심은 “도커가 읽을 수 있는 인증”을 제공하는 것입니다.
보안 모범 사례: 토큰 대신 Job Token, 그리고 최소 권한
- GitLab Container Registry는 가능하면
CI_JOB_TOKEN기반으로 해결합니다. - PAT를 써야 한다면 만료 기간을 짧게, 범위는
read_registry/write_registry정도로 최소화합니다. - 토큰을 Git 저장소에 커밋하지 말고 GitLab CI Variables에 저장합니다.
- 로그인은 항상
--password-stdin을 사용해 로그 노출을 막습니다.
빠른 진단 체크리스트(10분 컷)
- 로그인 대상이
CI_REGISTRY인지 확인 CI_REGISTRY_USER/CI_REGISTRY_PASSWORD로 GitLab Registry 로그인 시도- 2FA 사용 여부 확인, 외부 레지스트리는 PAT/Access Token 사용
- 토큰에 공백/개행이 섞이지 않았는지 길이로 확인
- 권한 범위에
write_registry가 있는지 확인 - 교차 프로젝트/그룹 푸시라면
CI_JOB_TOKEN정책 또는 PAT/Deploy Token으로 전환 dind사용 시DOCKER_HOST/DOCKER_TLS_CERTDIR설정과docker info확인- TLS 에러면 CA 인증서/HTTPS 설정 점검
마무리: 에러 메시지보다 “권한 모델”부터 의심하자
docker login 실패는 표면적으로는 인증 오류처럼 보여도, 실제로는 “토큰이 맞는데 권한이 없다” 또는 “로그인은 다른 데몬에 해놓고 푸시는 다른 데몬으로 한다” 같은 구조적 문제가 많습니다.
위의 분류(인증 정보, 주소/TLS, 권한)대로 좁혀가면 재현 환경이 달라도 해결 속도가 빨라집니다. 특히 GitLab Container Registry를 쓴다면 CI_JOB_TOKEN 기반을 기본값으로 두고, 교차 프로젝트나 외부 레지스트리로 확장될 때만 PAT/Deploy Token을 도입하는 전략이 운영·보안 모두에서 가장 안정적입니다.