Docker 시리즈 ⑧ Security — 컨테이너 보안의 기본
컨테이너 보안의 기초 — non-root 실행, 시크릿 관리, .dockerignore, 이미지 스캔, 리소스 제한까지 초보자도 챙겨야 할 핵심을 정리했다.
Docker 시리즈 ⑧ Security — 컨테이너 보안의 기본
시리즈 순서: Host → Image → Container → Network → Volume → Dockerfile → Compose → Security → Swarm
들어가며
"내 컨테이너에 누가 접근하겠어?"라는 생각이 가장 위험하다. 컨테이너는 격리되어 있지만 완전 안전한 박스는 아니다. 잘못 설정하면 호스트까지 영향이 갈 수 있다.
이 글에선 초보자도 반드시 챙겨야 할 6가지 핵심 보안 수칙을 정리한다.
1. non-root 사용자로 실행하기
가장 중요한 한 가지. Docker 컨테이너는 기본적으로 root 권한으로 실행된다. 컨테이너가 탈출되면 호스트 root 권한을 가져갈 수 있다는 뜻이다.
❌ 위험한 Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN yarn install
CMD ["node", "server.js"]
# ↑ root로 실행됨 ❌✅ 안전한 Dockerfile
FROM node:20-alpine
# 일반 사용자 생성
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 appuser
WORKDIR /app
COPY --chown=appuser:nodejs . .
RUN yarn install
USER appuser # 사용자 변경
CMD ["node", "server.js"]USER 지시문 이후의 모든 명령은 그 사용자 권한으로 실행된다.
Compose에서
services:
app:
image: my-app
user: "1001:1001" # UID:GID 직접 지정도 가능2. 시크릿(비밀번호, API 키)을 이미지에 박지 말기
❌ 절대 하지 말기
ENV API_KEY="sk-abc123..." # ❌ 이미지에 영구 박힘# docker-compose.yml
services:
app:
environment:
DB_PASSWORD: "mypassword" # ❌ 깃에 그대로 올라감COPY .env . # ❌ .env가 이미지에 포함됨이미지를 받은 사람이 docker history나 docker inspect로 다 볼 수 있다.
✅ 올바른 방법
방법 A: 런타임 환경변수
docker run -e API_KEY=$API_KEY my-app
docker run --env-file ./secrets.env my-app방법 B: Docker secrets (Swarm 모드)
services:
app:
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txt/run/secrets/db_password 파일로 컨테이너 안에서 접근.
방법 C: BuildKit secrets (빌드 시점)
# syntax=docker/dockerfile:1.4
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) yarn installdocker build --secret id=npm_token,src=$HOME/.npmrc .이미지에 토큰이 절대 안 남는다.
방법 D: 외부 시크릿 매니저
운영 환경에선 AWS Secrets Manager, HashiCorp Vault 등을 쓰는 게 정석.
3. .dockerignore로 민감 파일 제외
.dockerignore가 없으면 COPY . . 시 .env, .git, node_modules까지 다 이미지에 들어간다.
# .dockerignore
.git
.env
.env.*
*.log
*.key
*.pem
node_modules
.DS_Store
.vscode
.idea
secrets/특히 .env와 .git/은 반드시 제외. .git엔 커밋 히스토리 전체가 들어있어서 과거에 잠깐 커밋된 시크릿까지 노출될 수 있다.
4. 이미지 신뢰성 — 어떤 이미지를 쓸 것인가
공식 / 검증된 이미지 사용
- Docker Hub의 Official Image 마크 (예:
nginx,postgres) - Verified Publisher (Microsoft, AWS 등)
- 사내 레지스트리에서 자체 빌드한 이미지
태그보다 다이제스트로 고정
운영 환경에선 태그 대신 다이제스트로 고정.
FROM nginx@sha256:a6bd71f48f68c5b8...태그는 게시자가 다른 이미지로 바꿀 수 있지만, 다이제스트는 영원히 고정이다 (Image 글 참고).
이미지 스캔하기
이미지 안에 알려진 취약점(CVE)이 있는지 검사하는 도구를 쓰자.
# Trivy (가장 많이 씀, 무료)
trivy image my-app:latest
# Docker Scout (Docker Desktop 내장)
docker scout cves my-app:latest
# Snyk
snyk container test my-app:latestCI에 통합하면 PR에 자동으로 결과가 뜨게 할 수 있다.
5. 리소스 제한 걸기
기본값으로 컨테이너는 호스트의 모든 자원을 쓸 수 있다. 잘못된 코드 한 줄로 메모리 누수가 일어나면 호스트 전체가 멈춘다.
docker run에서
docker run -d \
--memory="512m" \
--memory-swap="512m" \
--cpus="1.0" \
--pids-limit=100 \
my-app| 옵션 | 의미 |
|---|---|
--memory | 최대 메모리 |
--memory-swap | 메모리 + 스왑 합계 (스왑 막으려면 메모리와 같게) |
--cpus | CPU 코어 수 (소수점 가능) |
--pids-limit | 프로세스 수 제한 (fork bomb 방어) |
Compose에서
services:
app:
image: my-app
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 256M6. 쓸데없는 권한 / 기능 제거
--privileged 사용 금지
docker run --privileged my-app # ❌ 호스트 거의 다 쓸 수 있음이건 컨테이너에 거의 root 권한을 주는 것과 같다. 정말 특수한 경우(Docker-in-Docker 등) 외엔 쓰지 말자.
Linux Capability 최소화
컨테이너가 가진 권한 일부를 빼는 방법.
docker run \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
my-app대부분의 앱은 모든 capability가 필요하지 않다. ALL을 빼고 진짜 필요한 것만 추가하는 게 안전하다.
읽기 전용 루트 파일시스템
docker run --read-only my-app컨테이너의 루트 파일시스템을 읽기 전용으로 만든다. 쓰기 필요한 곳은 별도 볼륨/tmpfs로.
docker run --read-only \
--tmpfs /tmp \
-v app-data:/data \
my-app빠르게 점검하는 보안 체크리스트
새 이미지/컨테이너를 띄우기 전에 한번씩 확인:
- [ ] Dockerfile에
USER지시문이 있는가? (root 실행 X) - [ ]
.dockerignore에.env,.git등 민감 파일이 들어있는가? - [ ] 이미지에 비밀번호/토큰이 박혀있지 않은가? (
docker history로 확인) - [ ] 베이스 이미지가 신뢰할 만한가? (공식 / Verified Publisher)
- [ ] 이미지 스캔을 돌려봤는가? (Trivy 등)
- [ ] 리소스 제한이 걸려있는가? (
--memory,--cpus) - [ ]
--privileged를 쓰지 않는가? - [ ] 운영용은 다이제스트(
@sha256:...)로 고정되어 있는가?
헷갈리는 포인트
❓ "Alpine 이미지가 보안에 좋다고 하던데요?"
이미지가 작아서 공격 표면이 좁은 건 맞다. 패키지가 적으니 취약점도 적다. 다만 절대적으로 안전한 건 아니고, 호환성 문제(musl libc)는 별개로 신경 써야 한다.
❓ "USER를 바꿨더니 권한 에러가 나요"
파일/디렉토리의 소유자가 root인데 일반 사용자로 실행하면 쓰기 권한이 없어 에러난다.
# 미리 소유권 변경
COPY --chown=appuser:nodejs . .
# 또는 RUN으로
RUN chown -R appuser:nodejs /app❓ "비밀번호를 환경변수로 넘기는 게 안전한가요?"
완벽하진 않다. docker inspect로 환경변수가 보일 수 있다. 진짜 민감한 건 secrets 메커니즘이나 외부 매니저를 써야 한다.
❓ "취약점 스캔 결과가 너무 많아요"
모든 CVE를 다 막을 순 없다. 우선순위:
- HIGH/CRITICAL 등급 먼저
- 실제로 사용하는 패키지 위주
- 베이스 이미지 업그레이드로 한꺼번에 해결되는 경우 많음
정리
보안의 핵심은 단순하다:
- non-root 실행 —
USER지시문 필수 - 시크릿 분리 — 이미지에 박지 말기
- `.dockerignore` —
.env,.git제외 - 신뢰할 수 있는 이미지 — 공식 + 다이제스트 고정
- 리소스 제한 — memory, cpus
- 쓸데없는 권한 제거 —
--privileged금지
이 6가지만 지켜도 사고의 90%는 막을 수 있다.
다음 글은 시리즈의 마지막 — 단일 호스트(Compose)를 넘어 여러 호스트로 컨테이너를 배포하는 Docker Swarm이다. 그리고 Swarm을 다 배운 뒤에 왜 Kubernetes로 넘어가는지 자연스럽게 이어진다.
이전 글: Docker 시리즈 ⑦ Compose 다음 글: Docker 시리즈 ⑨ Swarm — 여러 호스트로 확장하기
Related Posts
같이 읽으면 좋은 글
Docker 시리즈 ⑨ Swarm — 여러 호스트로 확장하기, 그리고 Kubernetes로
Docker Swarm의 핵심 개념(Service, Task, Stack)을 정리하고, 왜 결국 Kubernetes로 넘어가게 되는지 실제 프로젝트 경험을 바탕으로 정리했다.
Docker 시리즈 ⑦ Compose — 여러 컨테이너 한꺼번에 다루기
여러 컨테이너를 단일 명령으로 관리하는 docker compose의 구조, 핵심 옵션, 그리고 실무 활용 패턴을 초보자 눈높이로 정리했다.
Docker 시리즈 ⑥ Dockerfile — 이미지를 만드는 레시피
Dockerfile 명령어 전체 정리 — CMD vs ENTRYPOINT, COPY vs ADD, ARG vs ENV의 차이와 레이어 캐시 최적화 패턴까지 초보자 눈높이로 다룬다.