- Published on
Docker & Podman 명령어 완벽 가이드: 컨테이너 운영의 모든 것을 한 장에 정리하다
- Authors
- Name
- 1. 서론: 컨테이너 생태계의 양대 산맥
- 2. 아키텍처 비교: Docker vs Podman
- 3. 설치 및 초기 설정
- 4. 이미지 관리 명령어
- 5. 이미지 빌드
- 6. 컨테이너 라이프사이클 명령어
- 7. 컨테이너 모니터링 및 디버깅
- 8. 네트워크 관리
- 9. 볼륨 및 스토리지 관리
- 10. Docker Compose vs Podman Compose
- 11. Podman 고유 기능
- 12. 보안 관련 명령어 및 설정
- 13. 시스템 관리 및 정리
- 14. 실전 치트시트: 자주 쓰는 명령어 30선
- 15. 트러블슈팅 가이드
- 16. 참고 자료 및 레퍼런스
1. 서론: 컨테이너 생태계의 양대 산맥
1.1 Docker의 역사와 현재 위치
2013년 Solomon Hykes가 PyCon에서 "The future of Linux Containers"라는 5분짜리 라이트닝 토크로 세상에 공개한 Docker는 컨테이너 기술의 대중화를 이끌며 소프트웨어 배포 방식 자체를 혁신했다. Docker 이전에도 LXC(Linux Containers), FreeBSD Jail, Solaris Zones 같은 컨테이너 기술은 존재했지만, Docker는 이미지 레이어 시스템, Dockerfile 기반 선언적 빌드, Docker Hub라는 중앙 레지스트리를 결합하여 "Build once, run anywhere"라는 비전을 현실로 만들었다.
Docker는 이후 OCI(Open Container Initiative) 표준의 기반이 되었고, Kubernetes 생태계의 핵심 런타임으로 자리 잡았다. 그러나 2020년 Kubernetes가 dockershim을 deprecated하고, Docker Desktop의 상용 라이선스 정책 변경(250인 이상 기업 유료화)이 발표되면서 업계는 대안을 찾기 시작했다.
현재 Docker는 여전히 개발 환경의 사실상 표준(de facto standard)이며, Docker Hub는 세계 최대의 컨테이너 이미지 레지스트리로 군림하고 있다. 하지만 프로덕션 환경에서는 containerd, CRI-O, 그리고 Podman이 빠르게 영역을 넓혀가고 있다.
1.2 Podman의 등장 배경
Podman(Pod Manager)은 Red Hat이 주도하여 개발한 OCI(Open Container Initiative) 호환 컨테이너 엔진이다. Red Hat은 Docker의 단일 데몬(daemon) 아키텍처가 보안, 안정성, 시스템 통합 측면에서 근본적인 한계가 있다고 판단하고, 이를 해결하기 위해 Podman, Buildah, Skopeo라는 세 가지 도구를 개발했다.
- Podman: 컨테이너 실행 및 관리 (docker CLI 대체)
- Buildah: 이미지 빌드 특화 (docker build 대체)
- Skopeo: 이미지 복사, 검사, 서명 (레지스트리 간 이미지 관리)
이 세 도구는 각각 독립적으로 동작하면서도 서로 보완적인 관계를 형성한다. Unix 철학인 "하나의 일을 잘 하는 도구"를 충실히 따른 설계다.
1.3 왜 Podman인가?
Docker에서 Podman으로의 전환을 고려해야 하는 핵심 이유는 다음과 같다.
Daemonless Architecture: Docker는 항상 백그라운드에서 dockerd 데몬이 실행되어야 한다. 이 데몬이 죽으면 모든 컨테이너 관리가 불가능해진다(Single Point of Failure). Podman은 데몬 없이 각 컨테이너를 직접 fork-exec하므로 이 문제가 없다.
Rootless Containers: Docker도 rootless 모드를 지원하지만, Podman은 처음부터 rootless를 기본 설계 원칙으로 삼았다. 일반 사용자가 root 권한 없이 컨테이너를 실행할 수 있어 보안이 크게 향상된다.
Fork-Exec Model: 전통적인 Unix 프로세스 모델을 따르므로 systemd, audit, cgroup과의 통합이 자연스럽다. 각 컨테이너는 Podman 프로세스의 자식 프로세스로 존재한다.
Pod 네이티브 지원: Kubernetes의 Pod 개념을 로컬에서 그대로 사용할 수 있으며, podman generate kube 명령으로 Kubernetes YAML을 자동 생성할 수 있다.
CLI 호환성: alias docker=podman만 설정하면 대부분의 Docker 명령어가 그대로 동작한다.
2. 아키텍처 비교: Docker vs Podman
2.1 Docker 아키텍처: Client-Daemon 모델
Docker는 전형적인 클라이언트-서버 아키텍처를 따른다. 사용자가 docker run을 실행하면, Docker CLI(클라이언트)가 REST API를 통해 Docker Daemon(dockerd)에 요청을 보낸다. Daemon은 이를 containerd에 위임하고, containerd는 다시 runc를 호출하여 실제 컨테이너를 생성한다.
┌─────────────────────────────────────────────────────────────────┐
│ Docker Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ REST API ┌──────────────────┐ │
│ │ Docker │───────────────▶│ Docker Daemon │ │
│ │ CLI │ /var/run/ │ (dockerd) │ │
│ │ │ docker.sock │ │ │
│ └──────────┘ │ ┌──────────────┐ │ │
│ │ │ containerd │ │ │
│ │ │ │ │ │
│ │ │ ┌────────┐ │ │ │
│ │ │ │ runc │ │ │ │
│ │ │ │ (OCI) │ │ │ │
│ │ │ └────┬───┘ │ │ │
│ │ └───────┼─────┘ │ │
│ └──────────┼───────┘ │
│ │ │
│ ┌─────────────────────┼──────────────────┐ │
│ │ Container 1 │ Container 2 │ │
│ │ (process) │ (process) │ │
│ └─────────────────────┴──────────────────┘ │
│ │
│ ⚠ dockerd가 죽으면 모든 컨테이너 관리 불가 (SPOF) │
│ ⚠ docker.sock 접근 = root 권한 획득 가능 │
└─────────────────────────────────────────────────────────────────┘
이 구조의 핵심 문제점은 docker.sock에 대한 접근 권한이 사실상 root 권한과 동일하다는 것이다. Docker 그룹에 속한 사용자는 호스트의 파일 시스템을 마운트하거나 --privileged 플래그로 컨테이너를 실행하여 호스트를 완전히 장악할 수 있다.
2.2 Podman 아키텍처: Daemonless Fork-Exec 모델
Podman은 데몬이 없다. podman run을 실행하면, Podman 프로세스가 직접 conmon(Container Monitor)을 fork하고, conmon이 OCI 런타임(crun 또는 runc)을 실행하여 컨테이너를 생성한다.
┌─────────────────────────────────────────────────────────────────┐
│ Podman Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ direct fork-exec ┌──────────┐ │
│ │ Podman │────────────────────▶│ conmon │ │
│ │ CLI │ (no daemon!) │(container │ │
│ │ │ │ monitor) │ │
│ └──────────┘ └─────┬────┘ │
│ │ │
│ ┌─────▼────┐ │
│ │ crun │ │
│ │ (OCI │ │
│ │ runtime) │ │
│ └─────┬────┘ │
│ │ │
│ ┌─────────────────────┼──────────────────┐ │
│ │ Container 1 │ Container 2 │ │
│ │ (child proc) │ (child proc) │ │
│ └─────────────────────┴──────────────────┘ │
│ │
│ ✅ 데몬 없음 → SPOF 없음 │
│ ✅ 각 컨테이너가 독립 프로세스 → systemd 통합 용이 │
│ ✅ Rootless 기본 지원 │
└─────────────────────────────────────────────────────────────────┘
conmon은 컨테이너의 stdout/stderr를 캡처하고, 컨테이너의 exit code를 기록하며, Podman CLI가 종료된 후에도 컨테이너를 감시하는 경량 모니터 프로세스다.
2.3 핵심 비교 테이블
| 비교 항목 | Docker | Podman |
|---|---|---|
| 아키텍처 | Client-Daemon (dockerd) | Daemonless (fork-exec) |
| 기본 실행 권한 | root (daemon) | rootless (일반 사용자) |
| OCI 런타임 | runc | crun (기본), runc 지원 |
| 이미지 빌드 | 내장 (BuildKit) | Buildah 연동 |
| 데몬 프로세스 | 필수 (dockerd + containerd) | 없음 |
| systemd 통합 | 제한적 | 네이티브 (Quadlet) |
| Pod 지원 | 없음 (Compose로 대체) | 네이티브 지원 |
| K8s YAML 생성 | 없음 | podman generate kube |
| K8s YAML 실행 | 없음 | podman play kube |
| Socket 기반 API | docker.sock (항상 활성) | podman.sock (필요 시 활성) |
| 컨테이너 모니터 | containerd-shim | conmon |
| 기본 레지스트리 | docker.io 단일 | 다중 레지스트리 검색 |
| Compose 지원 | Docker Compose (공식) | podman-compose / podman compose |
| 라이선스 | Apache 2.0 + 상용(Desktop) | Apache 2.0 (완전 오픈소스) |
| 보안 프레임워크 | AppArmor/Seccomp | SELinux/AppArmor/Seccomp |
3. 설치 및 초기 설정
3.1 Docker 설치
Ubuntu / Debian
# 기존 Docker 패키지 제거
sudo apt-get remove docker docker-engine docker.io containerd runc
# 필수 패키지 설치
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release
# Docker 공식 GPG 키 추가
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Docker 저장소 추가
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Docker Engine 설치
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 현재 사용자를 docker 그룹에 추가 (재로그인 필요)
sudo usermod -aG docker $USER
# 서비스 시작 및 자동 시작 설정
sudo systemctl start docker
sudo systemctl enable docker
# 설치 확인
docker version
docker run hello-world
CentOS / RHEL / Rocky Linux
# 기존 패키지 제거
sudo yum remove -y docker docker-client docker-client-latest \
docker-common docker-latest docker-latest-logrotate \
docker-logrotate docker-engine
# Docker 저장소 추가
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
# Docker Engine 설치
sudo yum install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 서비스 시작
sudo systemctl start docker
sudo systemctl enable docker
# 사용자 그룹 추가
sudo usermod -aG docker $USER
macOS (Docker Desktop)
# Homebrew를 통한 설치
brew install --cask docker
# 또는 Docker Desktop 공식 사이트에서 .dmg 다운로드
# https://www.docker.com/products/docker-desktop/
# 설치 후 Docker Desktop 앱 실행 → Docker Engine 자동 시작
docker version
3.2 Podman 설치
Ubuntu / Debian
# Ubuntu 22.04+ 에서는 기본 저장소에 포함
sudo apt-get update
sudo apt-get install -y podman
# 최신 버전이 필요한 경우 (Ubuntu)
sudo mkdir -p /etc/apt/keyrings
curl -fsSL "https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_$(lsb_release -rs)/Release.key" \
| gpg --dearmor \
| sudo tee /etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg > /dev/null
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg] \
https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_$(lsb_release -rs)/ /" \
| sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list > /dev/null
sudo apt-get update
sudo apt-get install -y podman
# 설치 확인
podman version
podman info
CentOS / RHEL / Rocky Linux
# RHEL 8+ / CentOS Stream 8+ 에서는 기본 포함
sudo dnf install -y podman
# 추가 도구 설치
sudo dnf install -y buildah skopeo podman-compose
# Podman은 데몬이 없으므로 systemctl start 불필요!
podman version
macOS (Podman Machine)
# Homebrew를 통한 설치
brew install podman
# Podman Machine 초기화 (Linux VM 생성)
podman machine init
# Machine 시작
podman machine start
# 설치 확인
podman version
podman machine list
# Machine 중지
podman machine stop
# Machine 삭제
podman machine rm
참고: macOS와 Windows에서 Podman은 경량 Linux VM(QEMU 또는 Apple Hypervisor Framework 기반)을 생성하여 그 안에서 컨테이너를 실행한다. Docker Desktop과 유사한 방식이지만, Podman Desktop이라는 무료 GUI 도구도 제공된다.
3.3 호환성 설정: alias docker=podman
기존 Docker 기반 스크립트와 워크플로우를 변경 없이 사용하려면 간단한 alias 설정만으로 충분하다.
# 쉘 설정 파일에 추가 (~/.bashrc 또는 ~/.zshrc)
alias docker=podman
alias docker-compose=podman-compose
# 즉시 적용
source ~/.bashrc # 또는 source ~/.zshrc
# RHEL/CentOS에서는 podman-docker 패키지 제공
sudo dnf install -y podman-docker
# 이 패키지는 /usr/bin/docker → /usr/bin/podman 심볼릭 링크를 생성
# docker.sock 에뮬레이션도 포함
3.4 Docker Desktop vs Podman Desktop
| 비교 항목 | Docker Desktop | Podman Desktop |
|---|---|---|
| 라이선스 | 250인 이상 기업 유료 ($5+/user/month) | 완전 무료 (Apache 2.0) |
| GUI | 풍부한 UI | 기본 UI (빠르게 개선 중) |
| Extension | Docker Extensions 마켓플레이스 | Extension 지원 |
| Kubernetes | 내장 K8s 클러스터 | Kind/Minikube 연동 |
| VM 백엔드 | Linux Kit (macOS), WSL2 (Windows) | QEMU / Apple Hypervisor |
| 리소스 관리 | CPU, Memory, Disk 설정 | podman machine init --cpus --memory |
4. 이미지 관리 명령어
컨테이너는 이미지로부터 생성된다. 이미지 관리는 컨테이너 운영의 기본 중 기본이다.
4.1 이미지 검색
# Docker Hub에서 이미지 검색
docker search nginx
podman search nginx
# 결과 수 제한
docker search --limit 5 nginx
podman search --limit 5 nginx
# 공식 이미지만 검색 (Docker)
docker search --filter is-official=true nginx
# Podman은 여러 레지스트리를 동시 검색 (registries.conf 설정에 따라)
# /etc/containers/registries.conf
# unqualified-search-registries = ["docker.io", "quay.io", "ghcr.io"]
podman search --list-tags docker.io/library/nginx
4.2 이미지 풀 (Pull)
# 기본 이미지 다운로드
docker pull nginx
podman pull nginx
# 특정 태그 지정
docker pull nginx:1.25-alpine
podman pull nginx:1.25-alpine
# 특정 레지스트리에서 풀
docker pull ghcr.io/myorg/myapp:latest
podman pull quay.io/prometheus/prometheus:latest
# 특정 플랫폼(아키텍처) 지정
docker pull --platform linux/arm64 nginx:latest
podman pull --platform linux/arm64 nginx:latest
# 모든 태그 다운로드
docker pull --all-tags nginx
podman pull --all-tags nginx
# Digest로 특정 빌드 지정 (불변 참조)
docker pull nginx@sha256:abc123...
podman pull nginx@sha256:abc123...
4.3 이미지 목록 확인
# 로컬 이미지 목록
docker images
podman images
# 상세 형식 (동일한 명령어)
docker image ls
podman image ls
# 특정 이미지 필터링
docker images nginx
podman images nginx
# dangling 이미지만 표시 (태그 없는 이미지)
docker images -f dangling=true
podman images -f dangling=true
# 이미지 ID만 출력
docker images -q
podman images -q
# 커스텀 포맷 출력
docker images --format "{{.Repository}}:{{.Tag}} - {{.Size}}"
podman images --format "{{.Repository}}:{{.Tag}} - {{.Size}}"
# JSON 형태 출력 (Podman)
podman images --format json
4.4 이미지 상세 정보 (Inspect)
# 이미지 상세 정보 확인
docker inspect nginx:latest
podman inspect nginx:latest
# 특정 필드만 추출 (Go template)
docker inspect --format '{{.Config.ExposedPorts}}' nginx
podman inspect --format '{{.Config.ExposedPorts}}' nginx
# 이미지 크기 확인
docker inspect --format '{{.Size}}' nginx
podman inspect --format '{{.Size}}' nginx
# 환경 변수 확인
docker inspect --format '{{.Config.Env}}' nginx
podman inspect --format '{{.Config.Env}}' nginx
# Entrypoint 및 Cmd 확인
docker inspect --format '{{.Config.Entrypoint}} {{.Config.Cmd}}' nginx
podman inspect --format '{{.Config.Entrypoint}} {{.Config.Cmd}}' nginx
4.5 이미지 히스토리 (History)
# 이미지 레이어 히스토리 확인
docker history nginx
podman history nginx
# 전체 명령어 표시 (잘리지 않게)
docker history --no-trunc nginx
podman history --no-trunc nginx
# JSON 형태 (Podman)
podman history --format json nginx
4.6 이미지 태그 (Tag)
# 이미지에 새 태그 추가
docker tag nginx:latest myregistry.com/nginx:v1.0
podman tag nginx:latest myregistry.com/nginx:v1.0
# 여러 태그 추가
docker tag myapp:latest myapp:v2.1.0
docker tag myapp:latest myapp:stable
podman tag myapp:latest myapp:v2.1.0
podman tag myapp:latest myapp:stable
4.7 이미지 삭제 (Remove)
# 이미지 삭제
docker rmi nginx:latest
podman rmi nginx:latest
# 동일한 명령어 (권장)
docker image rm nginx:latest
podman image rm nginx:latest
# 강제 삭제 (실행 중인 컨테이너가 있어도)
docker rmi -f nginx:latest
podman rmi -f nginx:latest
# 모든 이미지 삭제
docker rmi $(docker images -q)
podman rmi -a
# Dangling 이미지만 삭제
docker image prune -f
podman image prune -f
# 사용되지 않는 모든 이미지 삭제
docker image prune -a -f
podman image prune -a -f
# 특정 조건으로 필터링하여 삭제 (24시간 이상 된 이미지)
docker image prune -a --filter "until=24h"
podman image prune -a --filter "until=24h"
4.8 이미지 저장/로드 (Save/Load)
에어갭(air-gapped) 환경이나 오프라인 전송 시 유용하다.
# 이미지를 tar 파일로 저장
docker save -o nginx.tar nginx:latest
podman save -o nginx.tar nginx:latest
# 여러 이미지를 하나의 tar로 저장
docker save -o images.tar nginx:latest redis:latest postgres:15
podman save -o images.tar nginx:latest redis:latest postgres:15
# gzip 압축으로 저장
docker save nginx:latest | gzip > nginx.tar.gz
podman save nginx:latest | gzip > nginx.tar.gz
# tar 파일에서 이미지 로드
docker load -i nginx.tar
podman load -i nginx.tar
# gzip 압축 파일에서 로드
docker load -i nginx.tar.gz
podman load -i nginx.tar.gz
4.9 이미지 Import/Export
save/load가 이미지 메타데이터(레이어, 태그, 히스토리)를 포함하는 반면, export/import는 컨테이너의 파일 시스템만 처리한다.
# 컨테이너의 파일 시스템을 tar로 내보내기
docker export my-container -o container-fs.tar
podman export my-container -o container-fs.tar
# tar 파일에서 이미지 생성
docker import container-fs.tar myimage:imported
podman import container-fs.tar myimage:imported
# URL에서 직접 import
docker import https://example.com/rootfs.tar.gz myimage:latest
podman import https://example.com/rootfs.tar.gz myimage:latest
4.10 레지스트리 로그인/푸시
# Docker Hub 로그인
docker login
podman login docker.io
# 특정 레지스트리 로그인
docker login ghcr.io
podman login quay.io
# 사용자명/패스워드 직접 지정
docker login -u username -p password registry.example.com
podman login -u username -p password registry.example.com
# 로그아웃
docker logout
podman logout docker.io
# 이미지 푸시
docker push myregistry.com/myapp:v1.0
podman push myregistry.com/myapp:v1.0
# 모든 태그 푸시
docker push --all-tags myregistry.com/myapp
podman push --all-tags myregistry.com/myapp
5. 이미지 빌드
5.1 Dockerfile vs Containerfile
Docker는 Dockerfile이라는 이름을 사용하고, Podman/Buildah는 Containerfile을 기본으로 인식한다. 그러나 Podman은 Dockerfile도 자동으로 인식하므로 이름 변경 없이 사용 가능하다. 내용과 문법은 완전히 동일하다.
# Dockerfile 또는 Containerfile — 문법은 동일
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
EXPOSE 3000
USER node
CMD ["node", "dist/main.js"]
| 항목 | Dockerfile | Containerfile |
|---|---|---|
| 사용 도구 | Docker | Podman, Buildah |
| 문법 | 동일 | 동일 |
| 기본 파일명 | Dockerfile | Containerfile |
| 상호 호환 | Podman이 Dockerfile 인식 | Docker는 Containerfile 미인식 |
| 지정 빌드 | docker build -f Containerfile . | podman build -f Dockerfile . |
5.2 docker build vs podman build
# 기본 빌드
docker build -t myapp:latest .
podman build -t myapp:latest .
# 특정 Dockerfile 지정
docker build -f Dockerfile.prod -t myapp:prod .
podman build -f Containerfile.prod -t myapp:prod .
# Build argument 전달
docker build --build-arg NODE_ENV=production -t myapp:prod .
podman build --build-arg NODE_ENV=production -t myapp:prod .
# 캐시 없이 빌드
docker build --no-cache -t myapp:latest .
podman build --no-cache -t myapp:latest .
# 타겟 스테이지 지정 (멀티스테이지)
docker build --target builder -t myapp:builder .
podman build --target builder -t myapp:builder .
# 멀티 플랫폼 빌드 (Docker BuildKit)
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .
# Podman 멀티 플랫폼 빌드
podman build --platform linux/amd64,linux/arm64 --manifest myapp:latest .
# 빌드 시 메모리 제한
docker build --memory 2g -t myapp:latest .
# 빌드 컨텍스트 크기 최소화 — .dockerignore 활용 필수
# .dockerignore (또는 .containerignore)
# node_modules
# .git
# *.md
# dist
# .env
5.3 멀티스테이지 빌드 (Multi-stage Build) 실전 예제
멀티스테이지 빌드는 빌드 환경과 실행 환경을 분리하여 최종 이미지 크기를 극적으로 줄이는 핵심 기법이다.
Go 애플리케이션 예제
# ============================================
# Stage 1: 빌드 환경
# ============================================
FROM golang:1.22-alpine AS builder
# 빌드에 필요한 도구 설치
RUN apk add --no-cache git ca-certificates
WORKDIR /app
# 의존성 먼저 복사 (캐시 활용)
COPY go.mod go.sum ./
RUN go mod download
# 소스 코드 복사 및 빌드
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s" -o /app/server ./cmd/server
# ============================================
# Stage 2: 실행 환경 (scratch = 빈 이미지)
# ============================================
FROM scratch
# CA 인증서 복사 (HTTPS 통신용)
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 바이너리만 복사
COPY /app/server /server
# 비root 사용자로 실행
USER 65534:65534
EXPOSE 8080
ENTRYPOINT ["/server"]
Java (Spring Boot) 예제
# ============================================
# Stage 1: 빌드
# ============================================
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY gradle/ gradle/
COPY gradlew build.gradle.kts settings.gradle.kts ./
RUN ./gradlew dependencies --no-daemon
COPY src/ src/
RUN ./gradlew bootJar --no-daemon -x test
# JRE 커스텀 런타임 생성 (jlink)
RUN jlink \
--add-modules java.base,java.logging,java.sql,java.naming,java.management,java.instrument,java.security.jgss,java.desktop \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress=zip-6 \
--output /custom-jre
# ============================================
# Stage 2: 실행 (커스텀 JRE)
# ============================================
FROM alpine:3.19
COPY /custom-jre /opt/java
COPY /app/build/libs/*.jar /app/app.jar
ENV JAVA_HOME=/opt/java
ENV PATH="${JAVA_HOME}/bin:${PATH}"
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Python 예제
# ============================================
# Stage 1: 의존성 빌드
# ============================================
FROM python:3.12-slim AS builder
RUN pip install --no-cache-dir poetry
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# ============================================
# Stage 2: 실행 환경
# ============================================
FROM python:3.12-slim
# 필요한 시스템 라이브러리만 설치
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
COPY /install /usr/local
WORKDIR /app
COPY . .
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
5.4 Build Cache 전략
# ❌ 비효율적: 소스 변경 시 매번 npm install 재실행
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
# ✅ 효율적: package.json 변경 시에만 npm install 재실행
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
캐시 레이어 순서 원칙: 변경 빈도가 낮은 레이어를 위쪽에, 변경 빈도가 높은 레이어를 아래쪽에 배치한다.
변경 빈도 낮음 ─── FROM (베이스 이미지)
│ RUN apt-get install (시스템 패키지)
│ COPY package.json (의존성 정의)
│ RUN npm ci (의존성 설치)
│ COPY . . (소스 코드)
변경 빈도 높음 ─── RUN npm run build (빌드)
5.5 Buildah를 활용한 스크립트 기반 빌드
Buildah는 Dockerfile 없이 쉘 스크립트로 이미지를 빌드할 수 있는 강력한 도구다.
#!/bin/bash
# buildah-build.sh — Dockerfile 없이 이미지 빌드
# 새로운 컨테이너 생성 (빈 이미지에서 시작)
container=$(buildah from scratch)
# 또는 베이스 이미지에서 시작
container=$(buildah from alpine:3.19)
# 패키지 설치
buildah run $container -- apk add --no-cache nginx
# 파일 복사
buildah copy $container ./nginx.conf /etc/nginx/nginx.conf
buildah copy $container ./html /var/www/html
# 설정
buildah config --port 80 $container
buildah config --entrypoint '["/usr/sbin/nginx", "-g", "daemon off;"]' $container
buildah config --author "DevOps Team" $container
buildah config --label maintainer="devops@example.com" $container
# 이미지로 커밋
buildah commit $container myapp:latest
# 정리
buildah rm $container
# 빌드 결과 확인
buildah images
5.6 Skopeo를 활용한 이미지 복사/검사
Skopeo는 이미지를 다운로드하지 않고 레지스트리 간 직접 복사하거나 검사할 수 있다.
# 레지스트리 간 이미지 직접 복사 (로컬 다운로드 없이!)
skopeo copy docker://docker.io/nginx:latest docker://quay.io/myorg/nginx:latest
# 이미지 메타데이터 검사 (다운로드 없이)
skopeo inspect docker://docker.io/library/nginx:latest
# 이미지 태그 목록 확인
skopeo list-tags docker://docker.io/library/nginx
# 이미지를 로컬 디렉토리로 저장 (OCI 형식)
skopeo copy docker://nginx:latest oci:./nginx-oci:latest
# 이미지를 tar 파일로 저장
skopeo copy docker://nginx:latest docker-archive:./nginx.tar:nginx:latest
# Private 레지스트리에 인증하여 복사
skopeo copy --src-creds user:pass --dest-creds user:pass \
docker://source-registry.com/app:v1 \
docker://dest-registry.com/app:v1
# 이미지 삭제 (레지스트리에서)
skopeo delete docker://myregistry.com/myapp:old-tag
6. 컨테이너 라이프사이클 명령어
6.1 컨테이너 생성 (Create)
create는 컨테이너를 생성만 하고 시작하지 않는다. 이후 start로 시작할 수 있다.
# 컨테이너 생성 (시작하지 않음)
docker create --name my-nginx nginx:latest
podman create --name my-nginx nginx:latest
# 생성된 컨테이너 확인
docker ps -a
podman ps -a
# 생성된 컨테이너 시작
docker start my-nginx
podman start my-nginx
6.2 컨테이너 실행 (Run) — 모든 주요 옵션 정리
run은 create + start를 한 번에 수행한다. 컨테이너 운영에서 가장 빈번하게 사용하는 명령어다.
# ============================================
# 기본 실행
# ============================================
docker run nginx
podman run nginx
# ============================================
# 백그라운드 실행 (-d: detach)
# ============================================
docker run -d --name web nginx
podman run -d --name web nginx
# ============================================
# 인터랙티브 모드 (-it: interactive + tty)
# ============================================
docker run -it ubuntu:22.04 bash
podman run -it ubuntu:22.04 bash
# ============================================
# 종료 시 자동 삭제 (--rm)
# ============================================
docker run --rm -it alpine sh
podman run --rm -it alpine sh
# ============================================
# 포트 매핑 (-p host:container)
# ============================================
docker run -d -p 8080:80 nginx # 특정 포트
docker run -d -p 80:80 -p 443:443 nginx # 다중 포트
docker run -d -p 127.0.0.1:8080:80 nginx # 특정 인터페이스
docker run -d -P nginx # 랜덤 포트 자동 매핑
podman run -d -p 8080:80 nginx
# ============================================
# 볼륨 마운트 (-v host:container[:options])
# ============================================
docker run -d -v /host/data:/container/data nginx # Bind mount
docker run -d -v myvolume:/container/data nginx # Named volume
docker run -d -v /host/data:/container/data:ro nginx # Read-only
docker run -d --mount type=tmpfs,destination=/tmp nginx # tmpfs
podman run -d -v /host/data:/container/data:Z nginx # SELinux 라벨 (:Z)
# ============================================
# 환경 변수 (-e, --env-file)
# ============================================
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql:8
docker run -d -e DB_HOST=db -e DB_PORT=5432 myapp
docker run -d --env-file .env myapp
podman run -d -e MYSQL_ROOT_PASSWORD=secret mysql:8
# ============================================
# 리소스 제한 (--memory, --cpus)
# ============================================
docker run -d --memory 512m --memory-swap 1g nginx
docker run -d --cpus 1.5 nginx
docker run -d --cpus 2 --memory 1g --memory-reservation 512m myapp
podman run -d --memory 512m --cpus 1.5 nginx
# ============================================
# 재시작 정책 (--restart)
# ============================================
docker run -d --restart always nginx # 항상 재시작
docker run -d --restart unless-stopped nginx # 수동 중지 제외 재시작
docker run -d --restart on-failure:5 nginx # 실패 시 최대 5회 재시작
docker run -d --restart no nginx # 재시작 안 함 (기본값)
podman run -d --restart always nginx
# ============================================
# 네트워크 설정 (--network)
# ============================================
docker run -d --network my-network nginx
docker run -d --network host nginx # 호스트 네트워크 직접 사용
docker run -d --network none nginx # 네트워크 없음
podman run -d --network my-network nginx
# ============================================
# PID / IPC 네임스페이스 공유
# ============================================
docker run -d --pid host nginx # 호스트 PID 네임스페이스
docker run -d --pid container:other nginx # 다른 컨테이너의 PID 공유
docker run -d --ipc host nginx # 호스트 IPC 공유
# ============================================
# 기타 유용한 옵션
# ============================================
docker run -d --hostname myhost nginx # 호스트명 설정
docker run -d --dns 8.8.8.8 nginx # DNS 서버 지정
docker run -d --add-host mydb:10.0.0.5 nginx # /etc/hosts 항목 추가
docker run -d --workdir /app myapp # 작업 디렉토리
docker run -d --user 1000:1000 myapp # 실행 사용자 지정
docker run -d --read-only myapp # 읽기 전용 파일 시스템
docker run -d --log-driver json-file \
--log-opt max-size=10m --log-opt max-file=3 nginx # 로그 드라이버 설정
6.3 컨테이너 목록 (ps)
# 실행 중인 컨테이너 목록
docker ps
podman ps
# 모든 컨테이너 (중지된 것 포함)
docker ps -a
podman ps -a
# 최근 생성된 컨테이너 n개
docker ps -n 5
podman ps -n 5
# 컨테이너 ID만 출력
docker ps -q
podman ps -q
# 커스텀 포맷
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
podman ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 특정 상태 필터링
docker ps -f status=exited
docker ps -f status=running
docker ps -f name=web
podman ps -f status=exited
6.4 컨테이너 시작/중지/재시작
# 컨테이너 시작
docker start my-nginx
podman start my-nginx
# 여러 컨테이너 동시 시작
docker start web db redis
podman start web db redis
# 컨테이너 중지 (SIGTERM → 10초 후 SIGKILL)
docker stop my-nginx
podman stop my-nginx
# 타임아웃 지정 (30초 후 강제 종료)
docker stop -t 30 my-nginx
podman stop -t 30 my-nginx
# 모든 실행 중인 컨테이너 중지
docker stop $(docker ps -q)
podman stop -a
# 컨테이너 강제 종료 (SIGKILL)
docker kill my-nginx
podman kill my-nginx
# 특정 시그널 전송
docker kill -s SIGHUP my-nginx
podman kill -s SIGHUP my-nginx
# 컨테이너 재시작
docker restart my-nginx
podman restart my-nginx
# 모든 컨테이너 재시작
docker restart $(docker ps -q)
podman restart -a
6.5 컨테이너 일시정지/재개
# 컨테이너 일시정지 (SIGSTOP — 프로세스 freeze)
docker pause my-nginx
podman pause my-nginx
# 일시정지 해제 (SIGCONT)
docker unpause my-nginx
podman unpause my-nginx
6.6 컨테이너 삭제
# 중지된 컨테이너 삭제
docker rm my-nginx
podman rm my-nginx
# 실행 중인 컨테이너 강제 삭제
docker rm -f my-nginx
podman rm -f my-nginx
# 볼륨도 함께 삭제
docker rm -v my-nginx
podman rm -v my-nginx
# 모든 중지된 컨테이너 삭제
docker container prune -f
podman container prune -f
# 모든 컨테이너 삭제 (실행 중 포함)
docker rm -f $(docker ps -aq)
podman rm -f -a
6.7 컨테이너 이름 변경 / 커밋 / 대기
# 컨테이너 이름 변경
docker rename old-name new-name
podman rename old-name new-name
# 컨테이너를 이미지로 저장 (현재 상태를 스냅샷)
docker commit my-container myimage:snapshot
podman commit my-container myimage:snapshot
# 커밋 시 메타데이터 추가
docker commit -m "Added config files" -a "Author" my-container myimage:v2
podman commit -m "Added config files" -a "Author" my-container myimage:v2
# 컨테이너 종료 대기 (exit code 반환)
docker wait my-container
podman wait my-container
7. 컨테이너 모니터링 및 디버깅
7.1 로그 확인 (Logs)
# 전체 로그 출력
docker logs my-container
podman logs my-container
# 실시간 로그 스트리밍 (follow)
docker logs -f my-container
podman logs -f my-container
# 마지막 N줄만 표시
docker logs --tail 100 my-container
podman logs --tail 100 my-container
# 타임스탬프 포함
docker logs -t my-container
podman logs -t my-container
# 특정 시간 이후 로그
docker logs --since 2024-01-01T00:00:00 my-container
docker logs --since 30m my-container # 최근 30분
docker logs --since 2h my-container # 최근 2시간
podman logs --since 30m my-container
# 특정 시간 이전 로그
docker logs --until 2024-01-01T12:00:00 my-container
podman logs --until 2024-01-01T12:00:00 my-container
# 조합: 실시간 + 마지막 50줄 + 타임스탬프
docker logs -f --tail 50 -t my-container
podman logs -f --tail 50 -t my-container
7.2 실시간 리소스 모니터링 (Stats)
# 모든 실행 중 컨테이너의 리소스 사용량 실시간 표시
docker stats
podman stats
# 특정 컨테이너만 모니터링
docker stats my-container
podman stats my-container
# 한 번만 출력 (스크립트용)
docker stats --no-stream
podman stats --no-stream
# 커스텀 포맷
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
podman stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
출력 예시:
NAME CPU % MEM USAGE / LIMIT NET I/O BLOCK I/O
web 0.50% 45.2MiB / 512MiB 12.5kB / 8.3kB 4.1MB / 0B
db 2.30% 256MiB / 1GiB 45.2kB / 12.1kB 50MB / 120MB
redis 0.10% 12.5MiB / 256MiB 3.2kB / 1.1kB 0B / 0B
7.3 프로세스 확인 (Top)
# 컨테이너 내부 프로세스 목록
docker top my-container
podman top my-container
# ps 옵션 전달
docker top my-container -aux
podman top my-container -aux
# Podman 전용: 추가 필드 지원
podman top my-container user pid ppid args %cpu %mem
podman top my-container huser hpid
7.4 상세 정보 확인 (Inspect)
# 컨테이너 전체 정보 (JSON)
docker inspect my-container
podman inspect my-container
# IP 주소 확인
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-container
podman inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-container
# 마운트 정보 확인
docker inspect -f '{{json .Mounts}}' my-container | jq .
podman inspect -f '{{json .Mounts}}' my-container | jq .
# 상태 확인
docker inspect -f '{{.State.Status}}' my-container
podman inspect -f '{{.State.Status}}' my-container
# 재시작 횟수 확인
docker inspect -f '{{.RestartCount}}' my-container
# 환경 변수 확인
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' my-container
podman inspect -f '{{range .Config.Env}}{{println .}}{{end}}' my-container
# 로그 경로 확인 (Docker)
docker inspect -f '{{.LogPath}}' my-container
7.5 파일 복사 (cp)
# 호스트 → 컨테이너
docker cp ./config.yml my-container:/app/config.yml
podman cp ./config.yml my-container:/app/config.yml
# 컨테이너 → 호스트
docker cp my-container:/app/logs/error.log ./error.log
podman cp my-container:/app/logs/error.log ./error.log
# 디렉토리 복사
docker cp ./configs/ my-container:/app/configs/
podman cp ./configs/ my-container:/app/configs/
# 아카이브 모드 (심볼릭 링크 보존)
docker cp -a my-container:/app/data ./backup/
podman cp -a my-container:/app/data ./backup/
7.6 컨테이너 접속 (Exec)
실행 중인 컨테이너 내부에서 명령어를 실행하는 핵심 디버깅 도구다.
# 인터랙티브 쉘 접속
docker exec -it my-container bash
docker exec -it my-container sh # bash가 없는 경우 (Alpine 등)
podman exec -it my-container bash
# 단일 명령어 실행
docker exec my-container ls -la /app
podman exec my-container ls -la /app
# 환경 변수 설정 후 실행
docker exec -e DEBUG=true my-container node script.js
podman exec -e DEBUG=true my-container node script.js
# 특정 사용자로 실행
docker exec -u root my-container apt-get update
podman exec -u root my-container dnf update
# 작업 디렉토리 지정
docker exec -w /app my-container npm test
podman exec -w /app my-container npm test
# 백그라운드로 명령 실행 (detach)
docker exec -d my-container touch /tmp/healthcheck
podman exec -d my-container touch /tmp/healthcheck
7.7 컨테이너 변경사항 (Diff)
# 컨테이너 파일 시스템 변경사항 확인
docker diff my-container
podman diff my-container
# 출력 예시:
# A /tmp/new-file (Added)
# C /etc/nginx/nginx.conf (Changed)
# D /var/log/old.log (Deleted)
7.8 포트 매핑 확인 (Port)
# 컨테이너의 포트 매핑 확인
docker port my-container
podman port my-container
# 특정 포트 확인
docker port my-container 80
podman port my-container 80
# 출력 예시:
# 80/tcp -> 0.0.0.0:8080
# 443/tcp -> 0.0.0.0:8443
7.9 이벤트 모니터링 (Events)
# 실시간 이벤트 스트리밍
docker events
podman events
# 특정 이벤트 타입 필터
docker events --filter event=start
docker events --filter event=stop
docker events --filter event=die
podman events --filter event=start
# 특정 컨테이너 이벤트만 필터
docker events --filter container=my-container
podman events --filter container=my-container
# 시간 범위 지정
docker events --since 1h --until 30m
podman events --since 1h
# JSON 형태 출력
docker events --format '{{json .}}'
podman events --format json
8. 네트워크 관리
8.1 네트워크 종류
| 네트워크 드라이버 | 설명 | Docker | Podman |
|---|---|---|---|
| bridge | 기본 네트워크, 가상 브리지를 통한 컨테이너 간 통신 | docker0 (기본 브리지) | Netavark (v4+) / CNI |
| host | 호스트 네트워크 직접 사용, 포트 매핑 불필요 | 지원 | 지원 |
| none | 네트워크 없음, 완전 격리 | 지원 | 지원 |
| macvlan | 컨테이너에 MAC 주소 할당, 물리 네트워크 직접 연결 | 지원 | 지원 |
| overlay | 멀티 호스트 네트워크 (Swarm) | 지원 | 미지원 |
| ipvlan | macvlan과 유사하나 같은 MAC 공유 | 지원 | 지원 |
8.2 네트워크 CRUD 명령어
# ============================================
# 네트워크 생성 (Create)
# ============================================
# 기본 bridge 네트워크 생성
docker network create my-network
podman network create my-network
# 서브넷 및 게이트웨이 지정
docker network create \
--subnet 172.20.0.0/16 \
--gateway 172.20.0.1 \
my-network
podman network create \
--subnet 172.20.0.0/16 \
--gateway 172.20.0.1 \
my-network
# IP 범위 지정
docker network create \
--subnet 172.20.0.0/16 \
--ip-range 172.20.240.0/20 \
--gateway 172.20.0.1 \
my-network
# 내부 네트워크 (외부 접근 불가)
docker network create --internal internal-net
podman network create --internal internal-net
# 특정 드라이버 지정
docker network create --driver macvlan \
--subnet 192.168.1.0/24 \
--gateway 192.168.1.1 \
-o parent=eth0 \
macvlan-net
# ============================================
# 네트워크 목록 (List)
# ============================================
docker network ls
podman network ls
# ============================================
# 네트워크 상세 정보 (Inspect)
# ============================================
docker network inspect my-network
podman network inspect my-network
# 연결된 컨테이너 확인
docker network inspect -f '{{range .Containers}}{{.Name}} {{end}}' my-network
# ============================================
# 네트워크 삭제 (Remove)
# ============================================
docker network rm my-network
podman network rm my-network
# 미사용 네트워크 정리
docker network prune -f
podman network prune -f
8.3 네트워크 연결/해제
# 실행 중인 컨테이너를 네트워크에 연결
docker network connect my-network my-container
podman network connect my-network my-container
# 고정 IP로 연결
docker network connect --ip 172.20.0.10 my-network my-container
podman network connect --ip 172.20.0.10 my-network my-container
# 네트워크에서 분리
docker network disconnect my-network my-container
podman network disconnect my-network my-container
8.4 컨테이너 간 통신 예제
사용자 정의 네트워크에서는 컨테이너 이름으로 DNS 해석이 자동으로 이루어진다.
# 사용자 정의 네트워크 생성
docker network create app-net
podman network create app-net
# 데이터베이스 컨테이너
docker run -d --name db --network app-net \
-e POSTGRES_PASSWORD=secret \
postgres:16-alpine
# 애플리케이션 컨테이너 (DB에 이름으로 접속 가능)
docker run -d --name app --network app-net \
-e DATABASE_URL="postgresql://postgres:secret@db:5432/mydb" \
-p 3000:3000 \
myapp:latest
# 테스트: app 컨테이너에서 db로 ping
docker exec app ping -c 3 db
# PING db (172.20.0.2): 56 data bytes
# 64 bytes from 172.20.0.2: icmp_seq=0 ttl=64 time=0.123 ms
주의: Docker/Podman의 기본
bridge네트워크에서는 컨테이너 이름으로 DNS 해석이 되지 않는다. 반드시 사용자 정의 네트워크(user-defined network) 를 생성해야 컨테이너 이름으로 통신이 가능하다.
8.5 Docker: docker0 bridge vs Podman: Netavark/CNI
Docker는 기본적으로 docker0라는 Linux bridge 인터페이스를 생성하고 iptables 규칙으로 네트워킹을 관리한다.
Podman은 v4.0부터 기본 네트워크 스택을 CNI(Container Network Interface) 에서 Netavark로 전환했다. Netavark는 Rust로 작성된 컨테이너 네트워크 스택으로, Aardvark-dns와 함께 DNS 해석을 제공한다.
# Podman 네트워크 백엔드 확인
podman info --format '{{.Host.NetworkBackend}}'
# 출력: netavark
# Netavark + Aardvark-dns 구조:
# ┌──────────────┐ ┌──────────────┐
# │ Container A │ │ Container B │
# │ 172.20.0.2 │ │ 172.20.0.3 │
# └──────┬───────┘ └──────┬───────┘
# │ │
# ┌──────▼─────────────────────▼──────┐
# │ Netavark Bridge │
# │ (nftables 기반 네트워크 관리) │
# ├───────────────────────────────────┤
# │ Aardvark-dns │
# │ (컨테이너 이름 → IP 해석) │
# └───────────────────────────────────┘
9. 볼륨 및 스토리지 관리
9.1 Named Volume 관리
# ============================================
# 볼륨 생성
# ============================================
docker volume create my-data
podman volume create my-data
# 드라이버 및 옵션 지정
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/path/to/share \
nfs-volume
# 라벨 추가
docker volume create --label project=myapp --label env=prod my-data
podman volume create --label project=myapp --label env=prod my-data
# ============================================
# 볼륨 목록
# ============================================
docker volume ls
podman volume ls
# 필터링
docker volume ls -f label=project=myapp
podman volume ls -f label=project=myapp
# Dangling 볼륨 (컨테이너에 연결되지 않은)
docker volume ls -f dangling=true
podman volume ls -f dangling=true
# ============================================
# 볼륨 상세 정보
# ============================================
docker volume inspect my-data
podman volume inspect my-data
# 마운트 포인트 확인
docker volume inspect -f '{{.Mountpoint}}' my-data
podman volume inspect -f '{{.Mountpoint}}' my-data
# ============================================
# 볼륨 삭제
# ============================================
docker volume rm my-data
podman volume rm my-data
# 미사용 볼륨 정리
docker volume prune -f
podman volume prune -f
9.2 Bind Mount vs Named Volume vs tmpfs
| 비교 항목 | Bind Mount | Named Volume | tmpfs |
|---|---|---|---|
| 호스트 경로 | 직접 지정 | Docker/Podman이 관리 | 없음 (메모리) |
| 데이터 지속성 | 호스트에 영구 저장 | 볼륨에 영구 저장 | 컨테이너 종료 시 삭제 |
| 성능 | 호스트 FS 의존 | 최적화 가능 | 최고 (메모리 기반) |
| 이식성 | 낮음 (호스트 경로 의존) | 높음 | 높음 |
| 백업 | 직접 관리 | docker volume 명령 | 불가 |
| 사용 예시 | 소스 코드 마운트, 설정 파일 | DB 데이터, 업로드 파일 | 임시 파일, 시크릿 |
# Bind Mount
docker run -d -v /home/user/data:/app/data nginx
docker run -d --mount type=bind,source=/home/user/data,target=/app/data nginx
# Named Volume
docker run -d -v app-data:/app/data nginx
docker run -d --mount type=volume,source=app-data,target=/app/data nginx
# tmpfs (메모리 기반, 컨테이너 종료 시 삭제)
docker run -d --tmpfs /tmp:rw,size=100m nginx
docker run -d --mount type=tmpfs,destination=/tmp,tmpfs-size=100m nginx
# 읽기 전용 마운트
docker run -d -v /host/config:/app/config:ro nginx
docker run -d --mount type=bind,source=/host/config,target=/app/config,readonly nginx
# Podman에서 SELinux 라벨 설정
podman run -d -v /host/data:/app/data:Z nginx # Z: 프라이빗 라벨
podman run -d -v /host/data:/app/data:z nginx # z: 공유 라벨
9.3 데이터 백업/복원 패턴
# ============================================
# 볼륨 데이터 백업
# ============================================
# 방법 1: 임시 컨테이너를 이용한 백업
docker run --rm \
-v my-data:/source:ro \
-v $(pwd):/backup \
alpine tar czf /backup/my-data-backup.tar.gz -C /source .
podman run --rm \
-v my-data:/source:ro \
-v $(pwd):/backup \
alpine tar czf /backup/my-data-backup.tar.gz -C /source .
# 방법 2: 날짜별 백업
docker run --rm \
-v postgres-data:/source:ro \
-v $(pwd)/backups:/backup \
alpine tar czf /backup/postgres-$(date +%Y%m%d).tar.gz -C /source .
# ============================================
# 볼륨 데이터 복원
# ============================================
# 새 볼륨 생성 후 복원
docker volume create restored-data
docker run --rm \
-v restored-data:/target \
-v $(pwd):/backup:ro \
alpine tar xzf /backup/my-data-backup.tar.gz -C /target
podman run --rm \
-v restored-data:/target \
-v $(pwd):/backup:ro \
alpine tar xzf /backup/my-data-backup.tar.gz -C /target
# ============================================
# 볼륨 간 데이터 마이그레이션
# ============================================
docker run --rm \
-v old-volume:/from:ro \
-v new-volume:/to \
alpine sh -c "cp -a /from/. /to/"
10. Docker Compose vs Podman Compose
10.1 docker-compose.yml 기본 구조
# docker-compose.yml (또는 compose.yml)
version: '3.9' # Compose 파일 형식 버전 (v2에서는 선택적)
services:
web:
build: ./app # Dockerfile 경로
image: myapp:latest # 빌드된 이미지 이름
container_name: myapp-web # 컨테이너 이름
ports:
- '3000:3000' # 포트 매핑
environment: # 환경 변수
- NODE_ENV=production
- DB_HOST=db
env_file: # 환경 변수 파일
- .env
volumes: # 볼륨 마운트
- ./app:/app
- node_modules:/app/node_modules
depends_on: # 의존 관계
db:
condition: service_healthy
redis:
condition: service_started
networks: # 네트워크
- app-net
restart: unless-stopped # 재시작 정책
deploy: # 리소스 제한
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
healthcheck: # 헬스체크
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:16-alpine
container_name: myapp-db
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-net
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U admin -d myapp']
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: myapp-redis
command: redis-server --appendonly yes --maxmemory 256mb
volumes:
- redis-data:/data
networks:
- app-net
volumes:
postgres-data:
driver: local
redis-data:
driver: local
node_modules:
networks:
app-net:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
10.2 주요 Compose 명령어
# ============================================
# Docker Compose (v2: docker compose / v1: docker-compose)
# ============================================
# 서비스 시작 (백그라운드)
docker compose up -d
docker compose -f docker-compose.prod.yml up -d
# 서비스 빌드 후 시작
docker compose up -d --build
# 특정 서비스만 시작
docker compose up -d web db
# 스케일링 (서비스 인스턴스 수 조정)
docker compose up -d --scale web=3
# 서비스 중지 및 리소스 정리
docker compose down
# 볼륨까지 삭제
docker compose down -v
# 이미지까지 삭제
docker compose down --rmi all
# 서비스 목록 및 상태
docker compose ps
# 서비스 로그
docker compose logs
docker compose logs -f web
docker compose logs --tail 100 web db
# 서비스 내부 명령 실행
docker compose exec web bash
docker compose exec db psql -U admin -d myapp
# 일회성 명령 실행 (run은 새 컨테이너 생성)
docker compose run --rm web npm test
docker compose run --rm web python manage.py migrate
# 서비스 빌드
docker compose build
docker compose build --no-cache web
# 서비스 재시작
docker compose restart
docker compose restart web
# 설정 유효성 검사
docker compose config
# 이미지 풀
docker compose pull
# ============================================
# Podman Compose
# ============================================
# podman-compose 설치
pip3 install podman-compose
# 또는 Podman 4.7+ 에서 podman compose (플러그인 방식)
# 사용법은 docker compose와 동일
podman compose up -d
podman compose down
podman compose ps
podman compose logs -f web
podman compose exec web bash
10.3 실전 예제: 웹앱 + DB + Redis 3-tier 구성
# compose.yml — 프로덕션 3-tier 아키텍처
services:
# ============================================
# Tier 1: Reverse Proxy (Nginx)
# ============================================
nginx:
image: nginx:1.25-alpine
container_name: proxy
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
app:
condition: service_healthy
networks:
- frontend
restart: unless-stopped
# ============================================
# Tier 2: Application (Node.js)
# ============================================
app:
build:
context: ./app
dockerfile: Dockerfile
args:
NODE_ENV: production
container_name: app
expose:
- '3000'
environment:
- NODE_ENV=production
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=appdb
- DB_USER=appuser
- DB_PASS=${DB_PASSWORD}
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- frontend
- backend
healthcheck:
test:
[
'CMD',
'node',
'-e',
"require('http').get('http://localhost:3000/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1) })",
]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
restart: unless-stopped
# ============================================
# Tier 3: Database (PostgreSQL)
# ============================================
postgres:
image: postgres:16-alpine
container_name: postgres
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgres-data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d:ro
networks:
- backend
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U appuser -d appdb']
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
restart: unless-stopped
# ============================================
# Cache: Redis
# ============================================
redis:
image: redis:7-alpine
container_name: redis
command: >
redis-server
--appendonly yes
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--requirepass ${REDIS_PASSWORD}
volumes:
- redis-data:/data
networks:
- backend
healthcheck:
test: ['CMD', 'redis-cli', '-a', '${REDIS_PASSWORD}', 'ping']
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
restart: unless-stopped
volumes:
postgres-data:
driver: local
redis-data:
driver: local
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 외부 접근 차단
10.4 환경별 구성 (Override)
# compose.override.yml — 개발 환경 (자동 로드)
services:
app:
build:
args:
NODE_ENV: development
volumes:
- ./app/src:/app/src # 소스 코드 핫 리로드
environment:
- NODE_ENV=development
- DEBUG=app:*
ports:
- '3000:3000' # 개발 시 직접 접근
- '9229:9229' # Node.js 디버거 포트
postgres:
ports:
- '5432:5432' # 개발 시 직접 접근
# compose.prod.yml — 프로덕션 환경
services:
app:
build:
args:
NODE_ENV: production
deploy:
replicas: 3
resources:
limits:
cpus: '4.0'
memory: 2G
nginx:
deploy:
resources:
limits:
cpus: '1.0'
memory: 256M
# 개발 환경 (compose.yml + compose.override.yml 자동 병합)
docker compose up -d
# 프로덕션 환경 (override 제외, prod 설정 사용)
docker compose -f compose.yml -f compose.prod.yml up -d
# 환경별 .env 파일 사용
docker compose --env-file .env.production up -d
11. Podman 고유 기능
11.1 Pod 관리
Podman의 Pod는 Kubernetes의 Pod와 동일한 개념이다. 하나의 Pod 안에 있는 컨테이너들은 Network namespace, IPC namespace, PID namespace를 공유한다.
# ============================================
# Pod 생성
# ============================================
# 기본 Pod 생성
podman pod create --name my-pod
# 포트 매핑과 함께 생성 (Pod 레벨에서 포트 정의)
podman pod create --name web-pod -p 8080:80 -p 8443:443
# 네트워크 지정
podman pod create --name web-pod --network my-network
# ============================================
# Pod에 컨테이너 추가
# ============================================
# Pod에 컨테이너 실행 (--pod 옵션)
podman run -d --pod my-pod --name nginx nginx:latest
podman run -d --pod my-pod --name php php:8.2-fpm
# Pod 내 컨테이너들은 localhost로 통신 가능
# nginx → localhost:9000 → php-fpm
# ============================================
# Pod 관리
# ============================================
# Pod 목록
podman pod list
podman pod ps
# Pod 상세 정보
podman pod inspect my-pod
# Pod 시작/중지/재시작
podman pod start my-pod
podman pod stop my-pod
podman pod restart my-pod
# Pod 일시정지/재개
podman pod pause my-pod
podman pod unpause my-pod
# Pod 삭제 (컨테이너 포함)
podman pod rm my-pod
podman pod rm -f my-pod # 강제 삭제
# 모든 Pod 삭제
podman pod rm -a -f
# Pod의 프로세스 확인
podman pod top my-pod
# Pod의 리소스 사용량
podman pod stats my-pod
11.2 Kubernetes YAML 생성 (podman generate kube)
로컬에서 테스트한 컨테이너/Pod 구성을 Kubernetes YAML로 자동 변환할 수 있다.
# 컨테이너에서 K8s YAML 생성
podman generate kube my-container > deployment.yaml
# Pod에서 K8s YAML 생성
podman generate kube my-pod > pod.yaml
# Service 정보 포함
podman generate kube --service my-pod > pod-with-service.yaml
# 생성된 YAML 예시:
# apiVersion: v1
# kind: Pod
# metadata:
# name: my-pod
# spec:
# containers:
# - name: nginx
# image: nginx:latest
# ports:
# - containerPort: 80
# hostPort: 8080
# - name: php
# image: php:8.2-fpm
11.3 Kubernetes YAML 실행 (podman play kube)
반대로 Kubernetes YAML 파일을 Podman에서 직접 실행할 수도 있다.
# K8s YAML 파일로 Pod 생성
podman play kube deployment.yaml
# ConfigMap 지원
podman play kube pod.yaml --configmap configmap.yaml
# 시크릿 지원
podman play kube pod.yaml --seccomp-profile-root ./profiles
# 기존 리소스 교체 (업데이트)
podman play kube --replace pod.yaml
# 리소스 삭제
podman play kube --down pod.yaml
# 빌드 포함
podman play kube --build pod.yaml
11.4 Systemd 서비스 생성
# 컨테이너를 systemd 서비스로 생성
podman generate systemd --name my-container > ~/.config/systemd/user/my-container.service
# 새 설정 반영 (사용자 레벨)
systemctl --user daemon-reload
systemctl --user enable my-container.service
systemctl --user start my-container.service
# Pod를 systemd 서비스로 생성
podman generate systemd --name my-pod --files
# → pod-my-pod.service, container-nginx.service, container-php.service 생성
# 옵션
podman generate systemd --name my-container \
--restart-policy always \
--time 30 \
--new # 시작 시 새 컨테이너 생성 (권장)
11.5 Quadlet: systemd 네이티브 컨테이너 관리
Podman 4.4+에서 도입된 Quadlet은 systemd unit 파일 형식으로 컨테이너를 선언적으로 관리하는 방식이다.
# ~/.config/containers/systemd/webapp.container
[Unit]
Description=My Web Application
After=network-online.target
[Container]
Image=docker.io/library/nginx:latest
ContainerName=webapp
PublishPort=8080:80
Volume=webapp-data:/usr/share/nginx/html:ro
Environment=NGINX_WORKER_PROCESSES=auto
AutoUpdate=registry
HealthCmd=curl -f http://localhost/ || exit 1
HealthInterval=30s
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
# Quadlet 파일 배치 후 적용
# 시스템 레벨: /etc/containers/systemd/
# 사용자 레벨: ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start webapp.service
systemctl --user enable webapp.service
systemctl --user status webapp.service
# 로그 확인
journalctl --user -u webapp.service -f
11.6 Rootless Containers 상세
Podman의 rootless 컨테이너는 User Namespace를 활용하여 컨테이너 내부의 root(UID 0)를 호스트의 일반 사용자 UID로 매핑한다.
# 현재 사용자의 UID 매핑 확인
cat /etc/subuid
# user1:100000:65536
# → user1은 호스트 UID 100000~165535를 컨테이너 내부 UID로 사용
cat /etc/subgid
# user1:100000:65536
# 매핑 구조:
# 컨테이너 UID 0 (root) → 호스트 UID 100000 (일반 사용자)
# 컨테이너 UID 1 → 호스트 UID 100001
# 컨테이너 UID 65535 → 호스트 UID 165535
# rootless 상태 확인
podman info --format '{{.Host.Security.Rootless}}'
# true
# rootless 모드에서의 제약 사항 확인
podman info | grep -A 5 rootless
# rootless 컨테이너 실행 (기본값)
podman run -d --name rootless-nginx -p 8080:80 nginx
# 호스트에서 프로세스 확인 — root가 아닌 일반 사용자로 실행됨
ps aux | grep nginx
# user1 12345 ... nginx: master process
12. 보안 관련 명령어 및 설정
12.1 Rootless 모드 설정
# ============================================
# Docker Rootless 모드 설정
# ============================================
# rootless Docker 설치
curl -fsSL https://get.docker.com/rootless | sh
# 환경 변수 설정 (~/.bashrc)
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
# rootless Docker 시작
systemctl --user start docker
systemctl --user enable docker
# ============================================
# Podman Rootless 모드 설정 (기본 활성화)
# ============================================
# subuid/subgid 설정 확인
grep $USER /etc/subuid /etc/subgid
# 설정이 없는 경우 추가
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
# 네임스페이스 마이그레이션 (기존 이미지 재매핑)
podman system migrate
12.2 Security Options
# ============================================
# Capability 관리
# ============================================
# 모든 capability 제거 후 필요한 것만 추가 (최소 권한 원칙)
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
podman run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
# 기본 capability 확인
docker run --rm alpine cat /proc/1/status | grep Cap
podman run --rm alpine cat /proc/1/status | grep Cap
# 주요 Capability 목록:
# NET_BIND_SERVICE : 1024 이하 포트 바인딩
# SYS_PTRACE : 프로세스 디버깅 (strace 등)
# NET_RAW : RAW 소켓 사용 (ping 등)
# CHOWN : 파일 소유권 변경
# DAC_OVERRIDE : 파일 권한 무시
# SETUID/SETGID : UID/GID 변경
# ============================================
# Security Options
# ============================================
# no-new-privileges: execve 시 권한 상승 방지
docker run --security-opt no-new-privileges:true myapp
podman run --security-opt no-new-privileges:true myapp
# Seccomp 프로파일 적용
docker run --security-opt seccomp=./custom-seccomp.json myapp
podman run --security-opt seccomp=./custom-seccomp.json myapp
# AppArmor 프로파일 (Docker/Ubuntu)
docker run --security-opt apparmor=docker-default myapp
# SELinux 라벨 (Podman/RHEL)
podman run --security-opt label=type:container_t myapp
podman run --security-opt label=disable myapp # SELinux 비활성화
12.3 읽기 전용 컨테이너
# 파일 시스템을 읽기 전용으로 설정
docker run --read-only nginx
podman run --read-only nginx
# 쓰기가 필요한 디렉토리만 tmpfs로 마운트
docker run --read-only \
--tmpfs /tmp \
--tmpfs /var/run \
--tmpfs /var/cache/nginx \
nginx
podman run --read-only \
--tmpfs /tmp \
--tmpfs /var/run \
--tmpfs /var/cache/nginx \
nginx
12.4 이미지 보안 검증
# ============================================
# Docker Content Trust (DCT)
# ============================================
# 서명된 이미지만 풀/실행 허용
export DOCKER_CONTENT_TRUST=1
docker pull nginx:latest # 서명 검증됨
# 이미지 서명
docker trust sign myregistry.com/myapp:v1.0
# 서명 확인
docker trust inspect --pretty myregistry.com/myapp:v1.0
# ============================================
# Podman 이미지 서명 (GPG 기반)
# ============================================
# 서명 정책 파일 확인
cat /etc/containers/policy.json
# GPG 키로 이미지 서명
podman push --sign-by security@example.com myregistry.com/myapp:v1.0
# 서명 정책 설정 예시 (/etc/containers/policy.json)
# {
# "default": [{"type": "reject"}],
# "transports": {
# "docker": {
# "myregistry.com": [
# {
# "type": "signedBy",
# "keyType": "GPGKeys",
# "keyPath": "/etc/pki/rpm-gpg/RPM-GPG-KEY-myorg"
# }
# ],
# "docker.io": [{"type": "insecureAcceptAnything"}]
# }
# }
# }
# Skopeo를 이용한 이미지 무결성 검증
skopeo inspect --raw docker://myregistry.com/myapp:v1.0 | jq .
12.5 보안 베스트 프랙티스 체크리스트
| 항목 | Docker 명령 / 설정 | Podman 명령 / 설정 |
|---|---|---|
| Root 실행 금지 | USER nonroot in Dockerfile | 기본 rootless |
| Capability 최소화 | --cap-drop ALL --cap-add ... | --cap-drop ALL --cap-add ... |
| Read-only FS | --read-only | --read-only |
| 권한 상승 방지 | --security-opt no-new-privileges | --security-opt no-new-privileges |
| 리소스 제한 | --memory --cpus --pids-limit | --memory --cpus --pids-limit |
| 네트워크 격리 | --network none / internal network | --network none / internal network |
| 이미지 서명 | Docker Content Trust | GPG signing / sigstore |
| 시크릿 관리 | Docker Secrets / 환경 변수 | Podman secrets / 환경 변수 |
| 베이스 이미지 | distroless / scratch / alpine | distroless / scratch / alpine |
| 이미지 스캔 | docker scout / Trivy | Trivy / Grype |
13. 시스템 관리 및 정리
13.1 디스크 사용량 확인 (system df)
# 디스크 사용량 요약
docker system df
podman system df
# 상세 정보
docker system df -v
podman system df -v
# 출력 예시:
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
# Images 15 5 4.2GB 2.8GB (66%)
# Containers 8 3 120MB 80MB (66%)
# Local Volumes 10 4 1.5GB 800MB (53%)
# Build Cache 20 0 500MB 500MB (100%)
13.2 전체 정리 (system prune)
# 중지된 컨테이너 + dangling 이미지 + 미사용 네트워크 + 빌드 캐시 정리
docker system prune
podman system prune
# 확인 프롬프트 없이 실행
docker system prune -f
podman system prune -f
# 미사용 이미지까지 포함 (주의!)
docker system prune -a -f
podman system prune -a -f
# 볼륨까지 포함 (매우 주의! 데이터 손실 가능)
docker system prune -a --volumes -f
podman system prune -a --volumes -f
# 특정 기간 이상 된 리소스만 정리
docker system prune -a --filter "until=720h" -f # 30일 이상
13.3 시스템 정보 및 버전
# 시스템 전체 정보
docker info
podman info
# 주요 확인 항목:
# - Storage Driver
# - Cgroup Version (v1/v2)
# - Security Options
# - Kernel Version
# - OS/Architecture
# - Registry 설정
# 버전 정보
docker version
podman version
# 클라이언트/서버 버전 개별 확인
docker version --format '{{.Client.Version}}'
docker version --format '{{.Server.Version}}'
podman version --format '{{.Client.Version}}'
14. 실전 치트시트: 자주 쓰는 명령어 30선
아래는 컨테이너 운영에서 가장 빈번하게 사용하는 30가지 명령어를 한눈에 정리한 테이블이다. Docker와 Podman 모두 동일한 문법을 사용한다.
| # | 작업 | 명령어 |
|---|---|---|
| 1 | 이미지 다운로드 | docker pull nginx:latest |
| 2 | 이미지 목록 | docker images |
| 3 | 이미지 삭제 | docker rmi nginx:latest |
| 4 | Dangling 이미지 정리 | docker image prune -f |
| 5 | 이미지 빌드 | docker build -t myapp:latest . |
| 6 | 컨테이너 실행 (백그라운드) | docker run -d --name web -p 80:80 nginx |
| 7 | 컨테이너 실행 (인터랙티브) | docker run -it --rm alpine sh |
| 8 | 실행 중 컨테이너 목록 | docker ps |
| 9 | 모든 컨테이너 목록 | docker ps -a |
| 10 | 컨테이너 중지 | docker stop web |
| 11 | 컨테이너 시작 | docker start web |
| 12 | 컨테이너 재시작 | docker restart web |
| 13 | 컨테이너 삭제 | docker rm web |
| 14 | 컨테이너 강제 삭제 | docker rm -f web |
| 15 | 모든 중지 컨테이너 삭제 | docker container prune -f |
| 16 | 컨테이너 로그 | docker logs -f --tail 100 web |
| 17 | 컨테이너 접속 (exec) | docker exec -it web bash |
| 18 | 파일 복사 (호스트→컨테이너) | docker cp file.txt web:/app/ |
| 19 | 파일 복사 (컨테이너→호스트) | docker cp web:/app/log.txt ./ |
| 20 | 리소스 모니터링 | docker stats |
| 21 | 상세 정보 (JSON) | docker inspect web |
| 22 | 네트워크 생성 | docker network create my-net |
| 23 | 네트워크 목록 | docker network ls |
| 24 | 볼륨 생성 | docker volume create my-vol |
| 25 | 볼륨 목록 | docker volume ls |
| 26 | Compose 서비스 시작 | docker compose up -d |
| 27 | Compose 서비스 중지 | docker compose down |
| 28 | 레지스트리 로그인 | docker login registry.example.com |
| 29 | 이미지 푸시 | docker push myregistry.com/app:v1 |
| 30 | 전체 시스템 정리 | docker system prune -a -f |
Tip: 위 모든 명령어에서
docker를podman으로 바꾸면 그대로 동작한다.
15. 트러블슈팅 가이드
15.1 권한 관련 문제 (Permission Issues)
Docker: "permission denied while trying to connect to the Docker daemon socket"
# 원인: 현재 사용자가 docker 그룹에 속하지 않음
# 해결:
sudo usermod -aG docker $USER
newgrp docker # 또는 재로그인
# 확인
groups $USER
docker ps # 에러 없이 동작해야 함
Podman Rootless: "Error: could not get runtime: cannot re-exec process"
# 원인: subuid/subgid 미설정
# 해결:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
# 네임스페이스 마이그레이션
podman system migrate
# 확인
podman unshare cat /proc/self/uid_map
Podman Rootless: 볼륨 마운트 퍼미션 문제
# 원인: 호스트와 컨테이너의 UID 매핑 불일치
# 방법 1: unshare로 소유권 변경
podman unshare chown 1000:1000 /host/path/data
# 방법 2: :U 옵션으로 자동 UID 매핑 (Podman 4.0+)
podman run -v /host/data:/data:U myapp
# 방법 3: SELinux 라벨 추가 (RHEL/CentOS)
podman run -v /host/data:/data:Z myapp
15.2 네트워크 연결 문제
컨테이너에서 외부 네트워크 접근 불가
# DNS 확인
docker exec my-container cat /etc/resolv.conf
docker exec my-container nslookup google.com
# 해결 1: DNS 서버 직접 지정
docker run --dns 8.8.8.8 --dns 8.8.4.4 myapp
# 해결 2: iptables/nftables 규칙 확인
sudo iptables -L -n -t nat
sudo nft list ruleset
# 해결 3: IP 포워딩 활성화
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
# 영구 설정: /etc/sysctl.conf에 net.ipv4.ip_forward=1 추가
컨테이너 간 이름으로 통신이 안 되는 경우
# 원인: 기본 bridge 네트워크 사용 중 (DNS 해석 미지원)
# 해결: 사용자 정의 네트워크 생성
docker network create my-net
docker run -d --network my-net --name db postgres:16
docker run -d --network my-net --name app \
-e DB_HOST=db myapp # 이제 "db"로 접근 가능
Podman rootless 포트 1024 미만 바인딩 실패
# 원인: rootless 모드에서는 기본적으로 1024 미만 포트 사용 불가
# 해결 1: 1024 이상 포트 사용
podman run -d -p 8080:80 nginx
# 해결 2: sysctl로 unprivileged 포트 범위 변경
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
# 영구 설정: /etc/sysctl.conf
# 해결 3: rootful 모드 사용
sudo podman run -d -p 80:80 nginx
15.3 스토리지/디스크 공간 문제
"no space left on device" 에러
# 디스크 사용량 확인
docker system df -v
podman system df -v
# 단계적 정리:
# 1단계: 중지된 컨테이너 삭제
docker container prune -f
# 2단계: Dangling 이미지 삭제
docker image prune -f
# 3단계: 미사용 볼륨 삭제 (주의: 데이터 확인 후!)
docker volume prune -f
# 4단계: 빌드 캐시 삭제
docker builder prune -f
# 5단계: 전체 정리 (최후의 수단)
docker system prune -a --volumes -f
# 특정 기간 이상 된 이미지만 정리
docker image prune -a --filter "until=720h" -f # 30일 이상
Podman 스토리지 경로 변경
# Rootless Podman의 기본 스토리지 위치
# ~/.local/share/containers/storage/
# 스토리지 경로 변경: ~/.config/containers/storage.conf
# [storage]
# driver = "overlay"
# graphroot = "/mnt/large-disk/containers/storage"
# 변경 후 마이그레이션
podman system reset # 주의: 모든 컨테이너/이미지 삭제됨
15.4 이미지 빌드 실패
Multi-platform 빌드 에러 (Docker)
# QEMU 에뮬레이터 설치 필요
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# buildx 빌더 생성
docker buildx create --name multiarch --use
docker buildx inspect multiarch --bootstrap
# 멀티 플랫폼 빌드
docker buildx build --platform linux/amd64,linux/arm64 \
-t myapp:latest --push .
빌드 컨텍스트가 너무 큰 경우
# .dockerignore 파일 생성
cat > .dockerignore << 'EOF'
.git
node_modules
dist
*.log
.env
.DS_Store
**/*.test.js
**/*.spec.js
coverage
.nyc_output
EOF
# 빌드 컨텍스트 크기 확인
du -sh . --exclude=.git --exclude=node_modules
# 특정 경로만 빌드 컨텍스트로 사용
docker build -f Dockerfile -t myapp . --build-context src=./src
15.5 일반적인 디버깅 패턴
# ============================================
# 즉시 종료되는 컨테이너 디버깅
# ============================================
# 종료 로그 확인
docker logs my-container
docker inspect -f '{{.State.ExitCode}}' my-container
docker inspect -f '{{.State.Error}}' my-container
# entrypoint를 오버라이드하여 쉘로 진입
docker run -it --entrypoint sh myapp:latest
podman run -it --entrypoint sh myapp:latest
# ============================================
# 네트워크 디버깅
# ============================================
# 네트워크 디버깅 전용 컨테이너
docker run --rm -it --network container:target-container \
nicolaka/netshoot bash
# 특정 네트워크에서 디버깅
docker run --rm -it --network my-net nicolaka/netshoot bash
# → nslookup, dig, curl, tcpdump, iperf3, netstat 등 사용 가능
# ============================================
# 파일 시스템 디버깅
# ============================================
# 컨테이너 파일 시스템 변경사항 확인
docker diff my-container
# 컨테이너의 파일 시스템을 tar로 추출하여 분석
docker export my-container | tar -tf - | head -50
# ============================================
# 리소스 제한 확인
# ============================================
# 컨테이너의 cgroup 설정 확인
docker exec my-container cat /sys/fs/cgroup/memory.max
docker exec my-container cat /sys/fs/cgroup/cpu.max
# OOM Killed 확인
docker inspect -f '{{.State.OOMKilled}}' my-container
16. 참고 자료 및 레퍼런스
공식 문서
- Docker 공식 문서: https://docs.docker.com/
- Docker CLI Reference: https://docs.docker.com/engine/reference/commandline/cli/
- Docker Compose Reference: https://docs.docker.com/compose/compose-file/
- Dockerfile Reference: https://docs.docker.com/engine/reference/builder/
- Podman 공식 문서: https://docs.podman.io/
- Podman CLI Reference: https://docs.podman.io/en/latest/Commands.html
- Buildah 공식 문서: https://buildah.io/
- Skopeo 공식 문서: https://github.com/containers/skopeo
OCI 표준
- OCI Image Spec: https://github.com/opencontainers/image-spec
- OCI Runtime Spec: https://github.com/opencontainers/runtime-spec
- OCI Distribution Spec: https://github.com/opencontainers/distribution-spec
보안 가이드
- CIS Docker Benchmark: https://www.cisecurity.org/benchmark/docker
- NIST Container Security Guide (SP 800-190): https://csrc.nist.gov/publications/detail/sp/800-190/final
- Docker Security Best Practices: https://docs.docker.com/engine/security/
- Podman Rootless Tutorial: https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md
관련 프로젝트
- containerd: https://containerd.io/
- CRI-O: https://cri-o.io/
- Netavark: https://github.com/containers/netavark
- Quadlet: https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
- Podman Desktop: https://podman-desktop.io/