SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-27

VPS에서 Podman과 Docker의 실제 차이점 비교

Podman은 데몬 없이 rootless로 컨테이너를 실행합니다. 이로 인해 발생하는 systemd 기반의 Quadlet 설정, 볼륨 소유권 문제, 1024번 미만 포트 바인딩 제한 등 VPS 운영 시 반드시 알아야 할 기술적 차이점을 상세히 설명합니다.

Podman과 Docker의 실제 차이점

Podman과 Docker는 VPS에서 동일한 OCI(Open Container Initiative) 이미지를 실행하므로, 어떤 소프트웨어를 실행할 수 있는지는 선택의 기준이 되지 않습니다. 차이점은 프로세스 모델에 있습니다. Docker는 모든 컨테이너를 소유하는 root 데몬을 실행하며, docker 명령은 데몬에게 작업을 요청하는 작은 클라이언트일 뿐입니다. Podman은 데몬이 없습니다. podman run은 호출한 주체의 자식 프로세스로 컨테이너를 시작하며, 사용자의 권한 없는 계정으로 실행됩니다.

그 외의 모든 사항은 이 하나의 사실에서 비롯됩니다. 자동 시작은 데몬이 아닌 systemd의 역할이 됩니다. 볼륨 소유권은 사용자 네임스페이스를 거치므로, 호스트에서 ls -l로 확인한 소유자와 컨테이너 내부에서 보이는 소유자가 다릅니다. 1024번 미만의 포트는 커널 설정을 변경하기 전까지 바인딩이 거부됩니다. docker CLI(명령줄 인터페이스)는 래퍼를 통해 계속 작동하지만, Docker 소켓을 요구하는 작업이 발생하면 한계에 부딪힙니다.

데몬 없음: 컨테이너를 시작할 때 실제로 실행되는 것

Docker 호스트에서 pstree -a 명령을 실행하면 dockerd 프로세스가 root 권한으로 실행 중이며, 그 옆에 containerd가 있고, 실행 중인 컨테이너마다 containerd-shim-runc-v2이 하나씩 존재함을 확인할 수 있습니다. 사용자의 애플리케이션은 해당 shim의 자식 프로세스이며, shim은 PID 1의 자식 프로세스입니다. 컨테이너를 시작한 셸과 컨테이너 사이에는 아무런 연결 고리가 없습니다. 데몬을 중지하면 해당 호스트의 모든 컨테이너에 대한 제어 평면을 잃게 되며, 기본값인 live-restore 설정이 꺼져 있다면 systemctl restart docker 명령은 컨테이너까지 함께 재시작합니다.

Podman에는 이와 대응하는 프로세스가 없습니다. 컨테이너를 시작하면 컨테이너의 메인 프로세스를 관리하는 conmon(컨테이너 모니터) 프로세스가 하나 생성되며, 이 프로세스는 명령을 실행한 사용자의 소유입니다.

podman run -d --name web -p 8080:80 docker.io/library/caddy:2
ps -o user,pid,ppid,args -C conmon
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080

ps 명령을 실행하면 conmon 프로세스가 root가 아닌 로그인한 사용자 권한으로 실행 중임을 확인할 수 있어야 하며, curl 명령은 200을 출력해야 합니다. 중앙 집중식 서비스가 컨테이너를 소유하지 않으므로, sudo apt upgrade podman 명령을 실행해도 이미 실행 중인 컨테이너는 중지되지 않으며, 특정 컨테이너의 모니터가 충돌하더라도 다른 컨테이너에 영향을 주지 않습니다.

데몬이 없다는 것은 그에 따른 대가도 따릅니다. 재부팅 후 컨테이너를 자동으로 시작해 주는 주체가 없습니다. Docker의 --restart=always는 부팅 시 데몬이 보장하는 기능이지만, Podman은 이를 systemd로 대체합니다. 아래의 quadlet 섹션이 바로 이 역할을 수행합니다.

소켓은 이 이야기의 나머지 절반입니다. /var/run/docker.sock은 root 소유의 API(애플리케이션 프로그래밍 인터페이스) 엔드포인트이며, 이 소켓에 쓰기 권한이 있는 프로세스는 호스트 파일 시스템을 마운트하는 권한 있는 컨테이너를 시작할 수 있습니다. 사용자를 docker 그룹에 추가하는 것은 우회적인 방법으로 root 권한을 부여하는 것과 같으므로, 각 서비스 계정에 필요한 권한만 부여하기 문서를 함께 읽어보는 것이 좋습니다. Podman은 사용자가 명시적으로 요청하지 않는 한 소켓을 노출하지 않으며, 생성된 소켓은 /run/user/<uid>/podman/podman.sock 경로에서 특정 사용자의 소유로 관리됩니다.

Ubuntu 24.04에 Podman 설치 및 rootless 모드 확인

sudo apt update
sudo apt install -y podman uidmap
podman --version
podman info | grep -i rootless

uidmap 패키지는 newuidmap 및 newgidmap을 제공합니다. 이들은 일반 사용자가 하위 ID 범위를 할당받을 수 있게 해주는 setuid 헬퍼이며, 이들이 없으면 rootless 컨테이너가 시작되지 않습니다. podman info 명령은 rootless: true을 출력해야 합니다.

2026년 8월 확인 기준으로 Ubuntu 24.04는 Podman 4.9를, Debian 13은 Podman 5.x를 제공합니다. quadlet 파일은 4.4 이상이 필요하고 .pod quadlet 파일은 5.0이 필요하므로 이 버전 차이는 중요합니다. 업스트림 문서의 예제를 복사하기 전에 podman --version를 실행하십시오.

모든 rootless 사용자는 하위 ID 범위가 필요합니다:

grep "$USER" /etc/subuid /etc/subgid

Ubuntu에서 adduser으로 생성된 사용자는 자동으로 범위를 할당받습니다. useradd -M 또는 구성 도구로 생성된 사용자는 그렇지 않은 경우가 많으며, 이때 다음과 같은 오류가 발생합니다:

Error: cannot find UID/GID for user deploy: no subuid ranges found for user "deploy" in /etc/subuid - check rootless mode in man pages.

범위를 할당한 다음, 새 매핑이 적용되도록 해당 사용자의 스토리지를 재설정하십시오:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrate

첫 실행 시 주의할 점이 하나 더 있습니다. Podman은 기본적으로 Docker Hub를 가정하지 않습니다. 짧은 이미지 이름은 /etc/containers/registries.conf의 unqualified-search-registries를 기준으로 해석되며, 터미널이 연결되지 않은 스크립트에서는 short-name resolution enforced but cannot prompt without a TTY 오류와 함께 pull이 실패합니다. 항상 전체 이름을 작성하십시오. nginx 대신 docker.io/library/nginx:1.27을 사용하십시오.

임대 서버에서 rootless 컨테이너가 실제로 제공하는 이점

rootless 컨테이너는 사용자 네임스페이스(user namespace) 내부에서 실행됩니다. 이는 프로세스에 고유한 사용자 ID 매핑을 부여하는 커널 기능입니다. 네임스페이스 내부에서 컨테이너의 슈퍼유저는 UID(사용자 ID) 0입니다. 하지만 VPS 호스트 외부에서 동일한 프로세스는 일반 로그인 사용자로 인식됩니다. 즉, 컨테이너 내부의 root는 호스트의 root가 아닙니다.

이것이 rootless가 제공하는 실질적인 이점의 전부입니다. root 권한으로 실행되어야 하는 이미지, 원격 코드 실행 취약점이 있는 웹 애플리케이션, 외부에서 UID 0 권한을 요구하는 탈옥(escape) 시도 등 모든 경우에, 공격자는 호스트 시스템의 권한이 아닌 사용자의 비특권 권한만을 획득하게 됩니다. rootless가 커널 버그로부터 사용자를 보호하지는 못하며, 탈옥한 프로세스가 사용자와 동일한 권한으로 실행되므로 사용자가 읽을 수 있는 모든 파일에 접근할 수 있어 개인 파일 보호도 불가능합니다. 격리 단위는 UID 매핑만큼이나 중요합니다. 이는 레지스트리에서 가져온 계층형 이미지보다 작은 머신처럼 관리하는 전체 사용자 공간을 감싸는 FreeBSD jail에서 더 쉽게 확인할 수 있습니다.

Docker도 rootless 모드로 실행할 수 있습니다. dockerd-rootless-setuptool.sh install은 사용자별 데몬을 설정하며, 이 방식은 매우 안정적입니다. 차이점은 기본 설정의 지향점입니다. Podman은 별도의 설정 없이도 rootless를 기본으로 사용하므로, 서비스가 2년 동안 조용히 root 권한으로 실행되는 대신 컨테이너가 80번 포트에 바인딩할 수 없다는 첫 번째 실패를 즉시 마주하게 됩니다.

볼륨 파일의 소유자가 왜 UID 100999로 표시됩니까?

사용자 네임스페이스(user namespace) 때문입니다. 컨테이너의 UID 0은 호스트의 UID로 매핑됩니다. 컨테이너의 UID 1은 subuid 범위의 첫 번째 ID로 매핑되며, 그 이후부터는 순차적으로 증가합니다. 범위가 100000에서 시작한다면, 컨테이너의 UID 1000은 호스트에서 100999로 나타납니다.

mkdir -p "$PWD/data"
podman run --rm --user 1000 -v "$PWD/data:/data" docker.io/library/alpine:3 sh -c 'id -u; touch /data/f'
ls -ln "$PWD/data"

컨테이너 내부에서 출력하면 1000이 나타납니다. 호스트에서 목록을 확인하면 소유자가 100999로 표시되는데, 이는 100000에 1000을 더하고 1을 뺀 값이 100999이기 때문입니다. 이는 오류가 아니며, 일반적인 chown 명령으로는 해결되지 않습니다. 권한이 없는 사용자는 네임스페이스 외부에서 파일 소유권을 변경할 수 없기 때문입니다.

해결 방법은 네 가지입니다.

  • podman unshare chown 1000:1000 "$PWD/data"는 동일한 사용자 네임스페이스 내부에서 chown을 실행하므로, 컨테이너가 인식하는 번호 체계에 따라 소유권이 변경됩니다.
  • -v "$PWD/data:/data:U"는 Podman이 소스 디렉터리의 소유권을 자동으로 수정하도록 합니다. 중요한 데이터가 없는 새 디렉터리에만 사용하십시오.
  • --userns=keep-id은 호스트의 UID를 컨테이너 내부의 동일한 UID로 매핑하여, 새로 생성되는 파일의 소유자가 사용자 본인이 되도록 합니다.
  • -v appdata:/data과 같은 명명된 볼륨(named volume)을 사용하면 이 문제를 피할 수 있습니다. Podman이 자체 저장소 내부에 소유권이 올바르게 설정된 상태로 볼륨을 생성하기 때문입니다.

Docker에서 이 문제를 겪었다면, 이는 한 단계 위에서 발생하는 동일한 문제입니다. 많은 이미지에서 제공하는 PUID 및 PGID 변수는 컨테이너 내부 프로세스가 사용할 UID를 설정하며, 루트리스(rootless) Podman 환경에서는 해당 UID가 한 번 더 매핑됩니다. 루트리스 컨테이너 내부에서 PUID=1000을 실행해도 호스트 파일은 여전히 100999 소유로 생성됩니다. 이 이중 매핑을 고려하여 숫자를 선택하거나, 데이터를 명명된 볼륨으로 옮겨 문제를 해결하십시오.

마운트에 관한 두 가지 추가 참고 사항입니다. Fedora 및 RHEL 예제에서 볼 수 있는 :z 및 :Z 플래그는 SELinux 재레이블 옵션이므로, Ubuntu와 같이 AppArmor를 사용하는 환경에서는 아무런 동작을 하지 않습니다. 또한 루트리스 Podman은 사용자가 읽을 수 없는 호스트 디렉터리를 마운트할 수 없는데, 이는 결함이 아니라 보안을 위한 의도된 동작입니다.

루트리스(rootless) Podman에서 80번 포트 게시가 거부되는 이유는 무엇입니까?

1024번 미만의 포트를 바인딩하려면 일반 사용자에게는 없는 권한이 필요하기 때문입니다. 오류 메시지에 해결 방법이 명시되어 있습니다.

Error: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission denied

두 가지 해결 방법이 있습니다. 호스트 전체의 임계값을 낮추는 방법입니다.

echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system
sysctl net.ipv4.ip_unprivileged_port_start

마지막 명령을 실행하면 80이 출력되어야 합니다. 이 설정의 의미를 명확히 이해해야 합니다. 컨테이너를 실행하는 사용자뿐만 아니라 시스템의 모든 사용자가 80번과 443번 포트를 바인딩할 수 있게 됩니다. 관리자가 한 명인 VPS에서는 허용 가능한 수준이지만, 다른 사용자의 계정이 존재하는 서버에서는 권장하지 않습니다. 다른 방법은 8080번 포트로 게시하고 앞에 리버스 프록시를 두는 것입니다. 이 방식이 certbot을 사용하여 nginx에서 인증서를 발급 및 갱신하는 데에도 적합합니다.

루트리스 모드에서 포트를 게시하면 애플리케이션이 인식하는 정보도 달라집니다. Podman 4.x는 기본적으로 rootlesskit 포트 핸들러와 함께 slirp4netns를 사용하며, 전달된 연결은 소스 주소가 재작성되어 접근 로그에 모든 방문자가 10.0.2.100으로 기록됩니다. Podman 5.0부터는 기본값이 pasta로 변경되어 실제 클라이언트 주소가 유지됩니다. 4.x 버전에서는 --network slirp4netns:port_handler=slirp4netns를 사용하면 처리량에 약간의 비용이 발생하지만 실제 소스 주소를 복원할 수 있습니다.

여기에는 한 가지 장점이 있습니다. 루트리스로 게시된 포트는 일반 프로세스가 소유한 일반적인 리스닝 소켓이므로 방화벽의 입력 규칙이 그대로 적용됩니다. Docker는 NAT(네트워크 주소 변환) 규칙과 자체 포워딩 허용 설정을 작성하여 포트를 게시하는데, 이것이 바로 게시된 Docker 포트가 ufw 규칙을 무시하고 통과하는 이유입니다. 루트 권한으로 실행되는 Podman도 유사한 방식을 사용하여 동일한 함정에 빠지지만, 루트리스 모드는 그렇지 않습니다.

Docker Compose 파일을 Podman에서 그대로 사용할 수 있습니까?

대부분 가능하며, 두 가지 경로를 통해 지원됩니다. 첫 번째는 podman-compose을 사용하는 것으로, 동일한 파일을 읽어 Podman CLI를 구동하는 별도의 구현체입니다.

sudo apt install -y podman-compose
podman-compose up -d
podman ps

두 번째는 실제 Docker Compose가 사용자별 소켓을 통해 Podman의 Docker 호환 API와 통신하는 방식입니다.

systemctl --user enable --now podman.socket
export DOCKER_HOST="unix:///run/user/$(id -u)/podman/podman.sock"
docker compose up -d
podman ps

docker compose ps과 podman ps은 동일한 컨테이너 세트를 참조하므로 두 명령 모두 같은 컨테이너 목록을 출력해야 합니다. 이름 해석(Name resolution)도 정상적으로 작동합니다. Podman의 기본 네트워크 백엔드인 netavark가 aardvark-dns를 실행하므로, 사용자 정의 네트워크에 있는 컨테이너들은 서로의 이름을 통해 통신할 수 있습니다.

물론 한계점도 존재합니다. /var/run/docker.sock을 마운트하는 모든 설정은 Podman 소켓을 가리키도록 수정하거나 제거해야 합니다. network_mode: host은 사용자 네임스페이스 환경에서 다르게 동작합니다. depends_on와 condition: service_healthy의 조합은 podman-compose 버전에 따라 지원 수준이 일정하지 않습니다. restart: always는 재부팅 시 자동으로 유지되지 않으며, 이는 다음 섹션에서 해결합니다. Compose는 여전히 단일 파일로 다중 컨테이너 스택을 정의하는 좋은 방법이며, Podman 환경에서는 일종의 변환 계층 역할을 합니다. 수년간 운영할 스택이라면 quadlet으로 변환하여 두 개의 추상화 계층 대신 하나만 유지하는 것이 좋습니다.

Pod: Docker가 제공하지 않는 개념

Pod는 하나의 네트워크 네임스페이스를 공유하는 컨테이너 그룹입니다. Podman은 해당 네임스페이스를 열어두기 위해 작은 infra 컨테이너를 시작하며, 그룹 내 컨테이너들은 사용자 정의 네트워크나 서비스 디스커버리 없이도 127.0.0.1을 통해 서로 통신합니다.

podman pod create --name app -p 8080:80
podman run -d --pod app --name app-cache docker.io/library/redis:7
podman run -d --pod app --name app-web docker.io/library/nginx:1.27
podman pod ps
podman ps --pod

podman pod ps을 실행하면 인프라 컨테이너를 포함하여 총 3개의 컨테이너가 있는 Pod Running이 표시되어야 합니다. 이제 웹 컨테이너는 app-cache:6379이 아닌 127.0.0.1:6379에서 Redis에 접근합니다. 공유 네임스페이스로부터 두 가지 규칙이 도출됩니다. 포트는 개별 멤버가 아닌 Pod 단위로 게시해야 하며, 두 멤버가 동일한 포트에서 수신 대기할 수 없습니다.

이는 Kubernetes 모델이며 Podman은 이를 적극적으로 수용합니다. podman kube generate app > app.yaml은 현재 실행 중인 상태를 기반으로 Kubernetes 매니페스트를 작성하며(이전 패키지에서는 podman generate kube라고 함), podman kube play app.yaml은 이를 다른 호스트에서 재현합니다. Quadlet에는 이러한 파일을 systemd 서비스로 실행하는 .kube 유닛 유형이 있습니다. 이는 서비스를 그룹화하는 완전히 다른 방식이며, 향후 Kubernetes 도입을 고려한다면 Podman을 선택해야 하는 가장 강력한 이유입니다.

데몬 없이 자동 시작: Quadlet 유닛

Quadlet은 systemd 생성기입니다. 컨테이너를 설명하는 짧은 파일을 부팅 시 실제 systemd 서비스로 변환합니다. 파일은 루트리스 사용자의 경우 ~/.config/containers/systemd/에, 루트 사용자의 경우 /etc/containers/systemd/에 배치합니다.

~/.config/containers/systemd/caddy.container:

[Unit]
Description=Caddy web server
After=network-online.target

[Container]
Image=docker.io/library/caddy:2
ContainerName=caddy
PublishPort=8080:80
Volume=caddy-data.volume:/data
Environment=TZ=UTC
AutoUpdate=registry

[Service]
Restart=always
MemoryMax=512M

[Install]
WantedBy=default.target

~/.config/containers/systemd/caddy-data.volume은 섹션 헤더가 볼륨을 생성하므로 거의 비어 있어도 됩니다:

[Volume]
systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50

서비스 이름은 파일 이름에서 결정됩니다. caddy.container는 caddy.service이 됩니다. systemctl --user enable caddy을 실행하지 마십시오. 생성된 유닛은 활성화할 수 없으며, systemd는 Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated.라고 응답합니다. [Install] 섹션은 부팅 시 컨테이너를 시작하는 역할을 하며, daemon-reload는 파일을 수정한 후 유닛을 다시 생성하는 역할을 합니다.

이제 거의 모든 사용자가 겪는 설정입니다:

sudo loginctl enable-linger deploy
loginctl show-user deploy --property=Linger

Linger=yes를 예상하십시오. linger 설정이 없으면 마지막 SSH 연결이 종료될 때 systemd가 전체 사용자 세션을 해제하므로, 모든 루트리스 컨테이너가 함께 중지되고 부팅 시 다시 시작되지 않습니다. 로그아웃 시 컨테이너가 사라지는 현상은 항상 이 때문입니다.

컨테이너는 일반 서비스 유닛의 메인 프로세스이므로 systemd의 자체 제어 기능이 직접 적용됩니다. [Service] 섹션의 MemoryMax= 및 CPUQuota=은 systemd로 제한하는 다른 모든 서비스와 동일하게 동작합니다. 이는 Ubuntu 22.04부터 기본으로 사용되는 cgroup v2(control group version 2)가 필요합니다. podman info | grep -i cgroup로 확인하십시오.

업데이트에는 일치하는 메커니즘이 있습니다. AutoUpdate=registry과 systemctl --user enable --now podman-auto-update.timer을 조합하면 동일한 태그의 더 최신 이미지가 있는지 레지스트리를 확인하고, 유닛을 재시작하며, 새 컨테이너 시작에 실패할 경우 이전 이미지로 롤백합니다. 변경 사항을 미리 보려면 먼저 podman auto-update --dry-run를 실행하십시오. 이전의 podman generate systemd 명령은 여전히 존재하지만 더 이상 사용되지 않으므로(deprecated), 새로운 작업에는 Quadlet을 작성하십시오.

docker 별칭이 유효한 경우와 그렇지 않은 경우

sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker ps

podman-docker는 Podman을 호출하는 /usr/bin/docker 래퍼를 설치합니다. nodocker 파일이 없으면 모든 호출 시 처음에 Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg. 메시지가 출력됩니다. 이 래퍼는 일상적으로 사용하는 명령어인 run, ps, logs, exec, build, pull, push, inspect, cp, volume, network 등을 모두 포함합니다.

이식되지 않는 기능은 더 짧고 명확합니다. Swarm 모드에 대응하는 기능이 없으므로 Swarm 스택을 배포할 수 없습니다. Docker 소켓과 통신하는 도구들은 Podman 소켓을 내보내야 하며, 일부 도구는 여전히 차이를 감지합니다. Traefik의 Docker 프로바이더는 /run/user/<uid>/podman/podman.sock를 가리키면 작동하지만, Watchtower는 podman auto-update이 해당 역할을 대신하므로 사용할 수 없습니다. 저장소는 분리되어 있으므로 Podman은 Docker로 이미 내려받은 이미지를 볼 수 없으며, 바쁜 Docker 호스트의 podman images은 처음에는 비어 있는 상태로 시작합니다.

실행 중인 스택 마이그레이션 단계별 안내

  1. 컨테이너를 소유할 권한 없는 사용자를 생성하거나 선택하고, 해당 사용자가 /etc/subuid에 범위를 가지고 있는지 확인합니다.
  2. 완전한 이름을 사용하여 레지스트리에서 모든 이미지를 다시 가져옵니다(pull). Podman은 자체 이미지 저장소를 사용하므로 Docker의 저장소를 읽지 않습니다.
  3. docker save app:1.4 | podman load을 사용하여 로컬에서 빌드된 이미지를 이동합니다.
  4. Docker 컨테이너를 중지하고, /var/lib/docker/volumes/<name>/_data에서 각 볼륨의 내용을 복사한 뒤 podman unshare chown -R 1000:1000 <path>를 사용하여 소유권을 수정합니다.
  5. 포트 문제를 해결합니다. 리버스 프록시 뒤에서 1024 이상의 포트를 게시하거나 net.ipv4.ip_unprivileged_port_start을 설정합니다.
  6. 컨테이너당 하나의 quadlet 파일을 작성하고 systemctl --user daemon-reload을 실행한 뒤 각 서비스를 시작합니다.
  7. sudo loginctl enable-linger <user>을 실행하고 VPS를 재부팅한 다음, 다시 로그인하여 podman ps에 모든 서비스가 다시 나열되는지 확인합니다.

두 엔진은 이미지 저장소와 네트워크를 별도로 관리하므로 공유하는 것이 없습니다. 따라서 마이그레이션 중에도 두 엔진을 모두 실행할 수 있으며, 유일하게 충돌할 수 있는 요소는 호스트 포트 번호뿐입니다. 한 서비스를 먼저 이동하고 하루 동안 모니터링한 뒤, 다음 서비스를 이동하십시오.

Podman 대 Docker: VPS에 무엇을 선택해야 하는가?

다른 사람들과 함께 관리하는 compose 파일로 스택을 운영하거나, Docker 소켓과 통신하는 도구에 의존한다면 Docker를 유지하십시오. 다른 사람들이 작성한 코드와의 호환성은 실질적인 기능이며, Docker는 이 면에서 더 우위에 있습니다. 팀원들의 노트북이 모두 Docker를 실행 중이라면, 운영 환경에서도 동일한 엔진을 실행함으로써 얻는 구체적인 이점이 있습니다.

VPS에서 직접 제어하는 소수의 서비스를 운영하거나, 각 애플리케이션을 docker 그룹 없이 별도의 권한 없는(unprivileged) 사용자로 실행하고 싶다면 Podman으로 전환하십시오. 배포판과의 정렬도 중요합니다. RHEL 및 그 파생 배포판은 Podman을 지원되는 엔진으로 제공하므로, 해당 시스템에서는 Podman을 사용하는 것이 예상치 못한 문제를 줄이는 길입니다. 그럼에도 해당 호스트에서 Docker를 사용하려면, Rocky Linux 및 AlmaLinux에서의 dnf 경로를 따라 이미 docker 명령을 점유하고 있는 podman-docker 래퍼를 제거하는 것부터 시작해야 합니다. 이미 systemd 유닛으로 모든 것을 관리하고 있다면, quadlet은 새로 배워야 할 도구라기보다 빠져 있던 조각이 채워지는 느낌을 줄 것입니다.

고려할 만한 중간 선택지가 하나 있습니다. Rootful Podman은 Docker와 매우 유사하게 동작하며, 래퍼를 통해 docker 명령을 유지하면서도 항상 실행 중인 데몬을 제거합니다. 다만 이는 보안 상태를 변화시키는 핵심 요소인 루트리스(rootless) 기능을 포기하는 것이므로, 전환 과정의 경유지로 간주하십시오.

아직 첫 번째 컨테이너 호스트를 구축 중이라면, 새 VPS에서의 Docker 설정 및 보안 강화 경로가 더 짧은 길이며, 여기서 얻은 지식은 헛되지 않습니다. 이미지와 볼륨은 두 엔진 모두에서 동일한 객체이므로, 나중에 전환하더라도 서비스 관리 방식 외에 변경되는 부분은 거의 없습니다.

FAQ

Podman은 Docker를 완벽하게 대체할 수 있습니까?

사용자가 입력하는 명령어 수준에서는 거의 그렇습니다. podman-docker을 설치하면 /usr/bin/docker 래퍼가 제공되며, run, ps, build, logs, exec는 동일하게 동작합니다. 하지만 데몬을 대체하는 것은 아닙니다. Swarm에 대응하는 기능은 없으며, /var/run/docker.sock에 연결하는 도구는 사용자별 Podman 소켓을 가리키도록 설정해야 합니다. 또한 Docker로 내려받은 이미지는 Podman과 별도의 저장소를 사용하므로 Podman에서 볼 수 없습니다.

SSH 로그아웃 시 루트리스(rootless) Podman 컨테이너가 중지되는 이유는 무엇입니까?

마지막 로그인이 종료되면 systemd가 사용자 세션과 해당 세션의 모든 사용자 서비스를 중지하기 때문입니다. sudo loginctl enable-linger <user>을 실행한 뒤 loginctl show-user <user> --property=Linger 명령의 결과가 Linger=yes인지 확인하십시오. Linger를 설정하면 활성 세션이 없어도 해당 사용자의 systemd 인스턴스가 계속 실행되며, 이는 재부팅 후 컨테이너가 다시 시작되도록 하는 조건이기도 합니다.

볼륨 내 파일의 소유자가 UID 100999로 표시되는 이유는 무엇입니까?

루트리스 Podman은 컨테이너의 UID 0을 호스트 사용자로 매핑하고, 컨테이너의 UID 1부터는 subuid 범위에 매핑합니다. 범위가 100000부터 시작하는 경우, 컨테이너의 UID 1000은 호스트에서 100999가 됩니다. 네임스페이스 내부에서 podman unshare chown 1000:1000 /path/to/data를 사용하여 수정하거나, 처음 실행 시 :U 플래그를 사용하여 마운트하거나, --userns=keep-id을 사용하여 컨테이너의 UID가 사용자의 UID와 일치하도록 설정하십시오.

Podman에서 docker-compose.yml을 계속 사용할 수 있습니까?

네, 두 가지 방법이 있습니다. podman-compose은 파일을 읽어 Podman CLI를 직접 구동합니다. 또는 systemctl --user enable --now podman.socket로 호환성 소켓을 활성화하고 DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock를 설정한 뒤, 실제 docker compose을 해당 소켓에 연결하여 실행할 수 있습니다. network_mode: host 관련 문제나 Docker 소켓을 마운트하는 서비스, 그리고 재부팅 후에도 유지하기 위해 quadlet 유닛과 linger 설정이 필요한 restart: always의 경우 다소 제약이 있을 수 있습니다.

루트리스(rootless)를 사용하면 컨테이너가 정말 더 안전해집니까?

특정 위험 하나를 제거합니다. 루트리스 컨테이너를 탈출한 프로세스는 루트 권한이 아닌 사용자의 비권한 권한을 갖게 됩니다. 이는 충분히 가치 있는 일이며, 루트 권한과 동등한 docker 그룹이 루트리스 Podman에 존재하지 않는 이유이기도 합니다. 커널 취약점을 막아주지는 않으며, 사용자가 읽을 수 있는 파일을 보호하지도 않으므로 다른 서버와 마찬가지로 보안 강화 조치를 유지해야 합니다.