Apple container의 Container Machine: Docker Desktop 없이 맥에서 진짜 리눅스 환경 굴리기
맥북을 개발 머신으로 쓰는 백엔드/인프라 엔지니어라면 한 번쯤 고민해봤을 거다. "Docker Desktop 라이선스도 신경 쓰이고, 리소스도 많이 먹고… 대안이 뭐가 있나." Lima, Colima, OrbStack로 갈아탄 사람도 많을 거다. 그런데 작년부터 Apple이 직접 apple/container 프로젝트를 GitHub에 올리면서 판이 좀 달라졌다. 이번 글에서는 그중에서도 최근 Hacker News에서 화제가 된 Container Machine 문서를 실무자 시선으로 뜯어본다.
먼저 분명히 해두자. 이 글은 공식 문서(container-machine.md)를 기준으로 쓴다. 문서에 없는 벤치마크 수치나 버전별 동작은 함부로 단정하지 않고, 확인이 필요한 부분은 그렇게 표시한다.
1. 왜 지금 화제이고 어떤 문제를 푸는가
우리가 흔히 쓰는 컨테이너는 "애플리케이션 하나"를 모델로 한다. nginx 이미지, postgres 이미지처럼 프로세스 하나 띄우고 끝나는 구조다. 그런데 실무에서 의외로 자주 필요한 건 그게 아니다. "리눅스 환경 자체"가 필요할 때가 있다.
- 로컬은 맥인데, 빌드와 실행은 리눅스에서 검증하고 싶을 때
systemd기반으로 돌아가는 서비스(예: postgresql, redis를 시스템 서비스로)를 통째로 테스트하고 싶을 때- alpine, ubuntu, debian 등 타겟 배포판별로 동작을 확인해야 할 때
Container Machine은 바로 이 지점을 노린다. 공식 문서의 표현을 빌리면 "A container machine is modeled after a Linux environment". 즉 앱 하나가 아니라 init 시스템까지 포함한 리눅스 환경 전체를 가볍고 영구적인(persistent) 형태로 맥 위에서 돌리는 게 목표다.
Docker Desktop이나 Lima와의 가장 큰 차이는 호스트 통합이다. 문서가 강조하는 핵심 컨셉이 "Edit on the Mac, build inside"인데, 풀어보면 이렇다.
- 내 맥 홈 디렉토리($HOME)에 있는 레포가 컨테이너 머신 안에서도 그대로 보인다.
- 맥 계정 username과 home 디렉토리가 자동으로 리눅스 환경에 매핑된다.
whoami를 쳐도 root가 아니라 내 호스트 계정 이름이 나온다. - 맥에서 VS Code로 코드를 짜고, 빌드/실행은 리눅스 안에서, 그리고 결과물은 다시 맥 네이티브 도구(브라우저, 프로파일러 등)로 들여다본다. 복사 단계가 없다.
OrbStack을 써본 사람이라면 "어? 그거 OrbStack도 비슷하게 해주는데?"라고 할 거다. 맞다. 컨셉은 OrbStack의 머신 기능과 상당히 겹친다. 차이라면 이건 Apple이 직접 관리하는 오픈소스이고, 애초에 Apple Silicon + Virtualization.framework 위에서 돌도록 설계됐다는 점이다.
2. 동작 원리: 명령어로 직접 보기
백문이 불여일견. Quickstart부터 따라가 보자. 문서 기준 가장 기본 흐름이다.
# alpine 기반 컨테이너 머신을 'dev'라는 이름으로 생성
container machine create alpine:latest --name dev
# 컨테이너 머신 안에서 whoami 실행
container machine run -n dev whoami
여기서 핵심은 출력이다. 일반 컨테이너에서 whoami를 치면 보통 root가 나오는데, 컨테이너 머신은 다르다.
$ container machine run -n dev whoami
your-mac-username # root가 아니라 호스트 맥 계정 이름
$ container machine run -n dev pwd
/home/your-mac-username # 맥 홈 디렉토리가 마운트된 위치
이게 바로 "username과 home 디렉토리 자동 매핑"이 동작하는 모습이다. 인자 없이 container machine run -n dev만 치면 인터랙티브 셸로 들어가고, 이 안에서 내 레포 디렉토리로 cd하면 맥에서 작업하던 파일이 그대로 보인다.
명령 단위로 한 번만 실행하고 빠지고 싶으면 이렇게 쓴다. -- 뒤에 붙이는 형태도 지원한다.
$ container machine run -n dev uname -a
Linux dev 6.x.x ... aarch64 GNU/Linux
$ container machine run -n dev -- cat /proc/cpuinfo
매번 -n dev를 붙이기 귀찮으면 기본 머신을 지정해두면 된다. 그리고 container machine은 m이라는 짧은 별칭이 있어서 m ls, m run처럼 쓸 수 있다.
container machine set-default dev
container machine run # 이제 -n 없이도 dev에서 동작
# 별칭 사용
m ls # 모든 컨테이너 머신 목록
m inspect dev # dev의 JSON 상세 정보
m stop dev # 중지
m rm dev # 영구 스토리지까지 포함해 삭제
격리 모델은 어떻게 되나
여기서부터는 문서에 명시된 범위와 합리적 추론을 구분해서 말하겠다. Apple의 container 프로젝트는 일반적으로 각 컨테이너를 경량 VM 위에서 격리하는 구조로 알려져 있고, 이는 Virtualization.framework를 기반으로 한다. 컨테이너 머신 역시 init 시스템(/sbin/init)을 직접 부팅한다는 점에서 단순 namespace 격리가 아니라 VM 경계를 가진 풀 리눅스 환경에 가깝다.
다만 "컨테이너마다 별도 VM인지, 머신마다 하나의 VM인지"의 구체적인 구현 디테일은 이 container-machine.md 문서만으로는 단정하기 어렵다. 실제 격리 단위와 커널 공유 여부는 프로젝트 상위 문서 확인이 필요하다. 다만 실무 체감상 중요한 건, init/systemd를 띄울 수 있다는 사실 자체다. 문서에도 이렇게 적혀 있다.
Run a database or whatever your stack needs as a system service —
systemctl start postgresqlworks on images with systemd installed.
일반 컨테이너에서 systemd 띄우려고 --privileged에 cgroup 마운트 삽질해본 사람이라면, 이게 기본으로 된다는 게 얼마나 편한지 알 거다.
3. 실무 관점: 고려사항, 트레이드오프, 흔한 함정
리소스 설정 — 메모리 기본값이 호스트의 절반이다
이거 모르고 쓰면 맥 전체가 버벅인다. 문서에 명시되어 있다. "Memory defaults to half of host memory." 16GB 맥북이면 컨테이너 머신 하나가 기본 8GB를 잡아간다는 얘기다. 여러 머신을 동시에 띄우는 시나리오라면 반드시 조정하자.
# CPU 4개, 메모리 8G로 변경
container machine set -n dev cpus=4 memory=8G
# 설정은 재시작 후 적용된다 — 이 순서가 중요
container machine stop dev
container machine run -n dev -- nproc
흔한 함정 하나: set으로 바꿔놓고 바로 적용 안 됐다고 당황하는 경우. 문서가 분명히 못 박는다. "Changes take effect after the next stop and start." 즉 실행 중인 머신에 즉시 반영되지 않는다. stop → run(또는 start)을 거쳐야 한다. CI 스크립트에서 동적으로 리소스 바꾸려다 이걸로 한참 헤매기 좋다.
볼륨/홈 마운트 모드 — ro로 묶다가 빌드가 깨진다
홈 마운트는 세 가지 모드가 있다. rw(기본), ro, none. 보안이나 실수 방지 목적으로 ro로 걸어두는 건 좋은데, 빌드 산출물을 홈 디렉토리 하위에 쓰는 툴체인을 쓰면 권한 에러로 빌드가 깨진다. 리눅스 안에서 흔히 이런 메시지를 보게 된다.
Read-only file system
error: failed to write to /home/your-username/project/target/...:
Read-only file system (os error 30)
이건 마운트 모드가 ro일 때 전형적으로 만나는 패턴이다. 빌드 캐시나 산출물 디렉토리를 홈 밖(예: 머신 내부 임시 경로)으로 빼거나, 마운트를 rw로 두는 식으로 우회해야 한다.
BYO 이미지 — systemd 없으면 그냥 컨테이너다
문서가 명확히 한 조건이 있다. "Any Linux image that includes /sbin/init works as a container machine." 즉 init이 없는 minimal 이미지를 그대로 쓰면 컨테이너 머신의 장점(서비스 supervisor, systemctl)을 못 쓴다. systemd 깔린 Ubuntu 머신을 직접 빌드하려면 문서의 Dockerfile을 거의 그대로 따라가야 한다.
FROM ubuntu:24.04
ENV container container
RUN apt-get update \
&& apt-get install -y \
dbus systemd openssh-server net-tools iproute2 \
iputils-ping curl wget vim-tiny man sudo \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& yes | unminimize
RUN systemctl set-default multi-user.target
# (이하 hugepages.mount 등 불필요 유닛 mask/disable 생략)
# 이미지 빌드 후 머신으로 생성
container build -t local/ubuntu-machine:latest .
container machine create local/ubuntu-machine:latest --name ubuntu
여기서 알아둘 디테일 하나. 첫 부팅 시 Apple이 기본 setup 스크립트를 돌려서 호스트 계정 매핑 유저를 만든다. 이걸 직접 제어하고 싶으면 이미지에 /etc/machine/create-user.sh 실행 스크립트를 넣으면 된다. root로, 첫 부팅에 한 번만 실행되며 CONTAINER_UID, CONTAINER_GID, CONTAINER_HOME, CONTAINER_USER, CONTAINER_MACHINE_ID 환경변수가 주어진다. 사내 표준 유저 설정(특정 그룹, sudoers 등)을 강제해야 하는 팀이라면 이 훅이 꽤 유용하다.
네트워킹 — 문서 범위 밖이라 단정 금지
개요에서 "네트워킹 구조 분석"을 기대했을 텐데, 솔직하게 말하면 container-machine.md 문서 자체에는 컨테이너 간 통신이나 호스트 브리지 동작에 대한 구체적 서술이 없다. 빌드한 Ubuntu 이미지에 net-tools, iproute2, iputils-ping을 넣는 걸로 보아 네트워크 도구가 동작하는 환경인 건 분명하지만, 구체적인 브리지/포트포워딩 동작 원리는 별도 네트워킹 문서로 확인이 필요하다. 여기서 추측으로 채우면 그게 바로 검색 유입자에게 잘못된 정보를 주는 길이라, 확인 전엔 단정하지 않겠다.
성능 비교 — 실측 수치는 직접 재라
마찬가지로 Docker Desktop / Lima / OrbStack 대비 정량 벤치마크는 이 문서에 없다. 다만 설계상 추론할 수 있는 트레이드오프는 있다.
- 장점 방향: Apple Silicon + Virtualization.framework 네이티브 설계라 ARM 리눅스 환경에서의 오버헤드가 작을 것으로 기대된다. 홈 디렉토리 직접 마운트라 파일 동기화 복사 비용이 없다.
- 비용 방향: 머신마다 init/systemd 풀 부팅 + 기본 메모리 절반 할당이라, "앱 컨테이너 하나만 가볍게"라는 용도엔 과하다.
실제 도입 검토라면 본인 워크로드(예: cargo build, go build, DB 부하 테스트)로 Docker Desktop/OrbStack과 직접 시간을 재서 비교하는 걸 권한다. 일반론으로 "더 빠르다"고 말하는 게 가장 위험하다.
4. 정리: 한 줄 요약과 도입 판단
한 줄 요약: Container Machine은 "앱 컨테이너"가 아니라 "내 맥 홈이 그대로 들어간 영구 리눅스 환경"을 Apple Silicon 위에서 네이티브로 굴리는 도구다.
이런 사람한테 맞다:
- 맥에서 코드 짜고 리눅스에서 빌드/테스트하는 워크플로우가 일상인 백엔드/시스템 개발자
- systemd 기반 서비스(DB 포함)를 로컬에서 통째로 띄워 검증해야 하는 경우
- alpine/ubuntu/debian 멀티 배포판 호환성을 자주 확인하는 라이브러리/CLI 메인테이너
- Docker Desktop 라이선스/리소스 부담에서 벗어날 오픈소스 대안을 찾는 팀
아직 신중해야 할 경우:
- 프로덕션 CI의 핵심 빌드 인프라로 바로 올리기엔, 네트워킹·성능 특성이 공개 문서로 충분히 검증되지 않았다. 로컬 개발 보조부터 시작하는 게 안전하다.
- Intel 맥 사용자. 이 프로젝트는 Apple Silicon 전제다.
- "앱 하나만 가볍게" 돌리는 용도라면 풀 init 부팅과 메모리 절반 기본값이 오히려 부담이다 — 이 경우는 기존 컨테이너 런타임이 낫다.
개인적으론 OrbStack 머신 기능을 잘 쓰던 사람이라면 컨셉 이해가 빠를 거고, "Apple 공식 + 오픈소스"라는 점에서 장기적으로 지켜볼 가치가 충분하다고 본다. 다만 지금 시점에선 메인 개발 환경을 통째로 갈아엎기보다, 별도 머신 하나 만들어서 멀티 배포판 테스트나 systemd 서비스 검증 용도로 먼저 굴려보길 권한다.