
systemd 서비스에서 실행 파일이 보는 파일시스템 범위를 줄이고 싶다면 RootDirectory=와 RootImage=를 검토할 수 있습니다. 두 설정은 서비스 프로세스의 루트 파일시스템을 바꿔서, 호스트 전체가 아니라 지정한 디렉터리나 이미지 안을 기준으로 실행되게 합니다.
결론부터 말하면, 이미 준비된 디렉터리 트리를 서비스 루트로 쓰려면 RootDirectory=, 파일시스템 이미지 파일이나 블록 장치를 루트로 쓰려면 RootImage=가 맞습니다. 단순한 읽기 전용 보호가 목적이라면 먼저 ProtectSystem=, ProtectHome=, ReadWritePaths= 같은 하드닝 설정으로 충분한지 확인하는 편이 좋습니다.
RootDirectory와 RootImage는 무엇을 바꿀까
RootDirectory=는 호스트의 절대 경로로 지정한 디렉터리를 서비스 프로세스의 루트로 사용합니다. 공식 systemd.exec 문서 기준으로 이 설정은 pivot_root 또는 chroot 계열 동작을 통해 실행 프로세스의 루트 디렉터리를 바꿉니다. 그래서 실행 파일과 실행에 필요한 라이브러리, 설정 파일이 새 루트 안에 있어야 합니다.
RootImage=는 디렉터리 대신 파일시스템 이미지 파일이나 블록 장치를 루트로 사용합니다. 이미지에는 파티션 테이블이 없는 파일시스템, 단일 Linux 호환 파티션을 가진 MBR 또는 GPT 이미지, Discoverable Partitions Specification을 따르는 GPT 파티션 구성이 올 수 있습니다.

두 설정은 서비스가 보는 경로 기준을 바꿉니다. 예를 들어 서비스 내부에서 /etc/app.conf를 열면, 그것은 호스트의 /etc/app.conf가 아니라 새 루트 안의 /etc/app.conf입니다. 이 점 때문에 파일이 없어서 실행이 실패하는 문제와, 의도하지 않은 호스트 경로를 못 보는 문제가 동시에 생길 수 있습니다.
언제 RootDirectory를 쓸까
RootDirectory=는 디렉터리 트리를 직접 만들고 관리할 때 단순합니다. 패키징 전 테스트 루트, 빌드 산출물로 만든 최소 실행 환경, 특정 서비스만을 위한 파일 트리를 준비해 두고 그 안에서 실행시키는 경우에 맞습니다.
[Service]
RootDirectory=/srv/example-root
ExecStart=/usr/bin/example-daemon
이 예시에서 ExecStart=의 /usr/bin/example-daemon은 호스트 기준 경로가 아니라 서비스가 들어간 새 루트 안의 경로로 해석됩니다. 따라서 /srv/example-root/usr/bin/example-daemon과 필요한 공유 라이브러리, 런타임 파일이 준비되어 있어야 합니다.
RootDirectory=만으로 충분한 격리가 완성되는 것은 아닙니다. 서비스가 /proc, /sys, /dev 같은 API 파일시스템을 필요로 하면 MountAPIVFS=를 함께 봐야 하고, 사용자 namespace 격리가 필요하면 PrivateUsers=도 검토해야 합니다.
언제 RootImage를 쓸까
RootImage=는 루트를 하나의 이미지로 배포하거나 교체하고 싶을 때 유용합니다. 이미지 파일을 버전 단위로 만들 수 있고, 운영 환경에서는 같은 이미지를 여러 호스트에 배포하기 쉬운 구조가 됩니다.
[Service]
RootImage=/var/lib/example/example.raw
ExecStart=/usr/bin/example-daemon
RootImage=를 쓰면 systemd는 이미지 파일을 마운트해서 서비스 루트로 사용합니다. 공식 문서 기준으로 RootImage=를 사용하는 unit은 systemd-udevd.service에 대한 After= 의존성을 자동으로 얻습니다. 또한 device 접근 정책을 좁게 둔 환경에서는 loop 장치 관련 허용이 함께 고려됩니다.
이미지를 읽기 전용으로 운영하거나, 무결성 검사를 붙이거나, 같은 이미지를 여러 서비스 배포 단위로 쓰려면 RootImage= 쪽이 관리하기 쉽습니다. 반대로 이미지 빌드와 검증 체계가 없다면 단순 디렉터리보다 운영 복잡도가 커질 수 있습니다.
호스트 경로는 어떻게 연결할까
루트를 바꾸면 서비스가 호스트의 로그 소켓, 상태 디렉터리, 런타임 socket을 자동으로 모두 볼 수 있는 것은 아닙니다. 필요한 경로는 명시적으로 연결해야 합니다. 이때 BindPaths=와 BindReadOnlyPaths=를 사용합니다.

| 설정 | 의미 | 주로 쓰는 상황 |
|---|---|---|
BindPaths= |
호스트 경로를 서비스 namespace 안에 쓰기 가능 bind mount로 연결 | 서비스가 상태 파일이나 socket을 특정 위치에 써야 할 때 |
BindReadOnlyPaths= |
호스트 경로를 읽기 전용 bind mount로 연결 | 로그 소켓, 인증서, 읽기 전용 설정처럼 수정이 필요 없는 파일을 넣을 때 |
공식 문서는 RootDirectory= 또는 RootImage=와 함께 syslog나 journal 프로토콜로 호스트 로깅 인프라를 쓰려면 관련 소켓을 호스트에서 마운트해야 한다고 설명합니다. 예를 들어 /dev/log, /run/systemd/journal/socket, /run/systemd/journal/stdout을 읽기 전용 bind mount로 연결하는 형태입니다.
[Service]
RootDirectory=/srv/example-root
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
ExecStart=/usr/bin/example-daemon
쓰기 경로는 더 좁게 잡아야 합니다. 넓은 호스트 디렉터리를 통째로 연결하면 루트를 바꾼 의미가 약해집니다. 상태 데이터라면 StateDirectory=, 실행 중 임시 파일이라면 RuntimeDirectory=를 먼저 검토하고, 그래도 외부 경로가 필요할 때만 BindPaths=를 추가하는 기준이 안전합니다.
RootEphemeral은 어떤 경우에 볼까
RootEphemeral=은 RootDirectory=나 RootImage=로 지정한 루트의 임시 복사본에서 서비스를 실행하는 설정입니다. 공식 문서 기준으로 복사본은 서비스가 활성화된 동안 사용되고, 서비스가 멈추거나 다시 시작되면 정리됩니다.
이 설정은 같은 기준 루트를 깨끗하게 유지하면서 서비스가 실행 중에 파일을 바꿀 수 있게 하고 싶을 때 의미가 있습니다. 다만 효율적인 임시 복사를 위해서는 파일시스템 특성이 중요합니다. 문서는 디렉터리 루트에서는 btrfs subvolume, 이미지 루트에서는 reflink를 지원하는 파일시스템이 효율적인 복사에 유리하다고 설명합니다.

선택 기준은 무엇일까
선택 기준은 배포 방식, 변경 수명, 필요한 호스트 연결입니다. 루트를 바꾸는 설정은 강력하지만, 그만큼 서비스 실행 환경을 직접 책임져야 합니다.
| 상황 | 우선 검토 | 이유 |
|---|---|---|
| 준비된 디렉터리 트리에서 서비스만 실행 | RootDirectory= |
파일을 직접 확인하고 수정하기 쉽고, 이미지 빌드가 필요 없음 |
| 루트를 이미지 단위로 배포하고 교체 | RootImage= |
파일시스템 이미지를 버전 단위 산출물로 다룰 수 있음 |
| 실행 중 변경은 허용하되 기준 루트는 보존 | RootEphemeral= |
서비스 수명 동안 임시 복사본을 사용하고 정지 시 정리 가능 |
| 호스트 루트는 그대로 두고 쓰기 범위만 제한 | ProtectSystem=, ReadWritePaths= |
전체 실행 루트를 준비하지 않아도 파일 쓰기 범위를 줄일 수 있음 |
자주 놓치는 점
첫째, 실행 파일 경로의 기준이 바뀝니다. unit 파일에 적은 ExecStart=/usr/bin/app은 새 루트 안의 /usr/bin/app입니다. 호스트에서 실행되는 helper가 아니라 서비스 namespace 안에서 실행될 파일을 준비해야 합니다.
둘째, WorkingDirectory=도 새 루트 기준으로 해석됩니다. 공식 문서 기준으로 WorkingDirectory=는 RootDirectory=로 지정한 서비스 루트에 상대적인 디렉터리를 받습니다. 루트를 바꾸면 작업 디렉터리도 함께 점검해야 합니다.
셋째, 로그와 알림 경로를 확인해야 합니다. NotifyAccess=와 함께 RootDirectory= 또는 RootImage=를 쓰면 notification socket은 호스트에서 루트 환경으로 자동 마운트됩니다. 그러나 syslog나 journal 프로토콜을 직접 쓰는 경우에는 관련 소켓 bind mount가 필요할 수 있습니다.
넷째, 경로 의존성이 자동으로 추가될 수 있습니다. 공식 문서 기준으로 RootDirectory=, RootImage=, RuntimeDirectory=, StateDirectory= 같은 경로 기반 설정은 필요한 mount unit에 대해 Requires=와 After= 의존성을 자동으로 얻습니다. 부팅 순서 문제를 볼 때 이 점을 함께 확인해야 합니다.
FAQ
RootDirectory는 컨테이너와 같은가
같지 않습니다. RootDirectory=는 서비스의 루트 파일시스템 시야를 바꾸는 설정입니다. 프로세스, 네트워크, device, capability, system call 같은 다른 격리는 별도 설정으로 다뤄야 합니다. 컨테이너 수준의 격리를 기대한다면 systemd 서비스 하드닝 옵션을 조합하거나 별도 컨테이너 도구를 검토해야 합니다.
RootImage를 쓰면 파일이 자동으로 읽기 전용이 될까
그렇게 단정하면 안 됩니다. 이미지의 마운트 옵션과 관련 설정을 봐야 합니다. 읽기 전용 운영이 목적이라면 RootImageOptions=, 이미지 파일 권한, 별도 쓰기 경로 설계를 함께 확인합니다.
서비스가 새 루트 안에서 호스트 OS 정보를 볼 수 있을까
공식 문서 기준으로 RootDirectory=나 RootImage=를 쓰는 서비스에는 호스트의 os-release 파일이 읽기 전용으로 /run/host/os-release에 제공됩니다. 애플리케이션이 실행 환경을 판별할 때 이 위치를 참고할 수 있습니다.
정리
RootDirectory=와 RootImage=의 핵심은 서비스가 보는 루트 파일시스템을 명시적으로 바꾸는 것입니다. 디렉터리 트리를 직접 관리하면 RootDirectory=, 이미지 단위 배포가 필요하면 RootImage=, 실행 중 변경을 임시 복사본에만 남기고 싶으면 RootEphemeral=을 함께 검토합니다.
적용 전에는 세 가지를 확인합니다. 실행 파일과 라이브러리가 새 루트 안에 있는지, 로그와 상태 저장 경로가 필요한 만큼만 연결되어 있는지, 단순 쓰기 제한 목적을 위해 과하게 루트를 바꾸고 있지는 않은지입니다. 이 기준을 잡고 적용하면 루트 격리를 서비스 하드닝의 한 부분으로 현실적으로 사용할 수 있습니다.
'프로그래밍 > 운영체제' 카테고리의 다른 글
| systemd DynamicUser 기준 (0) | 2026.08.14 |
|---|---|
| systemd OnCalendar 사용법 (0) | 2026.08.10 |
| systemd StartLimitBurst 기준 정리 (0) | 2026.08.06 |
| systemd network.target과 network-online.target 기준 정리 (0) | 2026.08.02 |
| systemd Conflicts 기준 정리 (0) | 2026.08.01 |





