Published on

Jenkins Docker 에이전트 Permission denied 해결

Authors

서버에서 Jenkins 파이프라인을 Docker 에이전트로 돌리다 보면, 로그에 permission denied 한 줄이 뜨는 순간부터 디버깅이 지옥이 됩니다. 문제는 대부분 “Jenkins가 컨테이너 안에서 무엇을 하려 했는지”와 “호스트에서 그 행위를 허용했는지”의 불일치에서 발생합니다.

이 글은 Jenkins Docker 에이전트 환경에서 흔히 만나는 권한 문제를 원인 패턴별로 빠르게 식별하고, 재발 방지까지 포함해 정리합니다. 더 많은 케이스를 한 번에 보고 싶다면 함께 참고하세요: Jenkins Docker 에이전트 Permission denied 7가지 해결

어떤 Permission denied 인지부터 분류하기

permission denied는 결과일 뿐이고, 실제로는 아래 중 하나인 경우가 대부분입니다.

  1. Docker 데몬 접근 불가: docker.sock 권한 문제
  2. 워크스페이스 마운트 디렉터리 쓰기 불가: UID/GID 불일치
  3. 컨테이너 내부에서 apk add, apt-get 등 설치 시도 실패: root 권한 부족
  4. Git 체크아웃/아카이브/캐시 경로 접근 실패: 파일 소유권 꼬임
  5. SELinux/AppArmor 정책으로 차단
  6. rootless Docker / Podman / DinD 조합에서 소켓/네임스페이스 충돌

먼저 Jenkins 콘솔 로그에서 “어디에 접근하다가 막혔는지”를 찾습니다.

  • Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
  • mkdir: cannot create directory ... Permission denied
  • fatal: could not create work tree dir ... Permission denied
  • E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)

이제 원인별로 해결을 들어가겠습니다.

1) Docker 소켓(docker.sock) 접근 권한 문제

증상

Jenkins 에이전트 컨테이너에서 docker ps 같은 명령을 실행할 때 아래가 뜹니다.

  • permission denied while trying to connect to the Docker daemon socket

원인

컨테이너가 호스트의 /var/run/docker.sock를 마운트했지만, 컨테이너 내부 사용자(예: jenkins UID 1000)가 소켓 파일에 접근할 권한이 없습니다.

해결 옵션 A: 컨테이너 사용자를 Docker 그룹에 맞추기

호스트에서 Docker 그룹 GID를 확인합니다.

getent group docker
# 예: docker:x:999:ubuntu

Jenkins Docker 에이전트 컨테이너 실행 시 해당 GID를 그룹으로 추가합니다.

docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  --group-add 999 \
  your-jenkins-agent-image:latest \
  docker ps

Jenkins Pipeline에서 Docker 에이전트를 직접 띄우는 경우(Declarative)라면 args로 주입합니다.

pipeline {
  agent {
    docker {
      image 'your-jenkins-agent-image:latest'
      args '-v /var/run/docker.sock:/var/run/docker.sock --group-add 999'
    }
  }
  stages {
    stage('docker') {
      steps {
        sh 'docker ps'
      }
    }
  }
}

해결 옵션 B: 임시로 root로 실행(권장도 낮음)

가장 빨리 뚫리지만 보안상 비용이 큽니다.

args '-u 0:0 -v /var/run/docker.sock:/var/run/docker.sock'

운영 환경에서는 최소 권한 원칙 때문에 옵션 A를 우선 권장합니다.

해결 옵션 C: Docker 소켓 대신 TCP 사용(보안 설계 필요)

소켓 공유 자체를 피하려면 Docker 데몬을 TLS 기반 TCP로 노출하고 인증서를 사용합니다. 다만 이건 인프라 보안 설계가 필요해 단순 트러블슈팅 범위를 넘어갑니다.

2) 워크스페이스 마운트에서 UID/GID 불일치

증상

  • Permission denied가 워크스페이스 경로(예: /var/jenkins_home/workspace/...)에서 발생
  • Git checkout, 캐시 생성, 빌드 산출물 쓰기에서 실패

원인

호스트의 Jenkins 홈 또는 워크스페이스 디렉터리를 컨테이너에 마운트했는데, 호스트 파일 소유자 UID/GID컨테이너 내부 사용자 UID/GID가 다릅니다.

진단

호스트에서 실제 소유권을 확인합니다.

ls -ld /var/jenkins_home
ls -ld /var/jenkins_home/workspace

컨테이너 내부에서 현재 사용자 UID/GID를 확인합니다.

id
whoami

해결 옵션 A: 컨테이너를 호스트 Jenkins UID/GID로 실행

호스트 Jenkins가 UID 1000, GID 1000이라면:

args '-u 1000:1000'

워크스페이스를 마운트하는 경우라면:

args '-u 1000:1000 -v /var/jenkins_home/workspace:/workspace'

해결 옵션 B: 마운트 대상 디렉터리 소유권을 정리

호스트에서 권한을 정리합니다.

chown -R 1000:1000 /var/jenkins_home
chmod -R u+rwX /var/jenkins_home

주의: 이미 다양한 UID로 생성된 파일이 섞여 있으면, 이 작업이 의도치 않은 접근권한 변경을 일으킬 수 있습니다. 특히 공유 스토리지(NFS) 사용 시 더 조심해야 합니다.

해결 옵션 C: 파이프라인에서 임시로 chown(최후 수단)

컨테이너가 root로 시작할 수 있을 때만 가능하며, 빌드마다 소유권을 바꾸는 건 비용이 큽니다.

sh 'chown -R 1000:1000 .'

3) 패키지 설치/툴 설치 단계에서 Permission denied

증상

  • Alpine에서 apk add 실패
  • Debian/Ubuntu에서 apt-get 실패
  • /usr/local/bin에 바이너리 설치 실패

예시:

  • E: Could not open lock file ... Permission denied

원인

에이전트 이미지가 비루트 사용자로 실행되는데, 설치 단계는 루트 권한이 필요합니다.

해결 1: 이미지 빌드 시점에 필요한 도구를 미리 포함

런타임에 설치하지 말고 Dockerfile에 넣는 방식이 가장 안정적입니다.

FROM jenkins/inbound-agent:latest
USER root
RUN apt-get update \
  && apt-get install -y --no-install-recommends git curl ca-certificates \
  && rm -rf /var/lib/apt/lists/*
USER jenkins

해결 2: 특정 스테이지만 root로 실행

필요한 단계에서만 root로 실행하고, 이후 다시 일반 사용자로 돌아가는 방식입니다.

stage('prepare') {
  agent {
    docker {
      image 'your-agent:latest'
      args '-u 0:0'
    }
  }
  steps {
    sh 'apt-get update && apt-get install -y make'
  }
}

다만 Jenkins 에이전트 컨테이너를 스테이지마다 새로 띄우는지, 동일 컨테이너를 재사용하는지에 따라 결과가 달라질 수 있습니다.

4) Docker-in-Docker(DinD)에서 권한 문제

증상

  • DinD 컨테이너에서 mount: permission denied
  • 빌드 중 overlayfs 관련 에러

원인

DinD는 일반적으로 --privileged 또는 적절한 커널 기능이 필요합니다. 권한이 부족하면 스토리지 드라이버가 동작하지 않습니다.

해결

DinD를 써야 한다면 보통 아래 옵션이 필요합니다.

docker run --privileged --rm docker:dind

하지만 가능하면 호스트 소켓 마운트 방식 또는 **빌드 전용 도구(예: Kaniko, BuildKit rootless)**로 전환하는 것이 운영 보안에 유리합니다.

5) SELinux/AppArmor로 인한 차단

증상

  • 권한을 맞춘 것 같은데도 특정 경로 마운트/접근이 계속 실패
  • CentOS/RHEL 계열에서 특히 자주 발생

원인

SELinux가 컨테이너의 볼륨 마운트 접근을 차단할 수 있습니다.

해결(SELinux)

볼륨 마운트에 라벨 옵션을 추가합니다.

  • :z 공유 라벨
  • :Z 단독 라벨

예:

docker run --rm \
  -v /var/jenkins_home/workspace:/workspace:Z \
  your-agent:latest \
  ls -la /workspace

AppArmor는 프로파일 정책에 따라 달라서, 해당 호스트의 Docker/컨테이너 런타임 설정을 함께 확인해야 합니다.

6) Jenkins에서 자주 보이는 “파일 소유권 꼬임” 패턴

패턴 A: 한 번 root로 실행한 뒤 계속 Permission denied

컨테이너를 root로 실행해 빌드 산출물을 만들면, 워크스페이스에 root 소유 파일이 생깁니다. 이후 비루트로 실행하면 그 파일을 수정/삭제하지 못해 실패합니다.

해결은 “처음부터 끝까지 동일 UID로 실행”하거나, root로 실행해야 한다면 마지막에 소유권을 되돌려야 합니다.

sh 'chown -R 1000:1000 .'

패턴 B: 캐시 디렉터리(.gradle, .m2, node_modules)만 죽는 경우

캐시 경로만 별도 볼륨으로 잡혀 있고, 그 볼륨의 소유권이 다른 UID로 되어 있는 경우가 많습니다.

캐시 볼륨을 쓴다면 아래처럼 UID/GID를 일관되게 맞추세요.

args '-u 1000:1000 -v jenkins-gradle-cache:/home/jenkins/.gradle'

7) 재발 방지를 위한 권장 구성(현업 기준)

권장 1: 에이전트 이미지 표준화

  • 필요한 툴을 Dockerfile에 포함
  • 기본 실행 유저 UID/GID를 조직 표준으로 통일
  • 워크스페이스/캐시 경로 권한을 이미지 단계에서 준비

권장 2: 소켓 마운트 방식이면 --group-add를 기본값으로

호스트 Docker 그룹 GID는 환경마다 다를 수 있으니, IaC에서 GID를 조회해 Jenkins 설정에 주입하는 방식이 안전합니다.

권장 3: 장애 대응 관점에서 “권한 문제”를 빠르게 좁히기

권한 문제는 로그만으로 헷갈릴 때가 많습니다. 컨테이너가 떠 있는 노드에서 바로 확인 가능한 체크리스트를 만들어두면 좋습니다. 운영 장애를 빠르게 줄이는 방식은 아래 글의 접근(짧은 시간에 원인 좁히기)과 결이 비슷합니다: K8s CrashLoopBackOff 즉시 원인 찾는 법

빠른 체크리스트(복붙용)

아래 커맨드로 “소켓 권한”과 “워크스페이스 UID”를 즉시 확인할 수 있습니다.

# 1) 호스트 docker.sock 권한
ls -l /var/run/docker.sock
getent group docker

# 2) 호스트 Jenkins 홈/워크스페이스 소유권
ls -ld /var/jenkins_home /var/jenkins_home/workspace

# 3) 컨테이너 내부 사용자 확인
id

# 4) 컨테이너에서 소켓 접근 테스트
docker ps

결론

Jenkins Docker 에이전트의 permission denied는 대부분 아래 2개로 수렴합니다.

  • Docker 데몬 접근: /var/run/docker.sock의 그룹 권한 문제(해결: --group-add)
  • 워크스페이스 쓰기: 호스트와 컨테이너의 UID/GID 불일치(해결: -u UID:GID 또는 호스트 소유권 정리)

여기에 패키지 설치(루트 필요), DinD(권한 및 커널 기능 필요), SELinux 라벨링까지 더해지면 케이스가 복잡해집니다. 하지만 로그에서 “막힌 대상이 소켓인지, 파일시스템인지, 패키지 매니저인지”만 먼저 분류하면 해결은 빠르게 수렴합니다.