Docker를 버린 지 18개월, runc 권고안을 끝까지 읽고서야 알았습니다
작년 11월 5일 아침이었습니다. 사내 보안 채널에 링크 하나가 올라왔습니다. runc 권고안 세 건. CVE-2025-31133, CVE-2025-52565, CVE-2025-52881. 셋 다 도착지가 같습니다. 공격자가 호스트의 /proc에 읽고 쓰는 경로를 손에 넣습니다.
처음 든 감정은 솔직히 안도였습니다. 우쭐함에 가까웠고요. 저희는 18개월 전에 Docker를 걷어냈거든요. 빌드 호스트 전부에 Podman을 깔았고, 가능한 곳은 루트 없이 돌렸고, 루트 소유 데몬 소켓이 잠금 풀린 문처럼 방치되는 상황을 없앴습니다. 그날 오전 열한 시 스탠드업에서 저는 "저희는 해당 없습니다"라는 말을 한 번 했습니다. 그 말이 틀렸다는 걸 아는 데 두 시간 걸렸습니다.
권고안 본문을 끝까지 읽었습니다. runc 관리자들이 crun과 youki에도 같은 계열의 문제가 있을 가능성이 높고, 패치를 함께 배포한다고 적어뒀더라고요. Fedora 호스트에서 Podman은 crun을 부릅니다. 저는 취약점 바로 위 계층을 갈아치워 놓고, 그 아래 계층까지 같이 고쳐졌다고 조용히 믿고 있었던 겁니다. 그 믿음에 근거가 하나도 없었다는 게 그날의 수확이었습니다.
그 주에 전 호스트 패치했습니다. Docker 쓰는 팀들과 똑같은 일정으로, 똑같은 마음으로요.

그런데 왜 다들 갈아타고 있나
이주 행렬은 실재합니다. 이유는 시시합니다. 돈입니다.
Docker Desktop은 개인, 교육기관, 비상업 오픈소스, 그리고 직원 250명 미만이면서 연 매출 1천만 달러 미만인 회사에 무료입니다. 이 선을 하나라도 넘으면 개발자 전원이 라이선스를 삽니다. 2026년 8월 기준 공식 가격표는 Pro가 연간 청구 시 사용자당 월 9달러(월 청구는 11달러), Team이 15달러(월 청구 16달러), Business가 24달러입니다. 엔지니어 60명짜리 조직이 Business를 쓰면 로컬 개발 도구 하나에 연 17,000달러가 조금 넘게 나갑니다.
숫자를 처음 보고 제가 한 계산은 좀 생활인다웠습니다. 17,000달러면 CI 러너를 몇 대 더 세우나, 아니면 사람 하나 뽑는 예산에 얼마나 보태지나. 계산이 끝나기도 전에 결론은 이미 나 있습니다. 이런 건 보안팀이 아니라 조달팀이 먼저 움직입니다.
자주 헷갈리는 지점 하나. 이건 Docker Desktop 라이선스 얘기지 Docker 자체 얘기가 아닙니다. Linux용 Docker Engine은 여전히 Apache 2.0입니다. CI 러너가 Linux 위에 얹혀 있다면, 애초에 돈을 낸 대상이 그게 아닙니다.
실제로 교체되는 건 어디까지인가
"Docker 대체"라는 말이 Docker가 얼마나 두꺼운지를 감춥니다. 위에서 아래로 대충 늘어놓으면 docker CLI, Compose, BuildKit, dockerd 데몬, containerd, 그리고 OCI 런타임인 runc나 crun입니다.
후보 목록에 오르는 거의 모든 도구는 위쪽 넷을 바꾸고 아래쪽 둘을 그대로 둡니다. 2024년에 누가 이 문장 하나만 제 앞에 놔줬으면 저는 반나절이 아니라 한 분기를 아꼈을 겁니다.
Docker도 같은 바닥을 향해 걸어가는 중입니다. Docker Engine v29는 새로 설치할 때 containerd 이미지 저장소를 기본값으로 잡고, 예전 그래프 드라이버는 완전히 접었습니다. 위쪽에서 싸우던 사람들이 아래쪽에서 만나는 그림입니다.
옮기기 전에 감사부터
마이그레이션이 보안 태세를 바꿨다는 말을 누가 꺼내기 전에, 지금 저희가 돌리는 스크립트를 먼저 놓겠습니다.
#!/usr/bin/env bash
# runtime-audit.sh — 이 컨테이너를 실제로 돌리는 건 무엇인가
# bash 5.x 기준. Docker Engine 29.6.2, Podman 6.0.2,
# nerdctl 2.3.0 (Ubuntu 24.04)에서 확인.
set -uo pipefail
row() { printf '%-10s %-14s %s\n' "$1" "$2" "$3"; }
row TOOL VERSION "LOW-LEVEL RUNTIME"
printf '%.0s-' {1..50}; echo
if command -v docker >/dev/null 2>&1; then
row docker \
"$(docker version --format '{{.Server.Version}}' 2>/dev/null || echo unavailable)" \
"$(docker info --format '{{.DefaultRuntime}}' 2>/dev/null || echo unavailable)"
fi
if command -v podman >/dev/null 2>&1; then
row podman \
"$(podman version --format '{{.Client.Version}}' 2>/dev/null || echo unavailable)" \
"$(podman info --format '{{.Host.OCIRuntime.Name}}' 2>/dev/null || echo unavailable)"
fi
if command -v nerdctl >/dev/null 2>&1; then
row nerdctl \
"$(nerdctl version --format '{{.Client.Version}}' 2>/dev/null || echo unavailable)" \
"set in /etc/containerd/config.toml"
fi
echo
echo "runtime binaries on this host:"
for rt in runc crun youki; do
if command -v "$rt" >/dev/null 2>&1; then
printf ' %-6s %s\n' "$rt" "$("$rt" --version 2>/dev/null | head -n1)"
fi
done
containerd 호스트라면 논쟁이 설정 한 줄에서 끝납니다. CRI 공식 설정 가이드(containerd 2.x)에서 가져온 조각입니다.
version = 3
[plugins."io.containerd.cri.v1.runtime".containerd]
default_runtime_name = "crun"
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.crun]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.crun.options]
BinaryName = "/usr/local/bin/crun"
컨테이너를 실제로 움직이는 건 저 BinaryName 한 줄입니다. 그 위쪽은 전부 손에 익는 감각, 패키징, 라이선스 문제고요.
후보 여섯
| 후보 | 최신 | 라이선스 | 걸리는 지점 |
|---|---|---|---|
| Podman | 6.0 (2026-06-24) | Apache 2.0 | 6.0에서 CNI·cgroups v1 제거 |
| containerd + nerdctl | nerdctl 2.3.0 (2026-05) | Apache 2.0 | 배터리 미포함, 직접 조립 |
| Apple container | 1.0.0 (2026-06-09) | Apache 2.0 | Apple Silicon + macOS 26 전용 |
| OrbStack | 상용 | 비공개, 월 8달러 | macOS 전용, 라이선스를 라이선스로 교환 |
| Rancher Desktop / Colima | 1.23.1 (2026-07) / 0.10.0 (2026-02) | 오픈소스 | 래퍼, 디버깅이 아래 계층으로 내려감 |
| Buildah / BuildKit | — | 오픈소스 | 데스크톱 문제를 풀지 않음 |
Podman 6.0
라이선스 때문에 Docker Desktop을 내리는 팀에게 기본값처럼 주어지는 답입니다. 6.0은 2026년 6월 24일에 나왔습니다.
좋은 쪽. 오래 떠 있는 루트 데몬이 없습니다. 루트리스가 기본 동작 방식이지 나중에 손보는 튜닝 항목이 아닙니다. Quadlet은 컨테이너를 진짜 systemd 유닛으로 만들어주는데, systemd를 이미 관리하는 조직이라면 이게 생각보다 큽니다. 유닛 파일 하나로 재시작·의존성·로그가 원래 쓰던 자리로 들어옵니다. Docker 호환 API 소켓도 제공해서 docker compose가 손 안 대고 그대로 돕니다.
# Podman 6.x, systemd 호스트에서 루트리스로.
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR}/podman/podman.sock"
loginctl enable-linger "$USER" # 로그아웃해도 소켓 유지
docker compose up -d
나쁜 쪽. 6은 대대적인 정리 버전입니다. CNI, iptables, slirp4netns, cgroups v1, BoltDB, Intel Mac, Windows 10이 한꺼번에 빠졌습니다. 가장 고약한 건 남아 있는 CNI 설정을 오류로 뱉지 않고 조용히 무시한다는 점입니다. 컨테이너는 멀쩡히 뜨는데 네트워크가 없습니다. 새벽에 이 증상 만나면 원인 찾는 데 한 시간은 그냥 날아갑니다. 호환 계층이 Docker v1.40 API를 겨냥하고 있어서 별난 Compose 기능은 어긋나기도 하고요.
Linux에 cgroups v2와 systemd를 쓰고 있다면 고르시고, CNI나 cgroups v1 위에 있다면 이번엔 건너뛰고 5.x에 머무는 편이 낫습니다.
containerd + nerdctl
정직한 선택은 운영에서 이미 돌리고 있는 걸 그대로 개발에 가져오는 겁니다. nerdctl 2.3.0은 2026년 5월에 나왔습니다.
containerd는 EKS, GKE, AKS가 돌리는 그 물건입니다. nerdctl은 Docker CLI와 거의 같은 손맛이라 적응 비용이 낮고, Compose와 루트리스, 지연 로딩 스냅샷까지 붙습니다. 대신 배터리가 없습니다. CNI, BuildKit, 스냅샷 설정을 직접 세웁니다.
그리고 containerd에도 자기 몫의 CVE가 있습니다. 2026년 6월에 1.7.33, 2.0.10, 2.1.9, 2.2.5, 2.3.2에서 CRI 플러그인 취약점 5건이 패치됐고, 그중 하나는 이미지의 LABEL이 호스트 명령 실행을 부르는 8.3점짜리였습니다. 아래로 내려간다고 조용해지지 않습니다.
개발과 운영의 거리를 좁히는 게 목표라면 고르시고, 팀에 인프라를 직접 조립하고 싶은 사람이 아무도 없다면 건너뛰는 편이 낫습니다.
맥 쪽 두 갈래
Apple의 container는 진짜 신참입니다. 1.0.0이 2026년 6월 9일에 Apache 2.0으로 나왔고, WWDC 2025 프리뷰에서 딱 1년 걸렸습니다. 컨테이너마다 Virtualization.framework로 경량 VM을 하나씩 띄우니 워크로드끼리 커널을 나눠 쓰지 않습니다. 보안 담당자 눈으로 보면 이 격리 경계는 네임스페이스와 질적으로 다른 물건입니다. 양방향 OCI 호환에 유휴 데몬도 없고, 1.0에서 container machine으로 영구 Linux 환경까지 붙었습니다. 다만 Apple Silicon과 macOS 26에서만 돕니다. 이건 제약이라기보다 벽입니다. 생태계는 이제 한 살이고 Linux CI 쪽은 아직 손이 갑니다.
OrbStack은 실용적인 맥 선택이고, 동시에 사람들이 은근히 투덜대는 그 선택입니다. 실제 Docker Engine과 Compose를 돌리니 워크플로가 하나도 안 바뀝니다. 제가 직접 써보니 Apple Silicon에서 시작 속도와 파일 공유 성능이 Docker Desktop보다 눈에 띄게 나았습니다. 정확한 벤치 수치는 공개 안 하겠습니다만, 차이가 확연해서 아무도 돌아가겠다는 사람이 없었습니다. 문제는 라이선스를 라이선스로 바꾸는 거래라는 점입니다. 개인 비상업 용도만 무료, 그것도 관련 수익 연 1만 달러 상한이 붙습니다. 상업 사용은 사용자당 월 8달러. macOS 전용에 소스도 닫혀 있습니다. 8달러가 24달러보다 싸다는 계산만 성립하면 되는 자리고, "오픈소스여야 한다"가 조건에 박혀 있으면 그 자리에서 탈락입니다.
자유로운 중간지대도 있습니다. Rancher Desktop 1.23.1이 2026년 7월, Colima 0.10.0이 2026년 2월에 나왔습니다. 둘 다 오픈소스에 상업 사용 무료고, Rancher Desktop은 k3s를 품고 있어서 별도 도구 없이 단일 노드 클러스터가 섭니다. containerd와 dockerd를 오가는 스위치도 붙어 있고요. Colima는 Lima 위에 얹힌 간결하고 스크립트하기 좋은 CLI입니다. 대신 둘 다 래퍼입니다. 터지면 버그는 대개 Lima나 containerd, VM 계층에 있고 디버깅은 스택 아래로 계속 내려갑니다. Rancher Desktop은 Electron 기반이라 그 특유의 무게감이 있고, Colima는 GUI가 없어서 설정 파일과 친해지는 수밖에 없습니다.

CI 쪽은 아예 다른 문제입니다
"도커 대안이 필요합니다"라는 말의 절반은 사실 "쿠버네티스 러너에서 privileged Docker-in-Docker를 못 돌리겠습니다"입니다. 이건 데스크톱 얘기가 아닙니다.
Buildah와 BuildKit은 둘 다 관리자 권한 데몬 없이 OCI 이미지를 굽습니다. Buildah는 Podman과 궁합이 좋고 루트 없이 빌드합니다. BuildKit은 docker buildx가 이미 쓰고 있는 엔진이라 캐시 의미 체계와 멀티 플랫폼 빌드가 그대로 따라옵니다.
여기서 진짜 신경 쓸 건 성능이 아니라 유지보수 상태입니다. 구글이 2025년 6월 3일에 Kaniko를 아카이빙했습니다. Chainguard가 포크하기 전까지, 규제 산업 파이프라인에서 뼈대 노릇 하던 도구가 그냥 방치돼 있었습니다. 유지보수가 끊긴 빌더를 고르는 건 천천히 진행되는 CVE 사고입니다. 터지는 날짜만 아직 안 정해진 거죠.
이 얘기가 안 맞는 자리
직원 250명 미만에 매출 1천만 달러 미만이면 Docker Desktop은 무료입니다. 이 마이그레이션은 안 해도 되는 일입니다. 그 시간과 돈은 다른 데 쓰는 편이 낫습니다.
팀이 Mac과 Windows와 Linux를 섞어 쓰고 있으면 OrbStack과 Apple container는 그 자리에서 목록에서 빠집니다. 표준화가 도구 성능보다 위입니다. 이건 제 편견인데, 개발 환경이 사람마다 다른 조직에서 새벽 장애 원인 찾아본 사람은 대체로 같은 편견을 갖게 되더라고요.
TestContainers나 별난 Compose 기능을 많이 쓴다면 호환성 테스트에 진짜 시간을 잡아두는 편이 낫습니다. API 호환 계층은 훌륭하지만 완벽하지는 않습니다. 어떤 팀이 Compose의 예외 케이스 하나 때문에 2주를 태우는 걸 봤습니다. 사람당 9달러를 아끼려다가요. 2주치 인건비 계산은 아무도 안 했습니다.
보안이 1순위라면, 런타임 이름을 바꾸는 것만으로 달라지는 건 거의 없습니다. Docker Desktop에도 심각한 컨테이너 탈출 취약점이 있었습니다. CVE-2025-9074, CVSS 9.3, 2025년 8월 4.44.3에서 패치됐습니다. runc도 crun도 containerd도 마찬가지였고요. 루트리스와 VM 경계는 실질적인 개선입니다. CLI를 갈아타는 건 아닙니다.

제가 지금 지키는 순서
옮기기 전에 감사부터 합니다. docker info --format '{{.DefaultRuntime}}'와 podman info --format '{{.Host.OCIRuntime.Name}}'를 나란히 돌려서 둘 다 runc 계열 바이너리를 뱉으면, 이번 이사는 라이선스 이사지 보안 이사가 아닙니다. 라이선스와 보안은 같은 문서에 섞지 않습니다. Docker Desktop 가격은 답이 정해진 조달 문제고, 컨테이너 탈출은 CLI 교체로 안 풀리는 구조 문제입니다.
Linux에서는 Podman이 안전한 기본값이지만 6.0은 호환성이 깨지는 업그레이드라 배포 전에 CNI 설정을 훑습니다. 개발과 운영을 가장 가깝게 붙이는 건 containerd와 nerdctl 조합이고, 대가는 인프라를 직접 세운다는 것입니다. 격리는 도구 이름이 아니라 경계에서 옵니다. 루트리스, 유저 네임스페이스, 아니면 Apple container처럼 컨테이너당 VM. 그리고 빌드 경로 위에 놓인 모든 것의 유지보수 상태를 확인합니다. Kaniko는 문제가 터지기 직전까지 멀쩡해 보였습니다.
작년 11월 그 아침에 스탠드업에서 뱉은 "저희는 해당 없습니다"라는 문장, 아직도 가끔 떠오릅니다. 그 말을 안 했으면 권고안을 더 빨리 끝까지 읽었을까요. 잘 모르겠습니다. 지금도.
이 글이 도움이 되셨나요?
버튼 하나가 다음 글을 쓰는 힘이 됩니다