FreeBSD jail과 Docker 컨테이너의 차이점 비교
FreeBSD jail과 Docker 컨테이너의 구조적 차이를 상세히 분석합니다. 완전한 사용자 공간을 격리하는 jail 방식과 계층형 이미지를 사용하는 Docker의 소프트웨어 관리, 네트워킹, 상태 유지 방식을 비교하여 워크로드에 적합한 기술을 선택하는 기준을 제시합니다.
FreeBSD jail과 Docker 컨테이너의 비교
FreeBSD jail과 Docker 컨테이너는 동일한 문제를 서로 다른 방식으로 해결합니다. 두 기술 모두 하나의 커널을 공유하며 격리된 사용자 공간을 실행하므로, 가상 머신과는 근본적으로 다릅니다. 차이점은 내부에 무엇을 담느냐에 있습니다. Docker 컨테이너는 레지스트리에서 가져온 계층형 이미지로부터 단일 프로세스를 실행합니다. 반면 jail은 고유한 /etc, 자체 rc 시작 스크립트, 고유한 pkg 데이터베이스를 포함한 완전한 FreeBSD 사용자 공간을 실행하며, 원하는 만큼 많은 프로세스를 구동할 수 있습니다. 이 페이지에서 다루는 거의 모든 차이점은 바로 이러한 구조적 차이에서 비롯됩니다.
SSD Nodes는 FreeBSD 이미지를 제공하지 않습니다. 이 플랫폼에서는 FreeBSD 서버를 대여할 수 없으며, 아래 내용은 이곳에서 구매 가능한 장비에 대한 설치 가이드가 아닙니다. 이 글은 두 격리 모델을 비교하여 워크로드에 적합한 기술을 판단하고, FreeBSD 팀의 설정을 이해할 수 있도록 돕기 위해 작성되었습니다.
Jail의 실제 의미
Jail은 2000년 3월 FreeBSD 4.0에서 처음 도입되었습니다. 이는 cgroups보다 앞선 기술이며 Docker보다는 약 10년 정도 먼저 등장했습니다. 이 메커니즘은 하나의 커널 호출로 이루어집니다. jail(8)은 디렉터리 트리를 가져와 그 안에서 Jail ID가 부여된 프로세스를 시작하며, 커널은 해당 ID를 가진 프로세스에 대해 특정 작업들을 거부합니다. Jail에 갇힌 프로세스는 Jail 외부의 프로세스를 볼 수 없고, 파일 시스템을 마운트하거나 언마운트할 수 없으며, 커널 모듈을 로드할 수 없고, Jail에 할당되지 않은 네트워크 주소에 바인딩할 수 없습니다. 별도로 학습해야 할 네임스페이스 유형이나 기능별 선택 사항은 없습니다. 제약 사항은 하나의 단위로 제공되며 Jail 설정의 매개변수를 통해 조정됩니다.
호스트에서 jls를 실행하면 실행 중인 Jail 목록을 확인할 수 있고, jexec web sh를 사용하면 web이라는 이름의 Jail 내부 셸로 진입할 수 있습니다.
Jail을 구축하려면 FreeBSD 사용자 공간(userland)을 디렉터리에 배치해야 합니다. 베이스 시스템이 이 과정을 자동으로 수행합니다.
sudo bsdinstall jail /usr/local/jails/containers/web이 명령은 릴리스에 맞는 베이스 배포 세트를 가져와 일반적인 설치 후 단계를 실행합니다. 따라서 새 서버에서와 마찬가지로 root 암호를 설정하고 시간대를 선택할 수 있습니다. 결과적으로 폴더 안에 FreeBSD 설치가 완료됩니다. 그 후 /etc/jail.conf에 해당 내용을 기술합니다.
web {
host.hostname = "web.example.internal";
path = "/usr/local/jails/containers/web";
ip4.addr = "10.0.0.10";
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
mount.devfs;
}Jail을 시작하고 상태를 확인합니다.
sudo service jail start web
jls이제 jls을 실행하면 JID, 호스트 이름, IP 주소와 함께 web가 목록에 나타나야 합니다. Jail이 나타나지 않으면 sudo jail -c web을 직접 실행하십시오. 이 명령은 동일한 설정을 포그라운드에서 적용하며, 서비스 출력에 실패 원인을 남기는 대신 수락할 수 없는 매개변수를 화면에 출력합니다.
두 번 읽어볼 가치가 있는 줄은 exec.start = "/bin/sh /etc/rc"입니다. Jail을 시작하면 내부에서 FreeBSD의 일반 부팅 스크립트가 실행되므로, Jail은 자체 /etc/rc.conf에서 활성화된 모든 서비스를 시작합니다. Docker 컨테이너에는 이에 대응하는 단계가 없습니다. Docker 컨테이너는 이미지의 엔트리포인트 프로세스를 실행하고 해당 프로세스가 종료되면 함께 멈추기 때문입니다.
소프트웨어 도입 방식: 이미지와 레지스트리 대 직접 구성하는 사용자 공간
이것이 첫날부터 체감하게 되는 차이점입니다.
Docker를 사용하면 소프트웨어 이름을 지정하여 가져옵니다. docker pull nginx은 누군가 빌드하고 테스트를 마친 계층화된 콘텐츠 주소 지정 방식의 이미지를 가져오며, docker compose up -d는 볼륨과 네트워크를 연결하여 해당 이미지를 실행합니다. 레지스트리가 곧 제품인 셈입니다. Docker 워크플로우의 가치는 수천 개의 프로젝트가 작동 가능한 이미지를 배포한다는 점에 있으며, 덕분에 VPS에서 Docker 실행하기는 거창한 프로젝트가 아닌 짧은 작업이 됩니다.
FreeBSD는 기본적으로 제공되는 jail 이미지의 공개 레지스트리가 없습니다. 빈 사용자 공간을 생성하고 그 안에 직접 설치해야 하는데, 이는 베어메탈 서버를 설정하는 방식과 동일합니다. 타이핑할 양은 더 많지만, jail 내부에서 실행되는 것은 호스트와 동일한 패키지 세트에서 pkg가 직접 설치한 것이므로 투명성은 더 높습니다.
도구는 이 과정을 단축해 줍니다. BastilleBSD는 일반적인 jail 관리자이며 패키지로 제공됩니다.
sudo pkg install bastille
sudo sysrc bastille_enable=YES
sudo bastille setup
sudo bastille bootstrap 15.1-RELEASEbastille setup은 네트워킹, 스토리지, 방화벽 설정을 자동으로 처리합니다. bastille bootstrap은 릴리스를 한 번 다운로드하며, 이후 생성하는 모든 jail은 이를 재사용합니다. FreeBSD 15.1은 2026년 6월에 출시된 현재 운영 릴리스입니다. 본인이 실행 중인 릴리스로 대체하십시오.
이제 jail 생성은 명령어 한 줄, 내부 구성은 명령어 한 줄이면 충분합니다.
sudo bastille create web 15.1-RELEASE 10.17.89.10/24
sudo bastille pkg web install nginx
sudo bastille service web nginx start
sudo bastille console webbastille console web은 jail 내부의 로그인 셸을 제공하며, bastille list는 호스트에 존재하는 항목을 보여줍니다. 빌드를 반복하려면 Bastille 템플릿을 사용하여 단계를 파일에 저장하고 jail에 적용하면 됩니다. 이는 이 세계에서 Dockerfile에 가장 가까운 방식입니다. 템플릿은 각 jail에서 다시 실행됩니다. 미리 빌드되어 도착하는 것은 아무것도 없습니다.
결론은 간단합니다. Docker는 타인이 빌드한 결과물을 제공하고, Jails는 본인이 직접 설치한 환경을 제공합니다. 만약 후보 소프트웨어가 컨테이너 이미지로만 배포된다면, 다른 고려 사항을 따지기 전에 이 지점에서 선택이 결정됩니다.
상태와 업그레이드: ZFS가 변화를 만드는 지점
Docker는 의도적으로 상태를 분리합니다. 컨테이너 파일 시스템은 일회용이며, 데이터는 명명된 볼륨(named volume)이나 바인드 마운트(bind mount)에 저장됩니다. 업그레이드는 docker compose pull을 수행한 뒤 docker compose up -d을 실행하는 과정입니다. 컨테이너는 교체되며, 볼륨에 저장하지 않은 모든 데이터는 사라집니다. 이 규칙을 따르면 기능이 되지만, 잊어버리면 데이터 손실 사고가 됩니다. 이것이 바로 바인드 마운트와 명명된 볼륨 사이의 선택이 Compose 스택에서 매우 중요한 이유입니다.
Jail은 상태를 분리하지 않으며, ZFS가 그 방식을 가능하게 합니다. Jail 전체가 하나의 데이터셋입니다.
sudo zfs snapshot zroot/jails/containers/web@pre-upgrade
sudo pkg -j web upgrade
sudo zfs rollback zroot/jails/containers/web@pre-upgrade이 명령을 실행하기 전에 zfs list로 실제 데이터셋 이름을 확인하십시오. 위 경로는 핸드북에서 사용하는 레이아웃입니다. 스냅샷은 약 1초가 소요되며, Jail의 내용이 변경되기 전까지는 디스크 공간을 거의 차지하지 않습니다. 업그레이드로 서비스가 중단되면, 롤백을 통해 패키지 데이터베이스와 새벽 2시에 직접 수정한 설정 파일을 포함한 전체 사용자 공간(userland)을 이전 상태로 되돌릴 수 있습니다. Docker는 모델 자체가 그런 기능을 원하지 않는다고 가정하므로 내장된 대응 기능이 없습니다.
zfs clone은 나머지 절반의 핵심입니다. 스냅샷의 클론은 부모와 변경되지 않은 블록을 공유하는 새로운 쓰기 가능한 Jail입니다. 따라서 3 GB 크기의 Jail을 스테이징용으로 복제해도 변경을 시작하기 전까지는 디스크 공간을 거의 차지하지 않습니다. 이것이 FreeBSD 관리자가 업그레이드를 연습하기 위해 "운영 환경과 동일한" Jail을 구축하는 방법입니다.
기본 시스템 업그레이드는 패키지 업그레이드와 별개입니다. 자체적인 사용자 공간 사본을 가진 Jail의 경우 다음을 수행합니다.
sudo freebsd-update -b /usr/local/jails/containers/web fetch installThin Jail은 이러한 반복 작업을 피합니다. nullfs를 통해 공유된 읽기 전용 베이스를 마운트하고 각 Jail에 작은 쓰기 가능 레이어를 제공하므로, 베이스를 한 번만 패치하면 모든 Jail이 그 결과를 반영합니다. Bastille은 기본적으로 Thin Jail을 생성합니다.
네트워킹: 포트 게시와 주소 지정 결정
Docker는 네트워킹을 자동으로 처리하며 예외적인 경우에만 포트를 게시하도록 요구합니다. 컨테이너는 브리지에 배치되며 사용자 정의 네트워크에서 서비스 이름으로 서로 통신하고, -p 8080:80는 그중 하나를 호스트에 노출합니다. Docker는 이를 위해 자체 패킷 필터 규칙을 작성하며, 이것이 바로 게시된 컨테이너 포트가 ufw를 우회하는 방식입니다.
Jail은 시작 단계에서 모델을 선택해야 하며, 두 가지 방식이 있습니다.
Shared IP. ip4.addr = "10.0.0.10"는 기존 호스트 인터페이스에 해당 주소를 추가하고 Jail을 그 주소로 제한합니다. Jail은 자체 네트워크 스택이 없으므로 자체 방화벽을 실행할 수 없습니다. 또한 모든 주소에 바인딩할 수도 없습니다. Jail 내부의 소켓이 0.0.0.0을 요청하면 커널이 이를 Jail의 주소로 재작성합니다. 두 개의 Jail이 동일한 주소의 80번 포트를 동시에 수신할 수 없으므로, 각각 주소를 할당하거나 앞에 리버스 프록시를 배치해야 합니다.
VNET. Jail에 vnet;을 추가하면 Jail은 자체 인터페이스, 라우팅 테이블, 방화벽 규칙을 포함한 완전한 네트워크 스택을 갖게 됩니다. 양쪽 끝이 연결된 가상 케이블인 epair을 사용하여 호스트와 연결하고, 호스트 측을 브리지에 배치합니다. 이는 Docker가 제공하는 방식과 가장 유사하며, Bastille의 -V 및 -B Jail 유형의 기반이 되는 모드입니다.
호스트 포트를 Jail로 포워딩하는 것은 pf 리다이렉트 규칙입니다. Bastille은 이를 다음과 같이 래핑합니다.
sudo bastille rdr web tcp 80 80EXPOSE나 자동 게시 기능은 없습니다. 주소나 리다이렉트 규칙이 허용하지 않는 한 Jail에 도달할 수 있는 것은 없습니다. 이는 초기 설정은 더 느리지만 방화벽은 훨씬 더 조용하게 유지됩니다.
리소스 제한: cgroups와 rctl 비교
Docker는 cgroups를 사용하여 컨테이너를 제한하며, 이 제한 설정은 컨테이너가 정의된 곳에 위치합니다. 즉, 명령줄의 --memory=1g --cpus=1.5 옵션이나 Compose 파일 내의 대응하는 키에 설정됩니다. 만약 스택을 VPS의 Docker Compose 파일로 관리하고 있다면, 제한 설정은 해당 서비스 옆에 위치하며 git을 통해 함께 이동합니다.
FreeBSD는 rctl를 사용하며, 이는 사용자가 직접 활성화해야 하는 하위 시스템입니다. 리소스 계정 관리는 모든 할당 시 약간의 비용이 발생하기 때문에 기본적으로 꺼져 있습니다. /boot/loader.conf에 튜너블(tunable)을 추가하고 재부팅하십시오:
kern.racct.enable=1그런 다음 규칙을 설정하고 모니터링하십시오:
sudo rctl -a jail:web:vmemoryuse:deny=1g
rctl -hu jail:webrctl -hu jail:web은 jail의 현재 사용량을 사람이 읽기 쉬운 단위로 출력하므로, 문제가 발생하기 전에 제한치에 얼마나 근접했는지 확인할 수 있습니다. deny 동작은 제한을 초과하는 할당이 jail 내부에서 실패하도록 만듭니다. 따라서 호스트에서 프로세스가 강제 종료되는 메시지 대신 애플리케이션 자체의 할당 오류를 확인할 수 있습니다.
rctl -a로 추가한 규칙은 재부팅 시 사라집니다. FreeBSD의 rctl 서비스는 /etc/rctl.conf에서 규칙을 다시 불러오므로, 해당 파일에 규칙을 작성하고 서비스를 활성화하십시오:
sudo sysrc rctl_enable=YES이 지점에서 Docker가 확실히 더 편리합니다. Compose 파일의 제한 설정은 그것이 제약하는 서비스와 함께 검토됩니다. 반면 rctl 규칙은 다른 곳에 정의된 jail을 지칭하는 별도 파일의 한 줄에 불과합니다.
가상 머신이 해답일 때: bhyve
Jail은 호스트 커널을 공유하므로 일부 기능은 영구적으로 사용할 수 없습니다. Jail은 다른 커널 버전을 실행할 수 없고, 커널 모듈을 로드할 수 없으며, Linux 컨테이너처럼 Linux 바이너리를 실행할 수 없습니다. FreeBSD에는 linuxulator라는 Linux 호환 계층이 있지만, 이는 Linux 시스템 호출의 일부만을 구현한 것이며 임의의 Linux 이미지를 실행하기 위한 일반적인 해결책은 아닙니다.
bhyve는 FreeBSD의 하이퍼바이저이며, 서로 다른 운영 체제, 서로 다른 커널, 또는 커널을 공유하고 싶지 않은 테넌트와 같이 확실한 머신 경계가 필요할 때 적합한 도구입니다. 이 경우 메모리를 공유하는 대신 예약해야 하며, 패치해야 할 커널이 하나 더 늘어나는 비용을 감수해야 합니다. 이는 Linux에서 컨테이너와 완전한 가상 머신 사이에서 선택하는 것과 동일한 결정이며, 중첩 가상화를 지원하는 VPS가 필요한지 여부를 결정하는 기준이 됩니다.
생태계, 대부분의 팀이 Docker를 사용하는 실질적인 이유
위의 모든 내용은 모델에 관한 것입니다. 대부분의 팀이 선택을 결정하는 기준은 각 기술을 둘러싼 생태계의 규모입니다.
Docker는 Docker Hub와 GHCR, docker compose, 단일 서버로 부족할 때 사용하는 Kubernetes, 컨테이너 지원이 이미 통합된 CI 러너, 그리고 거의 모든 프로젝트의 README에 포함된 한 줄짜리 명령어로 시작하는 퀵스타트를 제공합니다. Jails는 규모가 크고 세심하게 관리되는 FreeBSD ports 트리를 제공하지만, 즉시 실행 가능한 애플리케이션 번들의 수는 훨씬 적습니다. 프로젝트가 컨테이너 이미지만 배포할 때, FreeBSD를 사용한다면 문서를 읽고 직접 구성 요소를 조립해야 합니다.
Jails는 이러한 트레이드오프의 반대편에서 그 가치를 증명합니다. 이미 ZFS를 사용 중이며 전체 서비스의 스냅샷과 롤백을 중요하게 생각할 때, 서비스가 FreeBSD 네이티브일 때, 단일 프로세스가 아닌 테넌트별로 완전한 사용자 공간(userland)을 원할 때, 또는 커널, 패킷 필터, 파일 시스템 및 문서가 하나의 시스템으로 통합 관리되기를 원할 때 Jails를 선택합니다. 마지막 항목은 사람들이 FreeBSD를 일관성 있다고 부르는 이유이며, 이에 대해서는 서버 플랫폼으로서의 Linux와 FreeBSD 비교와 FreeBSD 15에서 서버 사용을 위해 변경된 점에서 더 자세히 다룹니다.
마지막 결론입니다. 팀이 이미 Docker에 익숙하다면, 전환 비용은 실재하며 그에 따른 보상은 구체적이어야 합니다. 격리 품질 때문에 전환하지 마십시오. 두 모델은 충분히 비슷하므로 설정 방식이 더 중요합니다. ZFS 기반의 전체 서비스 롤백을 원하거나, 이미 FreeBSD를 사용 중인 경우에만 전환하십시오.
FAQ
FreeBSD에서 Docker 이미지를 실행할 수 있습니까?
Linux 이미지는 실행할 수 없으며, 공식적으로 지원되는 경로도 아닙니다. FreeBSD는 OCI 컨테이너를 지원합니다. sudo pkg install -y podman-suite을 설치하면 Podman을 사용할 수 있는데, 이는 내부적으로 실제 jail을 생성하는 런타임인 ocijail를 통해 컨테이너를 실행합니다. 컨테이너 모니터를 위해 /dev/fd에 fdescfs를 마운트해야 하며, 컨테이너 NAT(네트워크 주소 변환)를 위해 pf이 필요합니다. FreeBSD 네이티브 OCI 이미지가 가장 잘 작동합니다. Linux 이미지는 추가로 Linux 호환성 계층이 필요하며, 2026년 8월 기준으로 FreeBSD의 Podman 포트는 여전히 실험적인 단계로 분류됩니다. 배포 환경이 Linux 이미지 스택으로 구성되어 있다면 Linux에서 실행하십시오. RHEL 계열의 경우 Rocky Linux 또는 AlmaLinux에 Docker 설치하기를 참조하십시오. 이 환경에서는 아무것도 설치하기 전부터 이미 docker 명령을 소유한 패키지로 Podman이 기본 제공됩니다.
FreeBSD jail이 Docker 컨테이너보다 더 안전합니까?
둘 다 호스트 커널을 공유하므로 커널 버그는 양쪽 모두에게 위험 요소이며, 신뢰할 수 없는 코드를 격리하기 위한 경계로 적합하지 않습니다. 차이점은 시작 지점에 있습니다. jail은 기본적으로 대부분의 작업이 거부된 상태에서 시작하며, 필요한 기능을 매개변수별로 하나씩 활성화하는 방식입니다. 반면 Docker 컨테이너는 일부 권한이 제한된 네임스페이스 내에서 root 권한으로 시작하며, 추가적인 보안 강화는 사용자가 직접 선택해야 합니다. 실제로는 모델보다 설정이 보안 수준을 결정합니다. allow.mount와 allow.raw_sockets이 활성화된 jail은 세심하게 설정된 컨테이너보다 더 안전하다고 볼 수 없습니다.
jail을 어떻게 백업합니까?
데이터셋을 스냅샷으로 생성한 뒤 전송하십시오. sudo zfs snapshot zroot/jails/containers/web@backup을 실행한 후, 해당 스냅샷을 zfs send 명령으로 다른 풀로 보내거나 외부로 복사할 파일로 만듭니다. jail은 전체 사용자 공간을 하나의 데이터셋에 유지하므로, 스냅샷을 통해 설치된 패키지와 데이터, 그리고 직접 수정한 모든 설정 파일을 일관된 시점에 백업할 수 있습니다. 이는 명명된 볼륨과 Compose 파일을 백업하고 나머지는 이미지로부터 다시 빌드하는 Docker의 방식과는 대조적입니다.
BastilleBSD가 꼭 필요합니까, 아니면 기본 시스템만으로 충분합니까?
기본 시스템만으로도 충분하며, 처음 시작할 때는 기본 시스템을 사용하는 것이 더 좋습니다. jail.conf, jls, jexec, service jail start 명령이 전체 모델을 포괄하므로, 이 명령들을 익히면 특정 호스트의 도구를 따로 배우지 않아도 어떤 FreeBSD 호스트든 관리할 수 있습니다. Bastille은 그 위에 얹혀진 편의 계층입니다. 릴리스를 부트스트랩하고, 씬(thin) jail을 생성하며, 템플릿을 적용하고 pf 리다이렉트 규칙을 자동으로 작성해 줍니다. 먼저 기본 명령을 익힌 뒤, jail의 개수가 늘어나 타이핑이 번거로워질 때 Bastille을 도입하십시오.