새 서버에 Docker를 올릴 때, 우리는 늘 두 가지를 함께 결정합니다. rootless로 설치할 것, 그리고 데이터를 /data/docker에 둘 것. 편하게 apt install docker.io 한 줄로 끝낼 수도 있지만, 운영 서버에서는 그렇게 하지 않습니다. 이 두 결정에는 분명한 이유가 있고, 그 이유를 이해하면 어느 배포판·어느 환경에서도 같은 원칙을 적용할 수 있습니다.
이 글은 결국 세 가지 질문에 답합니다.
- 왜 root가 아니라 rootless로 설치하는가
- 왜 기본 경로가 아니라
/data/docker에 데이터를 쌓는가 /data라는 개념은 대체 어디서 왔는가
대상 · Ubuntu 24.04 LTS · rootless Docker (Docker CE) · 소요 10분
1. 왜 rootless Docker인가
기본 방식으로 Docker를 설치하면 데몬(dockerd)이 root 권한으로 돕니다. 편하긴 하지만 보안 관점에서는 큰 부담입니다. Docker 데몬에 접근할 수 있는 사용자는 사실상 그 서버의 root와 같습니다. 컨테이너 하나가 탈출(container escape)하면, 그 즉시 호스트의 root를 쥐게 됩니다. “컨테이너로 격리했으니 안전하다”는 믿음이 root 데몬 위에서는 절반만 참인 셈입니다.
Rootless Docker는 이 데몬을 일반 사용자 권한으로 돌립니다. 사용자 네임스페이스(user namespace)를 이용해, 컨테이너 안의 root(uid 0)를 호스트에서는 권한 없는 일반 uid로 매핑합니다. 그래서 컨테이너가 탈출하더라도 손에 쥐는 것은 그 일반 사용자의 권한뿐, 호스트 root가 아닙니다.
- 공격 표면 축소 — root 데몬이 없으니, 데몬을 노린 권한 상승 경로가 사라집니다.
- 격리 강화 — 컨테이너 탈출의 피해가 일반 사용자 범위에 갇힙니다.
- 사용자 단위 운영 — Docker가 그 사용자의 홈·systemd(user) 아래에서 돌아, 여러 사용자가 서로 간섭 없이 각자의 Docker를 가질 수 있습니다.
솔직히 — rootless가 만능은 아닙니다. 1024 미만의 특권 포트를 컨테이너가 직접 열 수 없고(리버스 프록시로 우회), 네트워크는
slirp4netns를 거쳐 순수 성능이 약간 손해입니다. 하지만 우리가 서버에 올리는 대부분의 워크로드(웹·API·DB·캐시)에는 이 제약이 문제가 되지 않고, 얻는 보안 이득이 훨씬 큽니다. 도구는 상황에 맞게 고르는 것이지, 하나가 항상 옳은 것은 아닙니다.
2. 왜 기본 경로가 아니라 /data/docker인가
Docker의 데이터 — 이미지, 컨테이너 레이어, 볼륨, 빌드 캐시, 컨테이너 로그 — 는 예측 불가능하게 커집니다. 이미지를 몇 개 받고, 빌드를 몇 번 돌리고, 볼륨에 데이터가 쌓이고, 로그가 흐르다 보면 수십 기가바이트가 순식간에 찹니다.
문제는 Docker의 기본 데이터 경로가 /var/lib/docker, 즉 루트(/) 파티션 안이라는 점입니다. 아무 설정 없이 쓰면 이 폭증하는 데이터가 전부 /에 쌓입니다. 그리고 루트 파티션이 100%에 도달하는 순간, 서버는 로그도 못 남기고 명령어도 못 실행하는 최악의 상태로 얼어붙습니다.
그래서 우리는 Docker의 data-root를 /data/docker로 지정합니다. 이렇게 하면 이미지·볼륨·로그가 아무리 커져도 /data 볼륨 안에서 소진될 뿐, /와 OS는 멀쩡합니다. 한 곳의 폭증이 시스템 전체로 번지지 못하게 미리 격리하는 것입니다.
[ Docker 데이터를 어디에 두는가 ]
기본값 (위험)
이미지·볼륨·로그 폭증 ─▶ /var/lib/docker ─▶ / 파티션 100%
↓
OS 마비 (로그·명령 불가)
우리 방식 (격리)
이미지·볼륨·로그 폭증 ─▶ /data/docker ─▶ /data 볼륨만 소진
↓
/ 와 OS 는 정상
그래서 데이터 경로를 정할 때 우리가 늘 확인하는 것은 하나입니다 — 그 경로가 /와 같은 파티션에 있지는 않은가. 아무리 /data/docker라고 이름을 붙여도, 그 /data가 실은 별도 볼륨이 아니라 /와 같은 디스크라면 이 격리는 무의미하기 때문입니다.
3. /data는 어디서 온 개념인가
여기서 짚고 넘어갈 것이 있습니다. /data/docker의 /data는 Docker만을 위해 급조한 경로가 아닙니다. 우리가 모든 데이터베이스·미들웨어 서버에 일관되게 적용하는 파티션 분리 표준의 일부입니다.
그 표준은 서버의 저장 공간을 역할에 따라 네 개의 독립된 볼륨으로 나눕니다.
/ → OS 전용 (고정 크기, 신성불가침)
/app → 엔진·바이너리·설정 (거의 안 늘어남)
/log → 로그 + 로그로테이트 (예측 가능한 증가)
/data → 실제 데이터 — DB·볼륨·대용량 (I/O 집중, 크게 증가)
핵심은 커지는 속도와 방식이 다른 데이터를 서로 다른 방에 격리한다는 것입니다. 그래서 예측 불가능하게 커지는 것들 — 데이터베이스 파일, 볼륨, 그리고 Docker의 이미지·컨테이너·로그 — 은 전부 /data에 둡니다. data-root를 /data/docker로 두는 것은, 그 표준을 Docker에 그대로 적용한 것일 뿐입니다.
📌 철학은 여기서 이어집니다
왜 저장 공간을 이렇게 네 개로 나누는가, 그리고 이/data라는 개념이 실제로 어떤 새벽을 무사히 넘겼는가는 — 디스크가 가득 차던 새벽, 우리는 아무 일도 없었습니다 (‘역할에 따라 나눈 네 개의 공간’ 파트)에서 이어집니다. 그리고 왜/를 신성불가침으로 지키고 애플리케이션 데이터를 절대/에 쌓지 않는지는 같은 글의 ‘루트(/)를 지키는 것이 최우선입니다’ 파트에서 여러 겹의 방어선으로 다룹니다./data/docker는 그 철학의 실전 적용입니다.
4. 설치 — 준비 단계
이제 실제 설치입니다. rootless Docker는 일반 Docker CE 패키지에 더해 rootless 전용 패키지 몇 개가 필요합니다.
# Docker CE 저장소는 미리 추가돼 있다고 가정
sudo apt-get install -y
docker-ce docker-ce-cli containerd.io
docker-buildx-plugin docker-compose-plugin
docker-ce-rootless-extras uidmap dbus-user-session
slirp4netns fuse-overlayfs
docker-ce-rootless-extras— rootless 설치 도구(dockerd-rootless-setuptool.sh)uidmap— 사용자 네임스페이스 매핑(newuidmap/newgidmap)slirp4netns— rootless 네트워킹fuse-overlayfs— root 없이 쓰는 오버레이 스토리지 드라이버
먼저 root 데몬을 꺼둡니다. rootless와 rootful이 동시에 돌면 충돌하니, 시스템 레벨 Docker 서비스는 비활성화합니다.
sudo systemctl disable --now docker.service docker.socket
다음으로 사용자 네임스페이스에 쓸 subuid/subgid 범위를 등록합니다. 컨테이너 내부의 uid들이 호스트에서 매핑될 “빌린 uid 대역”입니다.
# 사용자에게 100000부터 65536개의 subuid/subgid 할당
echo "${USER}:100000:65536" | sudo tee -a /etc/subuid
echo "${USER}:100000:65536" | sudo tee -a /etc/subgid
마지막으로 linger를 켭니다. 기본적으로 사용자 systemd 서비스는 그 사용자가 로그인해 있을 때만 돕니다. 서버는 아무도 로그인하지 않은 채로 돌아야 하므로, linger를 켜서 로그인 세션과 무관하게 Docker가 상시 실행되게 합니다.
sudo loginctl enable-linger "${USER}"
5. 데이터 경로와 daemon.json
2·3단계에서 이야기한 /data/docker를 실제로 준비합니다. 소유자는 Docker를 돌릴 사용자로, 권한은 710(소유자만 접근)으로 잠급니다.
sudo mkdir -p /data/docker
sudo chown "${USER}:${USER}" /data/docker
sudo chmod 710 /data/docker
그다음 daemon.json으로 데이터 경로와 로그 정책을 지정합니다. 여기서 rootless의 함정이 하나 있습니다. rootful Docker는 /etc/docker/daemon.json을 읽지만, rootless는 ~/.config/docker/daemon.json을 읽습니다. 위치를 헷갈리면 설정이 먹지 않습니다.
# 파일 위치: ~/.config/docker/daemon.json (rootless 전용 경로)
{
"data-root": "/data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
6. rootless 데몬 기동
이제 Docker를 돌릴 사용자로 rootless 설치 도구를 실행하고, 사용자 systemd 서비스로 데몬을 올립니다. (root가 아니라 그 사용자 본인으로 실행하는 것이 핵심입니다.)
# Docker를 돌릴 사용자 셸에서 실행
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker.service
마지막으로 docker 명령이 rootless 소켓을 바라보도록 DOCKER_HOST를 셸 설정에 추가합니다. rootless 소켓은 사용자별 런타임 디렉토리 아래에 생깁니다.
# ~/.bashrc 에 추가
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
새 셸에서 docker info를 실행해 Docker Root Dir: /data/docker와 rootless 표시가 보이면 성공입니다.
docker info | grep -E "Root Dir|rootless"
# Docker Root Dir: /data/docker
# ... rootless
7. 로그 폭주까지 막는다
5단계 daemon.json의 log-opts를 그냥 지나치지 마세요. 이것도 /data 격리와 같은 목적의 안전장치입니다. Docker 컨테이너의 표준 출력 로그는 기본 설정에서 무한정 쌓입니다. 말 많은 컨테이너 하나가 로그로 디스크를 가득 채우는 일은 실제로 흔합니다.
max-size: 10m— 로그 파일 하나가 10MB에 도달하면 회전(rotate)max-file: 3— 최대 3개까지만 보관- → 컨테이너당 로그 상한 약 30MB로 고정
이렇게 상한을 못 박아두면, 어떤 컨테이너가 로그를 쏟아내도 그 양이 예측 가능한 선에서 멈춥니다. 커질 수 있는 것에는 미리 천장을 둔다 — /data로 데이터를 격리하는 것과 같은 발상입니다.
함정 모음 — 이럴 때 이렇게
- 로그아웃하니 Docker가 죽는다 —
enable-linger를 안 한 것입니다.sudo loginctl enable-linger <사용자>후 재기동하세요. docker명령이 root 소켓을 찾는다 —DOCKER_HOST가 설정 안 된 것입니다.echo $DOCKER_HOST로/run/user/UID/docker.sock을 가리키는지 확인하세요.daemon.json을 고쳤는데 반영이 안 된다 — rootless는/etc/docker가 아니라~/.config/docker/daemon.json입니다. 위치를 확인하고systemctl --user restart docker.- 서비스가 안 뜬다 —
journalctl --user -u docker.service -n 50으로 로그를 봅니다. 대개uidmap/subuid누락이나XDG_RUNTIME_DIR미설정입니다. - 소켓이 사라졌다 — 소켓은
/run/user/UID(tmpfs)에 있어 재부팅 시 재생성됩니다. linger가 켜져 있으면 부팅과 함께 자동으로 다시 올라옵니다.
마치며
Docker를 설치하는 방법은 많지만, 우리가 늘 같은 두 결정을 내리는 데에는 분명한 이유가 있습니다. rootless는 컨테이너의 사고가 호스트 root로 번지지 않게 하고, /data/docker는 데이터의 폭증이 /로 번지지 않게 합니다. 방향은 달라 보여도 목적은 하나입니다 — 한 곳의 문제가 전체의 문제가 되지 않도록 미리 격리하는 것.
이 두 원칙과 로그 상한, 권한 잠금(710)까지 — 우리는 이것을 표준으로 삼아, 어느 서버에 Docker를 올리든 결과가 같게 만듭니다. 검증된 방식을 매번 재현하는 것, 그것이 우리가 인프라에서 신뢰를 만드는 방법입니다. 파티션을 나누는 것도, Docker 데이터를 /data/docker에 모으는 것도, 결국 같은 원칙 위에 있습니다.