- Published on
GitLab CI Runner에서 Docker 권한 오류 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
GitLab CI 파이프라인에서 Docker 이미지를 빌드하거나 docker login/push를 수행할 때 가장 흔하게 마주치는 실패가 permission denied 계열 오류입니다. 특히 GitLab Runner를 Docker executor로 띄워 두고, CI Job 컨테이너 안에서 다시 Docker를 호출하는 구조(Docker-in-Docker 또는 호스트 소켓 마운트)를 쓰는 경우 권한/보안 설정이 조금만 어긋나도 바로 터집니다.
이 글에서는 오류 메시지 패턴별 원인을 먼저 분류하고, 그 다음에 Runner 실행 방식(소켓 마운트 vs DinD vs rootless vs Kubernetes) 별로 재현 가능한 해결책을 제공합니다. 운영 환경에서 보안 리스크까지 함께 정리하니, 단순히 “privileged 켜라” 수준에서 끝내지 않고 상황에 맞는 선택을 할 수 있을 것입니다.
1) 대표적인 Docker 권한 오류 패턴
아래 메시지 중 하나라도 보이면, 거의 항상 “Docker 데몬에 접근 권한이 없다” 또는 “데몬이 아예 없다/다른 데몬을 보고 있다” 문제입니다.
1.1 Docker 소켓 접근 거부
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sockdial unix /var/run/docker.sock: connect: permission denied
의미: Job 컨테이너가 /var/run/docker.sock에 접근하려고 했지만, 파일 권한/그룹이 맞지 않거나 소켓 자체가 마운트되지 않았습니다.
1.2 DinD 데몬 접속 실패/권한 문제
Cannot connect to the Docker daemon at tcp://docker:2375. Is the docker daemon running?error during connect: ...
의미: DinD 서비스 컨테이너(docker:dind)가 제대로 뜨지 않았거나, TLS 설정/네트워크/privileged 설정이 맞지 않습니다.
1.3 overlay2 / iptables / mount 관련 권한 오류
failed to solve: ... permission deniedmount: permission denied (are you root?)iptables: Permission denied (you must be root)
의미: DinD는 커널 기능(네임스페이스/마운트/iptables 등)이 필요해 대부분 privileged가 요구됩니다(환경에 따라 rootless로 우회 가능).
2) 먼저 확인할 것: Runner executor와 현재 구조
문제 해결의 핵심은 “CI Job 컨테이너가 어떤 방식으로 Docker를 쓰는지”를 명확히 하는 것입니다.
- (A) 호스트 Docker 소켓 마운트 방식: Job 컨테이너가 호스트의
/var/run/docker.sock를 공유해서 호스트 Docker 데몬을 직접 사용 - (B) Docker-in-Docker(DinD): Job 컨테이너는 별도 서비스 컨테이너(docker:dind)의 Docker 데몬을 TCP로 사용
- (C) Rootless Docker/BuildKit: privileged 없이 빌드하려는 방식
- (D) Kubernetes executor: Pod 안에서 DinD/kaniko/buildkit 등을 사용
각 방식마다 “권한 오류”의 의미가 달라집니다.
3) 해결 1: 호스트 소켓 마운트 방식에서 permission denied
3.1 전형적인 .gitlab-ci.yml
image: docker:27
services: []
variables:
DOCKER_HOST: unix:///var/run/docker.sock
build:
stage: build
script:
- docker version
- docker build -t myapp:${CI_COMMIT_SHA} .
이 방식은 빠르고 단순하지만, 소켓 권한과 보안 위험이 큽니다(소켓 접근 권한 = 사실상 호스트 root급 권한).
3.2 Runner 컨테이너에 소켓 마운트가 되어 있는지 확인
Runner가 Docker로 떠 있다면 다음을 확인합니다.
docker inspect gitlab-runner --format '{{json .Mounts}}' | jq .
/var/run/docker.sock가 Runner 컨테이너에 마운트되어 있어야 하고, Runner가 띄우는 Job 컨테이너에도 마운트되도록 설정되어야 합니다.
3.3 config.toml에 volumes 설정
/etc/gitlab-runner/config.toml (Runner 컨테이너 내부 기준)에서:
[[runners]]
name = "docker-runner"
executor = "docker"
[runners.docker]
image = "docker:27"
privileged = false
volumes = [
"/var/run/docker.sock:/var/run/docker.sock",
"/cache"
]
이후 Runner 재시작:
docker restart gitlab-runner
3.4 소켓 파일의 그룹/권한 문제(가장 흔함)
호스트에서:
ls -l /var/run/docker.sock
# srw-rw---- 1 root docker ... /var/run/docker.sock
Job 컨테이너 내부 사용자가 docker 그룹에 속하지 않으면 접근이 막힙니다. 해결 옵션은 다음 중 하나입니다.
- 옵션 1) Job 컨테이너를 root로 실행 (간단하지만 권장도는 낮음)
- 옵션 2) Job 이미지에 docker 그룹을 맞춰서 추가
- 옵션 3) Runner에
user = "root"지정
예: Runner에서 root로 Job을 실행(운영 시 주의):
[runners.docker]
user = "root"
또는, Alpine 기반 이미지에서 그룹 맞추기(소켓의 GID를 동적으로 읽어 그룹 생성):
build:
image: docker:27
script:
- apk add --no-cache shadow
- DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
- groupadd -g ${DOCKER_GID} dockersock || true
- usermod -aG ${DOCKER_GID} root || true
- docker ps
다만 위처럼 “컨테이너 내부 사용자/그룹”을 조작하는 방식은 이미지/배포 환경에 따라 제약이 있으니, 가능하면 DinD나 rootless 빌드로 구조를 바꾸는 편이 더 예측 가능합니다.
4) 해결 2: Docker-in-Docker(DinD)에서 권한/접속 오류
DinD는 “Job 컨테이너”와 “Docker 데몬 컨테이너”를 분리합니다. 보통 services: [docker:dind]를 사용합니다.
4.1 권장 예시(.gitlab-ci.yml)
image: docker:27
services:
- name: docker:27-dind
command: ["--mtu=1460"]
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
stages: [build]
build:
stage: build
script:
- docker info
- docker build -t myapp:${CI_COMMIT_SHA} .
핵심은:
DOCKER_HOST=tcp://docker:2375DOCKER_TLS_CERTDIR=""(TLS 비활성화; 내부 네트워크에서만)
4.2 Runner에 privileged가 필요한 이유
DinD는 내부에서 컨테이너를 또 띄우기 때문에(중첩) 커널 권한이 필요합니다. Runner의 config.toml에서:
[runners.docker]
privileged = true
privileged 없이도 동작하는 경우가 간혹 있지만(특정 커널/설정), iptables, mount, overlay2에서 자주 막힙니다.
4.3 DinD가 느리거나 불안정할 때: BuildKit 사용
Docker 23+ 환경에서는 BuildKit을 켜면 빌드 성능과 캐시가 좋아집니다.
variables:
DOCKER_BUILDKIT: "1"
COMPOSE_DOCKER_CLI_BUILD: "1"
권한 오류 자체를 직접 해결하진 않지만, DinD에서 발생하는 다양한 빌드 관련 문제를 줄여줍니다.
5) 해결 3: privileged를 못 쓰는 환경(보안/정책)이라면
조직 정책상 privileged가 금지되거나, 멀티테넌트 Runner에서 위험하다면 다음 대안을 검토합니다.
5.1 Rootless Docker + BuildKit(환경 제약 있음)
Rootless Docker는 커널 기능/네트워크 제약이 있어 완전한 대체가 어려울 수 있지만, 권한 모델을 크게 개선합니다. GitLab Runner에서 바로 적용하기보다는, 별도 빌드 노드/이미지로 구성하는 편이 일반적입니다.
5.2 Kubernetes 환경이라면 kaniko/buildkit 사용
Kubernetes executor에서 DinD는 보안/성능 이슈가 많습니다. 대신 kaniko(레지스트리로 직접 푸시)나 buildkitd를 권장합니다.
예: kaniko (개념 예시)
build:
image:
name: gcr.io/kaniko-project/executor:latest
entrypoint: [""]
script:
- /kaniko/executor \
--context ${CI_PROJECT_DIR} \
--dockerfile ${CI_PROJECT_DIR}/Dockerfile \
--destination ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
이 방식은 /var/run/docker.sock 자체가 필요 없어서 “소켓 permission denied” 류 문제가 구조적으로 사라집니다.
6) 디버깅 체크리스트(재발 방지용)
6.1 Job 컨테이너에서 현재 Docker 접속 대상 확인
echo "DOCKER_HOST=$DOCKER_HOST"
docker version || true
ls -l /var/run/docker.sock || true
id || true
DOCKER_HOST가unix:///var/run/docker.sock인지tcp://docker:2375인지부터 확정- 소켓이 존재하는데 permission denied면 사용자/그룹 문제
- tcp 접속 실패면 dind 서비스/네트워크/변수 문제
6.2 Runner 쪽 로그 확인
Runner가 Docker executor로 떠 있다면:
docker logs -f gitlab-runner
Kubernetes executor라면 Runner Pod 로그와 Job Pod 이벤트를 함께 봐야 합니다.
6.3 MTU/네트워크 이슈로 DinD가 간헐 실패한다면
EKS 같은 환경에서 네트워크/MTU 문제로 빌드가 불안정해질 수 있습니다. 이때는 DinD 서비스에 --mtu를 주거나 CNI/노드 네트워크를 점검합니다. (쿠버네티스에서 네트워크가 꼬일 때의 진단 흐름은 EKS kubelet NotReady - CNI plugin not initialized 해결 글의 접근법도 참고할 만합니다.)
또한 Pod 내부 시간 드리프트가 TLS/레지스트리 인증 실패로 이어지는 경우도 있어, “권한 오류처럼 보이는 인증 실패”를 의심해야 합니다. 필요하면 EKS Pod 시간 드리프트로 STS·TLS 실패 해결하기도 함께 확인하세요.
7) 보안 관점에서의 선택 가이드
- 호스트 소켓 마운트: 가장 간단/빠름. 하지만 소켓 접근 권한은 호스트 제어권에 가까워 위험. 신뢰할 수 있는 프로젝트/격리된 Runner에서만.
- DinD(privileged): 격리된 데몬을 쓰지만 privileged 자체가 위험. 그래도 소켓 공유보단 통제 포인트가 명확할 때가 많음.
- kaniko/buildkit(K8s): 컨테이너 빌드를 “데몬 없이” 처리해 권한 문제를 구조적으로 줄임. Kubernetes에선 보통 이쪽이 장기적으로 유리.
결론
GitLab CI Runner의 Docker 권한 오류는 대부분 “어떤 Docker 데몬을, 어떤 권한으로 접근하느냐”의 문제로 귀결됩니다. 먼저 소켓 마운트인지 DinD인지 구조를 확정하고, 그에 맞춰 (1) 소켓 권한/그룹을 맞추거나, (2) DinD를 올바른 변수와 privileged로 구성하거나, (3) 정책상 privileged가 어렵다면 kaniko/buildkit로 전환하는 것이 정석입니다.
운영 환경에서는 단기 처방으로 root/privileged를 켜기보다, 장기적으로는 빌드 방식 자체를 보안 친화적으로 재설계하는 것이 재발을 줄이는 가장 확실한 해결책입니다.