- Published on
Jenkins Pipeline Docker 권한 denied 해결 7단계
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Jenkins Pipeline에서 docker build, docker login, docker push 같은 단계가 갑자기 permission denied로 터지면, 대부분은 “Docker 데몬 소켓 접근 권한” 또는 “에이전트 실행 방식(호스트 vs 컨테이너 vs Kubernetes)”의 불일치에서 시작합니다. 문제는 에러 메시지가 비슷하게 보여도 원인이 여러 갈래라는 점입니다.
이 글은 현장에서 가장 자주 맞닥뜨리는 케이스를 기준으로, 재현 로그를 보고 원인을 좁히는 순서대로 7단계 체크리스트를 제공합니다. 한 번에 다 바꾸기보다, 단계별로 확인하고 최소 변경으로 해결하는 흐름을 권장합니다.
참고로 인프라 장애를 진단할 때처럼(예: EKS CrashLoopBackOff? OOMKilled 진단 7단계), “증상 분류 → 실행 주체 확인 → 권한 경계 확인 → 재발 방지” 순서로 접근하면 빠릅니다.
1단계: 정확한 에러 지점을 로그로 고정하기
먼저 어떤 명령이 어떤 사용자 컨텍스트에서 실패하는지 고정해야 합니다. Jenkins는 스텝 내부에서 쉘이 어떤 사용자로 실행되는지, 에이전트가 어디서 도는지에 따라 해결책이 완전히 달라집니다.
Pipeline에 아래처럼 “진단용 출력”을 잠깐 추가하세요.
pipeline {
agent any
stages {
stage('Diag') {
steps {
sh '''
set -eux
whoami
id
uname -a
ls -l /var/run/docker.sock || true
docker version || true
docker info || true
'''
}
}
}
}
여기서 핵심은 다음 두 줄입니다.
ls -l /var/run/docker.sock결과의 소유자/그룹(보통root:docker)과 권한 비트whoami,id결과의 사용자/그룹이docker그룹에 포함되는지
에러 메시지가 Got permission denied while trying to connect to the Docker daemon socket 형태라면, 거의 확실히 소켓 접근권한 문제입니다.
2단계: Jenkins 에이전트가 “호스트에서 실행”인지 “컨테이너에서 실행”인지 구분하기
가장 흔한 함정은 Jenkins 컨트롤러/에이전트가 컨테이너로 실행되는데, 파이프라인은 호스트의 Docker를 직접 제어하려고 하는 경우입니다.
다음 중 어디에 해당하는지 먼저 분류하세요.
- A: Jenkins 에이전트가 VM/베어메탈 호스트에서 직접 실행(전통적인
agent any+ systemd 서비스) - B: Jenkins가 Docker 컨테이너로 실행(예:
jenkins/jenkins컨테이너) - C: Jenkins가 Kubernetes 에이전트(Pod)로 실행
B, C라면 단순히 호스트에 docker 그룹을 추가하는 것만으로는 해결이 안 될 수 있습니다. 컨테이너 내부 사용자와 호스트의 소켓 권한이 맞아야 하기 때문입니다.
3단계: (호스트 실행형) Jenkins 사용자에 Docker 그룹 권한 부여
A 케이스(호스트에서 Jenkins가 실행)라면 정석은 Jenkins 실행 사용자(보통 jenkins)를 docker 그룹에 추가하는 것입니다.
sudo groupadd docker || true
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins
주의할 점:
- 그룹 추가 후에는 “세션 재시작”이 필요합니다. Jenkins 서비스 재시작이 가장 확실합니다.
- 이미 에이전트가 별도 사용자로 떠 있다면, 그 사용자도 같은 방식으로 처리해야 합니다.
이 단계에서 여전히 실패하면, 다음을 확인하세요.
getent group docker
ls -l /var/run/docker.sock
/var/run/docker.sock의 그룹이 docker가 아니라면(드물지만), Docker 데몬 설정이나 배포 방식이 달라졌을 수 있습니다.
4단계: (컨테이너 실행형) Docker 소켓 마운트와 GID 정합성 맞추기
B 케이스에서 가장 흔한 패턴은 “Docker 소켓은 마운트했는데, 컨테이너 내부 사용자가 소켓 그룹 GID와 불일치”입니다.
호스트에서 소켓의 GID를 확인합니다.
stat -c '%g %n' /var/run/docker.sock
예를 들어 GID가 998이라면, Jenkins 컨테이너 내부에서도 동일한 GID를 docker 그룹으로 만들어 사용자에 붙여야 합니다.
docker-compose.yml 예시는 다음과 같습니다.
services:
jenkins:
image: jenkins/jenkins:lts
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- jenkins_home:/var/jenkins_home
environment:
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
jenkins_home:
컨테이너 내부에서 그룹을 맞춥니다.
# 컨테이너 내부
sock_gid=$(stat -c '%g' /var/run/docker.sock)
getent group docker || groupadd -g "$sock_gid" docker
usermod -aG docker jenkins
운영에서는 이미지 빌드 단계에서 고정하거나, 엔트리포인트에서 GID를 동기화하는 방식이 재현성이 좋습니다.
보안적으로는 user: root가 부담일 수 있습니다. 하지만 “소켓 마운트 자체가 사실상 루트 권한”에 준하므로, 이 구조를 쓰는 순간 이미 신뢰 경계를 크게 열어둔 상태라는 점을 인지해야 합니다.
5단계: (Kubernetes 에이전트) Docker-in-Docker 대신 Kaniko 또는 BuildKit로 전환
C 케이스(Kubernetes Pod 에이전트)에서 permission denied가 나는 이유는 더 복잡합니다.
- 노드에 Docker가 없거나(containerd 사용)
- Pod에서
/var/run/docker.sock를 호스트Path로 마운트하지 않음 - 마운트해도 권한/보안정책(PSA, SELinux, AppArmor)으로 차단
K8s에서는 가능하면 “노드 Docker 소켓 공유”를 피하고, 이미지 빌드는 Kaniko나 BuildKit을 권장합니다.
Kaniko로 빌드/푸시하는 Jenkinsfile 예시
아래 예시는 Docker 데몬 없이 레지스트리로 빌드/푸시합니다.
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
command:
- cat
tty: true
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
volumes:
- name: docker-config
secret:
secretName: regcred
'''
}
}
stages {
stage('Build & Push') {
steps {
container('kaniko') {
sh '''
/kaniko/executor \
--context $WORKSPACE \
--dockerfile Dockerfile \
--destination registry.example.com/myapp:${BUILD_NUMBER}
'''
}
}
}
}
}
이 방식은 permission denied를 근본적으로 회피합니다(소켓 접근 자체가 없기 때문). 또한 노드 런타임이 Docker인지 containerd인지와 무관합니다.
6단계: Pipeline 코드에서 “Docker CLI 호출 방식”을 표준화하기
같은 팀/같은 Jenkins라도 파이프라인마다 docker 호출 방식이 제각각이면, 어떤 에이전트에서는 되고 어떤 에이전트에서는 실패합니다.
다음 중 하나로 표준화하세요.
- 호스트 Docker를 쓰는 경우:
DOCKER_HOST=unix:///var/run/docker.sock를 명시 - K8s: Kaniko/BuildKit으로 전환
- Jenkins Docker Pipeline 플러그인을 쓸 경우: 에이전트 환경과 플러그인 기대치가 맞는지 문서화
예를 들어, 최소한 아래처럼 “어디에 붙는 Docker인지”를 명시하면 디버깅이 쉬워집니다.
environment {
DOCKER_HOST = 'unix:///var/run/docker.sock'
}
stages {
stage('Docker Build') {
steps {
sh '''
set -eux
echo "DOCKER_HOST=$DOCKER_HOST"
docker version
docker build -t myapp:${BUILD_NUMBER} .
'''
}
}
}
이때 docker version이 Client만 출력되고 Server 정보를 못 가져오면, 대개 소켓 접근 또는 원격 데몬 접근 실패입니다.
7단계: 재발 방지 체크리스트(보안/운영 관점)
권한 denied는 “한 번 뚫으면 끝”이 아니라, 에이전트 교체/이미지 교체/노드 교체 때 재발합니다. 아래 항목을 운영 표준으로 박아두면 재발률이 크게 줄어듭니다.
7-1. 에이전트 유형별 표준 아키텍처를 문서화
- VM 에이전트:
jenkins사용자는docker그룹 포함 - 컨테이너 에이전트: 소켓 GID 동기화 방식 명시
- K8s 에이전트: Kaniko/BuildKit 사용(소켓 공유 금지)
7-2. “권한 해결”을 위해 무조건 chmod 666 하지 않기
가끔 급한 마음에 아래처럼 소켓 권한을 열어버리는 사례가 있습니다.
sudo chmod 666 /var/run/docker.sock
이건 단기 처방일 뿐 아니라, 사실상 호스트 루트 권한에 준하는 권한을 광범위하게 노출합니다. 감사/보안 점검에서도 바로 걸립니다.
7-3. 자격증명은 Jenkins Credentials로만 주입
docker login을 파이프라인에 하드코딩하면 로그/아티팩트로 새기 쉽습니다.
withCredentials([usernamePassword(credentialsId: 'reg-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')]) {
sh '''
echo "$REG_PASS" | docker login registry.example.com -u "$REG_USER" --password-stdin
'''
}
7-4. 장애가 “권한”이 아니라 “네트워크/레지스트리”인지 분리
현장에서 denied라는 단어가 레지스트리 인증 실패(requested access to the resource is denied)와 섞여 혼동되는 경우가 많습니다.
- Docker 데몬 소켓 권한 문제:
Got permission denied while trying to connect to the Docker daemon socket - 레지스트리 권한 문제:
requested access to the resource is denied
레지스트리/네트워크 이슈는 다른 진단 축입니다. 예를 들어 클러스터 네트워크가 불안정하면 TLS 타임아웃으로도 비슷하게 빌드/푸시가 실패할 수 있는데, 이런 케이스는 EKS TLS handshake timeout 원인·해결 9가지 같은 네트워크 관점 체크리스트가 더 맞습니다.
자주 보는 실패 시나리오별 빠른 처방 요약
- VM에서만 실패:
jenkins사용자가docker그룹에 없음(3단계) - Jenkins가 컨테이너: 소켓 마운트는 했지만 GID 불일치(4단계)
- Kubernetes 에이전트: Docker 소켓 접근 자체가 구조적으로 안 맞음(5단계)
denied가 레지스트리 관련 문구: Docker 권한이 아니라 인증/권한 정책 문제(7-4)
마무리
Jenkins Pipeline의 Docker permission denied는 “Docker를 쓸 수 있냐 없냐” 문제가 아니라, “누가(사용자) 어디서(에이전트) 어떤 데몬에(소켓/원격) 붙는가”의 3요소가 어긋날 때 발생합니다.
가장 빠른 해결은 1단계에서 사용자/소켓/에이전트 타입을 고정하고, A(호스트)면 그룹 권한, B(컨테이너)면 GID 정합성, C(K8s)면 Kaniko/BuildKit로 전환하는 것입니다. 이렇게 갈라서 처리하면 불필요한 권한 완화 없이도 안정적으로 재발을 막을 수 있습니다.