Ubuntu 24.04에서 Rootless Docker를 서비스 계정별로 운영하다 보면 XDG_RUNTIME_DIR, DOCKER_HOST, /run/user/<UID>/docker.sock 같은 경로와 환경변수를 자주 만나게 됩니다.
특히 sudo -u로 다른 서비스 계정의 Docker CLI를 실행하거나, 한 서버에서 여러 사용자가 각각 독립된 Rootless Docker daemon을 운영할 때는 어느 사용자의 Docker daemon에 연결하고 있는지를 정확하게 이해해야 합니다.
이 글에서는 freedesktop.org의 XDG Base Directory Specification, systemd의 pam_systemd, Docker 공식 Rootless 문서를 기준으로 XDG_RUNTIME_DIR과 Docker socket의 관계를 정리합니다.
이 글의 예시는 Ubuntu 24.04에서
svc-gitea라는 서비스 계정이 Rootless Docker를 사용하는 상황을 기준으로 설명합니다.
실제 UID는 서버마다 다르므로1005같은 값을 고정해서 사용하지 말고 반드시id -u로 확인해야 합니다.
핵심 구조
Rootless Docker에서 가장 먼저 구분해야 하는 것은 사용자 런타임 디렉터리, Docker daemon socket, Docker CLI 접속 대상입니다.
예를 들어 svc-gitea의 UID가 1005라면 일반적인 systemd 기반 Ubuntu 환경에서는 다음과 같은 구조가 됩니다.
Ubuntu Host
│
├─ Rootful Docker daemon
│ └─ /var/run/docker.sock
│
└─ svc-gitea
├─ UID: 1005
├─ XDG_RUNTIME_DIR=/run/user/1005
└─ Rootless Docker daemon
└─ /run/user/1005/docker.sock
Rootful Docker와 Rootless Docker는 같은 docker 명령을 사용할 수 있지만 접속하는 daemon socket은 다를 수 있습니다.
일반적인 Linux의 Rootful Docker daemon은 다음 Unix socket을 사용합니다.
/var/run/docker.sock
Rootless Docker는 기본적으로 다음 위치에 socket을 만듭니다.
$XDG_RUNTIME_DIR/docker.sock
XDG_RUNTIME_DIR이 /run/user/1005라면 실제 socket은 다음과 같습니다.
/run/user/1005/docker.sock
즉, 한 서버에서 여러 서비스 계정이 각각 Rootless Docker를 실행한다면 사용자별로 Docker daemon과 socket을 분리할 수 있습니다.
svc-postgres
└─ /run/user/1003/docker.sock
svc-gitea
└─ /run/user/1005/docker.sock
codex-agentd
└─ /run/user/1006/docker.sock
각 socket은 서로 다른 Docker daemon의 API 접점입니다.
XDG_RUNTIME_DIR이란?
XDG_RUNTIME_DIR은 freedesktop.org의 XDG Base Directory Specification에서 정의하는 사용자별 런타임 데이터 디렉터리입니다.
socket, named pipe와 같이 사용자 세션 동안 필요한 런타임 객체를 저장하는 용도로 사용합니다.
XDG 규격은 XDG_RUNTIME_DIR에 대해 다음과 같은 조건을 요구합니다.
- 해당 사용자가 소유해야 합니다.
- 해당 사용자만 읽고 쓸 수 있어야 합니다.
- Unix 권한은
0700이어야 합니다. - 사용자 로그인 세션의 생명주기와 연결되어야 합니다.
- 로컬 파일 시스템에 있어야 합니다.
- 재부팅 후에도 영구 보존되는 데이터 저장소로 사용해서는 안 됩니다.
중요한 점은 XDG 규격 자체가 경로를 /run/user/<UID>로 고정하지는 않는다는 것입니다.
Ubuntu처럼 systemd를 사용하는 Linux에서는 pam_systemd와 systemd-logind가 사용자 로그인 시 일반적으로 다음 디렉터리를 생성합니다.
/run/user/<UID>
그리고 해당 경로를 XDG_RUNTIME_DIR로 설정합니다.
예를 들어:
id -u svc-gitea
결과가 다음과 같다면:
1005
일반적인 런타임 디렉터리는 다음과 같습니다.
/run/user/1005
정상적인 로그인 세션에서는 다음과 같이 확인할 수 있습니다.
echo "$XDG_RUNTIME_DIR"
예:
/run/user/1005
systemd의 pam_systemd 문서에서도 사용자 로그인 시 /run/user/$UID가 생성되고 사용자 런타임 디렉터리로 사용된다고 설명합니다.
Rootless Docker와 Docker Socket
Docker는 Docker CLI와 Docker daemon이 분리되어 있습니다.
사용자가 다음 명령을 실행하더라도:
docker ps
실제로 컨테이너를 조회하는 것은 Docker CLI 자체가 아니라 Docker daemon입니다.
구조를 단순화하면 다음과 같습니다.
docker CLI
│
│ Docker Engine API
▼
docker.sock
│
▼
Docker daemon
│
├─ containers
├─ images
├─ networks
└─ volumes
따라서 어떤 docker.sock에 연결했는지가 어떤 Docker daemon을 제어하는지를 결정합니다.
Rootful Docker
일반적인 Rootful Docker daemon의 기본 Unix socket은 다음과 같습니다.
unix:///var/run/docker.sock
Docker 공식 문서에서도 Linux에서 별도의 host 또는 context를 지정하지 않은 기본 Docker CLI 연결 대상으로 /var/run/docker.sock을 설명합니다.
Rootful Docker daemon은 일반적으로 root 권한으로 실행됩니다.
Rootless Docker
Rootless Docker는 Docker daemon과 컨테이너를 일반 사용자 권한으로 실행하기 위한 방식입니다.
Docker 공식 Rootless 문서에서 기본 socket 위치는 다음과 같이 설명합니다.
$XDG_RUNTIME_DIR/docker.sock
그리고 XDG_RUNTIME_DIR은 일반적으로 다음과 같습니다.
/run/user/$UID
따라서 UID가 1005인 사용자의 Rootless Docker socket은 일반적으로 다음 위치에 있습니다.
/run/user/1005/docker.sock
socket 존재 여부는 다음처럼 확인할 수 있습니다.
GITEA_UID="$(id -u svc-gitea)"
sudo -u svc-gitea \
test -S "/run/user/$GITEA_UID/docker.sock" &&
echo "Docker socket exists"
직접 확인하려면 다음 명령도 사용할 수 있습니다.
ls -l "/run/user/$GITEA_UID/docker.sock"
XDG_RUNTIME_DIR과 DOCKER_HOST의 역할
XDG_RUNTIME_DIR과 DOCKER_HOST는 서로 관련되어 있지만 역할은 다릅니다.
XDG_RUNTIME_DIR
XDG_RUNTIME_DIR은 사용자별 런타임 파일을 저장할 기준 위치입니다.
XDG_RUNTIME_DIR="/run/user/1005"
Rootless Docker는 기본적으로 이 경로 아래에 docker.sock을 만듭니다.
/run/user/1005/docker.sock
즉 다음 관계가 성립합니다.
XDG_RUNTIME_DIR
│
└─ docker.sock
DOCKER_HOST
DOCKER_HOST는 Docker CLI가 어느 Docker daemon endpoint에 연결할 것인지 지정하는 환경변수입니다.
예를 들어:
export DOCKER_HOST="unix:///run/user/1005/docker.sock"
그 다음:
docker ps
를 실행하면 Docker CLI는 지정된 Unix socket으로 요청을 보냅니다.
다음과 같이 한 번의 명령에만 적용할 수도 있습니다.
DOCKER_HOST="unix:///run/user/1005/docker.sock" docker ps
따라서 두 환경변수의 역할은 다음처럼 구분할 수 있습니다.
XDG_RUNTIME_DIR
└─ 사용자 런타임 디렉터리가 어디인지 나타냄
└─ Rootless Docker의 기본 socket 위치 결정에 사용
DOCKER_HOST
└─ Docker CLI가 실제로 연결할 Docker daemon endpoint를 지정
XDG_RUNTIME_DIR을 설정했다고 해서 Docker CLI의 접속 대상이 항상 자동으로 해당 사용자의 Rootless Docker daemon으로 결정되는 것은 아닙니다.
Docker CLI의 실제 endpoint는DOCKER_HOST, Docker context 또는 명령행의--host/--context설정에 따라 결정됩니다.
Docker Context와의 관계
현재 Docker에서는 DOCKER_HOST만이 Docker daemon을 선택하는 방법은 아닙니다.
Docker CLI에는 Docker context 기능이 있습니다.
현재 context는 다음 명령으로 확인할 수 있습니다.
docker context ls
예를 들어 다음처럼 나타날 수 있습니다.
NAME DESCRIPTION DOCKER ENDPOINT
default unix:///var/run/docker.sock
rootless * unix:///run/user/1005/docker.sock
*가 붙은 context가 현재 사용 중인 context입니다.
Rootless Docker 설치 도구인 다음 명령을 이용해 설치하면:
dockerd-rootless-setuptool.sh install
Docker Engine 23.0 이후에는 설치 도구가 rootless Docker context를 자동으로 구성합니다.
따라서 svc-gitea 계정으로 정상 로그인한 터미널에서는 DOCKER_HOST를 직접 설정하지 않아도 다음 명령이 Rootless daemon으로 연결될 수 있습니다.
docker ps
하지만 자동화 스크립트에서 sudo -u로 다른 사용자의 Docker CLI를 실행하는 경우에는 현재 로그인 사용자의 Docker context와 환경이 그대로 적용된다고 가정하면 안 됩니다.
이 경우 어떤 daemon에 접근해야 하는지가 명확하다면 socket을 직접 지정하는 방식이 이해하기 쉽습니다.
DOCKER_HOST="unix:///run/user/1005/docker.sock" docker ps
서비스 계정으로 Rootless Docker에 접근하기
svc-gitea 계정의 Rootless Docker 상태를 다른 관리자 계정에서 확인한다고 가정해 보겠습니다.
먼저 UID를 확인합니다.
GITEA_UID="$(id -u svc-gitea)"
echo "$GITEA_UID"
예:
1005
런타임 디렉터리를 확인합니다.
ls -ld "/run/user/$GITEA_UID"
Docker socket을 확인합니다.
ls -l "/run/user/$GITEA_UID/docker.sock"
그 다음 svc-gitea 사용자 권한으로 Docker CLI를 실행합니다.
sudo -u svc-gitea env \
XDG_RUNTIME_DIR="/run/user/$GITEA_UID" \
DOCKER_HOST="unix:///run/user/$GITEA_UID/docker.sock" \
docker ps
이 명령은 다음 순서로 이해하면 됩니다.
sudo -u svc-gitea
│
└─ 명령을 svc-gitea 권한으로 실행
│
▼
XDG_RUNTIME_DIR=/run/user/1005
│
└─ svc-gitea의 런타임 디렉터리 지정
│
▼
DOCKER_HOST=unix:///run/user/1005/docker.sock
│
└─ Docker CLI가 접근할 socket 명시
│
▼
docker ps
│
└─ svc-gitea의 Rootless Docker daemon에 요청
여기서 docker ps의 접속 대상을 직접 결정하는 값은 DOCKER_HOST입니다.
XDG_RUNTIME_DIR은 Rootless Docker와 사용자 systemd 환경에서 사용하는 런타임 기준 경로입니다.
따라서 다음 명령에서는:
sudo -u svc-gitea env \
XDG_RUNTIME_DIR="/run/user/$GITEA_UID" \
DOCKER_HOST="unix:///run/user/$GITEA_UID/docker.sock" \
docker ps
DOCKER_HOST에 socket의 절대 경로가 이미 지정되어 있으므로 Docker CLI가 socket 위치를 추론할 필요가 없습니다.
이런 방식은 여러 Rootless Docker daemon이 존재하는 서버에서 자동화 스크립트가 어느 daemon을 대상으로 실행되는지 명확하게 표현할 수 있다는 장점이 있습니다.
sudo 사용자 전환에서 주의할 점
Rootless Docker를 사용할 때 단순히 다음과 같이 계정만 변경하면 모든 사용자 세션 환경이 완전히 재현된다고 생각하기 쉽습니다.
sudo -iu svc-gitea
하지만 systemd 사용자 세션은 단순한 UID 전환과 다릅니다.
Docker 공식 Troubleshooting 문서에서는 sudo -iu <USER>로 전환한 환경에서 다음과 같은 명령이 실패할 수 있다고 설명합니다.
systemctl --user start docker
대표적인 오류는 다음과 같습니다.
Failed to connect to bus: No such file or directory
원인은 sudo로 사용자만 변경한 환경이 pam_systemd를 통해 생성된 정상적인 로그인 세션과 동일하지 않을 수 있기 때문입니다.
Docker 공식 문서에서는 필요한 경우 다음과 같은 실제 사용자 로그인 방식을 안내합니다.
ssh svc-gitea@localhost
또는 systemd-container의 machinectl을 사용하는 환경이라면:
sudo machinectl shell svc-gitea@
와 같은 방식으로 사용자 세션을 생성할 수 있습니다.
다만 다음처럼 이미 실행 중인 Rootless Docker daemon의 socket에 Docker CLI 명령 하나를 보내는 작업은 별개의 문제입니다.
sudo -u svc-gitea env \
XDG_RUNTIME_DIR="/run/user/$GITEA_UID" \
DOCKER_HOST="unix:///run/user/$GITEA_UID/docker.sock" \
docker ps
이 경우 필요한 socket이 실제로 존재하고 svc-gitea가 접근할 수 있다면 Docker CLI가 해당 daemon에 연결할 수 있습니다.
즉 다음 두 상황을 구분해야 합니다.
Rootless Docker user systemd service를 관리
└─ 정상적인 사용자 systemd 세션이 중요
이미 실행 중인 Docker daemon에 CLI로 접근
└─ 올바른 사용자 권한과 Docker socket endpoint가 중요
자주 발생하는 오류 확인
Rootless Docker에서 연결 문제가 발생하면 먼저 사용자, 런타임 경로, socket, Docker endpoint를 순서대로 확인하는 것이 좋습니다.
UID 확인
id svc-gitea
id -u svc-gitea
예:
uid=1005(svc-gitea) gid=1005(svc-gitea) groups=1005(svc-gitea)
1005
런타임 디렉터리 확인
GITEA_UID="$(id -u svc-gitea)"
ls -ld "/run/user/$GITEA_UID"
일반적으로 해당 사용자가 소유하고 있어야 합니다.
Docker socket 확인
ls -l "/run/user/$GITEA_UID/docker.sock"
또는:
sudo -u svc-gitea \
test -S "/run/user/$GITEA_UID/docker.sock" &&
echo "OK"
socket 자체가 없다면 Docker CLI 환경변수 문제보다 Rootless Docker daemon이 실행 중인지 먼저 확인해야 합니다.
DOCKER_HOST 확인
현재 shell에서 다음을 확인합니다.
echo "$DOCKER_HOST"
다른 계정으로 실행할 때는 다음처럼 확인할 수 있습니다.
sudo -u svc-gitea env | grep '^DOCKER_HOST='
출력이 없다고 해서 반드시 문제가 있는 것은 아닙니다.
Docker context가 Rootless endpoint를 가리키고 있다면 DOCKER_HOST 없이도 동작할 수 있기 때문입니다.
Docker context 확인
sudo -u svc-gitea docker context ls
Docker context를 사용하지 않고 정확한 socket을 직접 테스트하려면 다음처럼 실행합니다.
sudo -u svc-gitea env \
XDG_RUNTIME_DIR="/run/user/$GITEA_UID" \
DOCKER_HOST="unix:///run/user/$GITEA_UID/docker.sock" \
docker info
정상적인 Rootless Docker라면 docker info의 Server 영역에서 Rootless 관련 security option을 확인할 수 있습니다.
/var/run/docker.sock으로 연결되는 경우
다음과 같은 오류가 발생할 수 있습니다.
Cannot connect to the Docker daemon at unix:///var/run/docker.sock.
Is the docker daemon running?
Linux Docker CLI는 별도의 host나 custom context가 없으면 기본 Unix socket으로 /var/run/docker.sock을 사용할 수 있습니다.
따라서 Rootless daemon에 연결해야 하는데 default context가 Rootful socket을 가리키고 있거나 실행 환경에서 Rootless context를 찾지 못한다면 의도하지 않은 endpoint를 사용할 수 있습니다.
이 경우 현재 설정을 먼저 확인합니다.
docker context ls
echo "$DOCKER_HOST"
Rootless socket을 직접 지정해서 비교할 수도 있습니다.
GITEA_UID="$(id -u svc-gitea)"
sudo -u svc-gitea env \
XDG_RUNTIME_DIR="/run/user/$GITEA_UID" \
DOCKER_HOST="unix:///run/user/$GITEA_UID/docker.sock" \
docker info
XDG_RUNTIME_DIR 오류
Rootless Docker daemon을 직접 실행하는 과정에서 XDG_RUNTIME_DIR이 설정되지 않았다면 Docker 공식 문서에서 설명하는 다음 계열의 오류가 발생할 수 있습니다.
could not get XDG_RUNTIME_DIR
systemd 기반 Ubuntu에서는 정상적인 pam_systemd 로그인 세션을 사용하면 일반적으로 다음 값이 자동으로 구성됩니다.
XDG_RUNTIME_DIR=/run/user/<UID>
따라서 임의로 /tmp 같은 공용 임시 디렉터리를 Rootless Docker의 런타임 디렉터리로 사용하는 방식은 피하는 것이 좋습니다.
Docker 공식 문서에서도 Rootless Docker의 runtime directory는 해당 사용자만 접근할 수 있어야 하고, /tmp 아래에 두는 방식은 TOCTOU 공격 위험 때문에 권장하지 않습니다.
여러 Rootless Docker daemon을 운영할 때
한 Ubuntu 서버에서 PostgreSQL, Gitea, Worker 등을 서로 다른 Linux 서비스 계정과 Rootless Docker daemon으로 분리했다고 가정해 보겠습니다.
Ubuntu 24.04
│
├─ postgres-svc
│ ├─ UID 1003
│ └─ /run/user/1003/docker.sock
│
├─ gitea-svc
│ ├─ UID 1004
│ └─ /run/user/1004/docker.sock
│
└─ codex-agentd
├─ UID 1005
└─ /run/user/1005/docker.sock
각 사용자는 자신의 Rootless Docker daemon을 사용합니다.
예를 들어 Gitea daemon을 확인하려면:
GITEA_UID="$(id -u gitea-svc)"
sudo -u gitea-svc env \
XDG_RUNTIME_DIR="/run/user/$GITEA_UID" \
DOCKER_HOST="unix:///run/user/$GITEA_UID/docker.sock" \
docker ps
Agent Worker daemon을 확인하려면:
AGENT_UID="$(id -u codex-agentd)"
sudo -u codex-agentd env \
XDG_RUNTIME_DIR="/run/user/$AGENT_UID" \
DOCKER_HOST="unix:///run/user/$AGENT_UID/docker.sock" \
docker ps
두 명령에서 docker 실행 파일은 같을 수 있지만 서로 다른 socket을 사용하므로 서로 다른 Docker daemon에 요청을 보냅니다.
docker CLI
│
├─ /run/user/1004/docker.sock
│ └─ gitea-svc Rootless Docker daemon
│
└─ /run/user/1005/docker.sock
└─ codex-agentd Rootless Docker daemon
이 구조를 이해하면 여러 서비스 계정의 Rootless Docker를 운영할 때 docker ps 결과가 예상과 다르게 보이는 이유도 쉽게 찾을 수 있습니다.
정리
Rootless Docker의 socket 구조를 이해할 때는 다음 세 가지를 구분하는 것이 중요합니다.
XDG_RUNTIME_DIR
│
└─ 사용자별 런타임 파일의 기준 디렉터리
예: /run/user/1005
Rootless Docker socket
│
└─ Docker daemon의 Unix socket
예: /run/user/1005/docker.sock
DOCKER_HOST / Docker context
│
└─ Docker CLI가 어느 Docker daemon에 접속할지 결정
Ubuntu처럼 systemd를 사용하는 환경에서는 일반적으로:
XDG_RUNTIME_DIR=/run/user/<UID>
가 사용되고 Rootless Docker의 기본 socket은:
$XDG_RUNTIME_DIR/docker.sock
이므로 결과적으로 다음 형태가 됩니다.
/run/user/<UID>/docker.sock
따라서 다음 명령은:
sudo -u svc-gitea env \
XDG_RUNTIME_DIR="/run/user/$GITEA_UID" \
DOCKER_HOST="unix:///run/user/$GITEA_UID/docker.sock" \
docker ps
svc-gitea 권한으로 명령을 실행하면서 사용자 런타임 경로를 명시하고, Docker CLI가 svc-gitea의 Rootless Docker socket에 연결하도록 endpoint를 명확하게 지정한 것입니다.
특히 여러 서비스 계정이 각각 별도의 Rootless Docker daemon을 사용하는 서버에서는 UID와 socket 경로를 명시적으로 확인하는 습관이 중요합니다.
id -u <사용자>
ls -ld /run/user/<UID>
ls -l /run/user/<UID>/docker.sock
docker context ls
echo "$DOCKER_HOST"
이 다섯 가지를 확인하면 Rootless Docker의 사용자, runtime directory, socket, Docker CLI 연결 대상이 서로 올바르게 연결되어 있는지 대부분 확인할 수 있습니다.
참고 자료
- Docker 공식 Rootless mode 문서: https://docs.docker.com/engine/security/rootless/
- Docker 공식 Rootless Tips: https://docs.docker.com/engine/security/rootless/tips/
- Docker 공식 Rootless Troubleshooting: https://docs.docker.com/engine/security/rootless/troubleshoot/
- Docker CLI 공식 문서: https://docs.docker.com/reference/cli/docker/
- Docker Context 공식 문서: https://docs.docker.com/engine/manage-resources/contexts/
- XDG Base Directory Specification: https://specifications.freedesktop.org/basedir/latest/
- systemd
pam_systemd문서: https://www.freedesktop.org/software/systemd/man/latest/pam_systemd.html
'운영체제(OS) > Docker' 카테고리의 다른 글
| Docker Compose One-off Container와 Named Volume (0) | 2026.09.09 |
|---|---|
| Host OS와 Container OS의 관계 (0) | 2026.09.09 |
| postgresql18.3 로컬 주소DB 구축 (8) - pg_trgm, 인덱스 설정 (0) | 2026.04.08 |
| postgresql18.3 로컬 주소DB 구축 (7) - 위치정보 테이블 적재 (0) | 2026.04.08 |
| postgresql18.3 로컬 주소DB 구축 (6) - WSL Docker에서 윈도우 로컬 파일을 인식하지 못할 때 해결법 (볼륨 마운트) (0) | 2026.04.07 |
댓글