- Published on
GitHub Actions Docker CI/CD 권한(permis…) 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론부터 결론까지 한 번에 읽고 바로 고칠 수 있도록, GitHub Actions에서 Docker 기반 CI/CD를 만들 때 반복적으로 발생하는 permission denied 류 문제를 유형별로 정리합니다. 에러 메시지는 비슷해 보여도 실제 원인은 (1) GitHub 토큰 권한, (2) 레지스트리 인증/스코프, (3) 러너의 Docker 데몬 접근, (4) 파일/워크스페이스 권한, (5) 캐시/아티팩트 권한으로 갈립니다. 특히 “permis…”로 잘려 보이는 로그(예: permission denied, denied: requested access, insufficient_scope)는 초반에 정확히 분기하지 않으면 삽질 시간이 길어집니다.
아래는 가장 흔한 실패 시나리오를 에러 문구 → 원인 → 해결로 매핑하고, 바로 붙여 넣어 쓸 수 있는 워크플로 예제까지 포함한 가이드입니다.
1) 먼저 로그에서 "어떤 권한"인지 분기하기
권한 오류는 크게 두 종류입니다.
1.1 GitHub/레지스트리 인증·권한(HTTP 401/403 계열)
- 예시
denied: requested access to the resource is deniedunauthorized: authentication requiredinsufficient_scope: authorization failed403 Forbidden(레지스트리 API)
이 경우는 대체로 토큰 스코프 또는 레지스트리 권한 문제입니다.
1.2 러너 내부 파일·소켓 권한(Unix permission denied)
- 예시
permission denied while trying to connect to the Docker daemon socketGot permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sockEACCES: permission denied, open ...
이 경우는 Docker 데몬 접근 권한 또는 워크스페이스 파일 소유자/권한 문제입니다.
2) GHCR push가 실패: GITHUB_TOKEN 권한 부족
가장 흔한 케이스입니다. GitHub Container Registry(GHCR)에 push할 때 GITHUB_TOKEN이 기본 권한으로는 부족하거나, 리포지토리/조직 정책 때문에 막힙니다.
2.1 전형적인 증상
docker push ghcr.io/<owner>/<image>:<tag>에서denied: requested access to the resource is denied
2.2 해결: workflow에 permissions 명시
GITHUB_TOKEN에 packages: write가 필요합니다. 또한 private repo/조직 정책에 따라 contents: read도 명시하는 편이 안전합니다.
name: ci
on:
push:
branches: [ main ]
permissions:
contents: read
packages: write
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository_owner }}/myapp:${{ github.sha }}
2.3 조직(Org) 레벨에서 막히는 경우
조직 보안 정책에서 GitHub Actions의 패키지 쓰기를 제한하는 경우가 있습니다.
- Org settings → Packages → Actions 접근 정책
- 패키지(컨테이너) 가시성/권한 설정
이 경우 워크플로를 고쳐도 계속 403/denied가 나며, 조직 정책 수정 또는 **PAT(개인 액세스 토큰)**로 우회해야 합니다.
3) Docker Hub / ECR push가 실패: 자격증명 스코프 문제
3.1 Docker Hub
- 증상: 로그인은 됐는데 push에서
denied: requested access - 원인: 리포지토리 이름/네임스페이스 불일치, 권한 없는 org repo로 push
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest
3.2 AWS ECR
- 증상:
no basic auth credentials,denied: User is not authorized - 원인: OIDC/IAM Role 권한 부족 또는 ECR 로그인 누락
permissions:
id-token: write
contents: read
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-ecr-push
aws-region: ap-northeast-2
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:${{ github.sha }}
IAM 정책에는 최소한 ecr:BatchCheckLayerAvailability, ecr:PutImage, ecr:InitiateLayerUpload, ecr:UploadLayerPart, ecr:CompleteLayerUpload 등이 필요합니다.
4) "Cannot connect to the Docker daemon" / docker.sock permission denied
4.1 GitHub-hosted runner(ubuntu-latest)에서는 보통 발생하지 않음
기본적으로 Docker 사용이 가능하지만, 다음 상황에서 터집니다.
- 컨테이너 잡(
container:) 안에서 Docker를 실행하려는 경우 - self-hosted runner에서 docker 그룹/권한이 잘못된 경우
4.2 self-hosted runner 해결 체크리스트
- 러너 실행 유저가 docker 그룹에 속해 있는지
id
getent group docker
sudo usermod -aG docker <runner-user>
- docker 데몬이 실행 중인지
sudo systemctl status docker
- 소켓 권한
ls -l /var/run/docker.sock
> 주의: 그룹 추가 후에는 세션 재로그인(또는 러너 서비스 재시작)이 필요합니다.
4.3 컨테이너 잡에서 Docker가 필요하면: DinD 또는 BuildKit 사용
컨테이너 잡 내부에서 Docker를 쓰면 소켓이 없어서 막히는 경우가 많습니다. 가능하면 docker/build-push-action + Buildx를 쓰고, 정말 필요하면 DinD를 명시합니다.
jobs:
build:
runs-on: ubuntu-latest
services:
docker:
image: docker:24-dind
options: >-
--privileged
ports:
- 2375:2375
env:
DOCKER_HOST: tcp://localhost:2375
steps:
- uses: actions/checkout@v4
- run: docker version
5) 빌드 중 파일 permission denied: COPY/RUN 단계에서 터지는 케이스
5.1 전형적인 증상
COPY failed: file not found or excluded by .dockerignore(권한과 비슷하게 보이지만 다른 문제)chmod: changing permissions of '...': Operation not permittedEACCES: permission denied(Node/Gradle 등 빌드 툴)
5.2 원인 1: 워크스페이스 파일 권한이 깨짐(특히 self-hosted)
self-hosted 러너에서 이전 잡이 root로 파일을 만들어두면 다음 잡에서 일반 유저가 접근 못합니다.
해결: 체크아웃 직후 워크스페이스 소유권 정리(필요 시)
- uses: actions/checkout@v4
- name: Fix workspace permissions (self-hosted only)
if: ${{ runner.os == 'Linux' }}
run: |
sudo chown -R $(id -u):$(id -g) "$GITHUB_WORKSPACE"
sudo chmod -R u+rwX "$GITHUB_WORKSPACE"
5.3 원인 2: Dockerfile에서 non-root 사용 시 디렉터리 권한 미설정
컨테이너 런타임을 non-root로 돌리는 건 좋은데, 빌드/실행 시 쓰기 디렉터리를 안 열어두면 터집니다.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# non-root
RUN addgroup -S app && adduser -S app -G app \
&& chown -R app:app /app
USER app
CMD ["node", "server.js"]
5.4 원인 3: Git 파일 모드/실행권한 문제
쉘 스크립트를 실행하는데 Permission denied가 나면 실행 비트가 빠졌을 수 있습니다.
git update-index --chmod=+x scripts/deploy.sh
6) actions/cache, build cache에서 권한/키 문제로 간접 실패
캐시가 “권한 오류”를 직접 내기보다는, 캐시 미스 → 빌드 재실행 → 다른 단계에서 권한 문제가 폭발하는 식으로 나타나기도 합니다. Docker 레이어 캐시를 쓴다면 캐시 구성도 함께 점검하세요.
Buildx의 GHA 캐시 예시:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build (with cache)
uses: docker/build-push-action@v6
with:
context: .
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
7) PR에서만 실패: fork PR의 토큰 권한 제한
fork에서 올라온 PR은 보안상 secrets가 주입되지 않거나, GITHUB_TOKEN 권한이 제한됩니다. 그래서 main 브랜치에서는 되는데 PR에서만 denied가 납니다.
7.1 해결 전략
- PR에서는 빌드만 하고 push는 하지 않는다.
- push는
push이벤트(내부 브랜치)에서만 수행.
on:
pull_request:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name == 'push' }}
tags: ghcr.io/${{ github.repository_owner }}/myapp:${{ github.sha }}
8) 최소 권한(Least Privilege) 기준으로 권한 설계하기
권한 문제를 막겠다고 무작정 토큰 권한을 넓히면, 나중에 보안 감사/침해 사고에서 문제가 됩니다. 권장 패턴은 다음입니다.
- GHCR push가 필요한 워크플로에만:
permissions: packages: write, contents: read
- AWS OIDC를 쓰는 워크플로에만:
permissions: id-token: write, contents: read
- PR 워크플로에는 secrets/push 동작을 넣지 않기
9) 실전 템플릿: GHCR에 안전하게 build & push
아래 템플릿은 권한/이벤트 분리를 포함해 permission 이슈를 가장 덜 일으키는 형태입니다.
name: docker-cicd
on:
pull_request:
push:
branches: [ main ]
permissions:
contents: read
packages: write
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Login to GHCR (push only)
if: ${{ github.event_name == 'push' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build (PR) / Build+Push (main)
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name == 'push' }}
tags: |
ghcr.io/${{ github.repository_owner }}/myapp:${{ github.sha }}
ghcr.io/${{ github.repository_owner }}/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
10) 마무리: "permission"은 대개 설정의 경계에서 터진다
GitHub Actions + Docker CI/CD에서 permission 오류는 코드의 문제가 아니라 **경계면(토큰·레지스트리·러너·파일시스템)**에서 터지는 경우가 대부분입니다. 따라서 다음 순서로 보면 빠르게 해결됩니다.
- 로그가 인증(401/403)인지, 로컬 권한(EACCES/docker.sock)인지 분기
- 레지스트리 push라면
permissions: packages: write부터 확인 - self-hosted라면 docker 그룹/소켓/워크스페이스 소유권 확인
- PR(fork) 이벤트에서는 push/secret을 분리
- 캐시를 쓰면 캐시 설정도 함께 점검
캐시가 꼬여서 빌드 단계가 불필요하게 반복되고, 그 과정에서 권한 문제가 더 자주 드러나는 경우도 많습니다. 캐시 관련 이슈는 위에서 링크한 글(GitHub Actions 캐시 안 먹힘 원인 7가지)을 함께 확인하면 재발 방지에 도움이 됩니다.