Rootless Docker와 /data/docker — root 없이, 그리고 / 를 지키며 (Ubuntu 24.04)

2026.09.09

·

새 서버에 Docker를 올릴 때, 우리는 늘 두 가지를 함께 결정합니다. rootless로 설치할 것, 그리고 데이터를 /data/docker에 둘 것. 편하게 apt install docker.io 한 줄로 끝낼 수도 있지만, 운영 서버에서는 그렇게 하지 않습니다. 이 두 결정에는 분명한 이유가 있고, 그 이유를 이해하면 어느 배포판·어느 환경에서도 같은 원칙을 적용할 수 있습니다.

이 글은 결국 세 가지 질문에 답합니다.

  • 왜 root가 아니라 rootless로 설치하는가
  • 왜 기본 경로가 아니라 /data/docker에 데이터를 쌓는가
  • /data라는 개념은 대체 어디서 왔는가

대상 · Ubuntu 24.04 LTS · rootless Docker (Docker CE) · 소요 10분

Sponsored


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/dataDocker만을 위해 급조한 경로가 아닙니다. 우리가 모든 데이터베이스·미들웨어 서버에 일관되게 적용하는 파티션 분리 표준의 일부입니다.

그 표준은 서버의 저장 공간을 역할에 따라 네 개의 독립된 볼륨으로 나눕니다.

  /       →  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/dockerrootless 표시가 보이면 성공입니다.

docker info | grep -E "Root Dir|rootless"
# Docker Root Dir: /data/docker
# ... rootless

7. 로그 폭주까지 막는다

5단계 daemon.jsonlog-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에 모으는 것도, 결국 같은 원칙 위에 있습니다.


MORE POSTS

다른 글 보기

테크 랩

GroupFlow 콜센터 통합 – 06. 통화 녹취, 금융사만 하는 거 아닙니다: 일반 기업이 녹취를 남겨야 하는 이유

통화 녹취는 금융사만? 일반 기업의 녹취 필요성과, 쌓아만 두지 않고 STT로 검색하는 녹취 관리.
2026.09.18
테크 랩

GroupFlow 콜센터 통합 – 05. 부재중 전화, 그냥 사라지고 있지 않나요: 통화 추적과 후속 관리 자동화

부재중 전화를 놓치지 않는 통화 추적·후속 관리 자동화 — 처리상태 태그·후속 필터·대시보드·문자 후속.
2026.09.17
테크 랩

GroupFlow 콜센터 통합 – 04. 교환기는 있는데 소프트폰만 쓰나요: LG U+ DCS를 그룹웨어에 연동하다

LG U+ DCS 같은 IP-PBX 교환기를 교체하지 않고 그룹웨어에 연동 — 레거시 소프트폰을 무중단으로 대체하는 방법.
2026.09.16

프로젝트 문의 환영합니다

기획부터 개발, 운영까지 함께 만들어 드립니다.

무료 3분 자가진단

우리 회사, 자체 클라우드가 답일까?

AWS vs 자체 인프라 · 11개 항목 3분 체크