Docker Engine을 설치하고 나면 바로 몇 가지 불편과 위험이 드러난다. 일반 계정으로 docker ps를 치면 권한 오류가 나고, 컨테이너 로그는 기본 설정에서 크기 제한 없이 디스크를 채운다. Docker 데몬을 재시작하면 실행 중이던 컨테이너도 함께 죽는다. 이 글에서는 Ubuntu에 Docker Engine 설치 이후 Ubuntu 24.04 호스트에서 해두면 좋은 설정을 실제 실행 결과로 정리한다. 다루는 항목은 non-root 사용자 권한, 로그 로테이션, live-restore, 네트워크 대역, 데이터 디렉터리 이동이다.
$ docker --version
Docker version 29.9.1, build dfc520d
$ systemctl is-enabled docker containerd docker.socket
enabled
enabled
enabled
Ubuntu에서는 패키지 설치 시 서비스가 자동 활성화되므로 부팅 시 시작 설정(systemctl enable)은 따로 할 필요가 없다.
non-root 사용자로 docker 실행
데몬은 Unix 소켓 /var/run/docker.sock으로 요청을 받고, 이 소켓은 root:docker 소유에 그룹 쓰기 권한만 열려 있다.
$ id
uid=1000(noble) gid=1000(noble) groups=1000(noble),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),101(lxd)
$ docker ps
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock
$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Oct 18 16:33 /var/run/docker.sock
$ getent group docker
docker:x:986:
사용자를 docker 그룹에 넣는다. 그룹 변경은 현재 세션에 바로 반영되지 않는다.
$ sudo usermod -aG docker $USER
$ id -nG
noble adm cdrom sudo dip plugdev lxd
다시 로그인하기 전에는 sg docker -c나 newgrp docker로 그룹을 적용한 셸에서 실행할 수 있다.
$ sg docker -c 'docker run --rm hello-world' | head -3
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
새로 SSH 접속한 세션에서는 그룹이 반영되어 sudo 없이 동작한다.
$ id -nG
noble adm cdrom sudo dip plugdev lxd docker
$ docker run --rm hello-world | head -2
Hello from Docker!
다만 docker 그룹은 사실상 root 권한이다. 호스트 경로를 마운트하면 root만 읽을 수 있는 파일도 그대로 읽힌다.
$ docker run --rm -v /etc:/host-etc alpine head -1 /host-etc/shadow
root:*:19836:0:99999:7:::
신뢰하는 계정만 그룹에 넣는다. 공유 서버라면 데몬 자체를 사용자 권한으로 돌리는 rootless 모드(dockerd-rootless-setuptool.sh install)를 검토한다.
daemon.json으로 데몬 기본값 설정
설치 직후 기본값을 확인한다. 로그 드라이버는 json-file, 데이터는 /var/lib/docker, live-restore는 꺼져 있다.
$ docker info --format '{{.LoggingDriver}} {{.DockerRootDir}} {{.CgroupDriver}} {{.LiveRestoreEnabled}}'
json-file /var/lib/docker systemd false
$ docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
172.17.0.0/16
기본 설정에서는 로그 파일에 크기 제한이 없다. 1KB 랜덤 문자열을 계속 찍는 컨테이너는 15초 만에 로그가 5MB가 됐다.
$ docker run -d --name noisy alpine sh -c 'while true; do head -c 1000 /dev/urandom | base64 -w0; echo; done'
bf9884de8fa0e7df06324ed3562ee97ff5d06257d2a0cd59eecd0cfee6c18ba7
$ sudo du -h $(docker inspect -f '{{.LogPath}}' noisy)
5.0M /var/lib/docker/containers/bf9884de8fa0.../bf9884de8fa0...-json.log
데몬 설정은 /etc/docker/daemon.json 하나에 모은다. 파일이 없으면 새로 만든다.
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true,
"default-address-pools": [
{ "base": "10.200.0.0/16", "size": 24 }
]
}재시작 전에 --validate로 문법을 검사한다. JSON이 틀리면 데몬이 아예 뜨지 않는다.
$ sudo dockerd --validate --config-file /etc/docker/daemon.json
configuration OK
$ sudo systemctl restart docker
$ docker info --format '{{.LoggingDriver}} {{.LiveRestoreEnabled}}'
json-file true
| 키 | 예시 값 | 효과 |
|---|---|---|
| log-opts.max-size / max-file | 10m / 3 | 컨테이너당 로그를 10MB × 3개로 제한(최대 약 30MB) |
| live-restore | true | dockerd 재시작·업그레이드 중에도 컨테이너 유지 |
| default-address-pools | 10.200.0.0/16, size 24 | docker network create가 쓸 대역. 사내망과 172.17~ 대역이 겹칠 때 변경 |
| data-root | /srv/docker | 이미지·컨테이너·볼륨 저장 위치 |
| registry-mirrors | [“https://mirror.example”] | Docker Hub pull 미러 |
| dns | [“10.0.0.53”] | 컨테이너 기본 DNS 서버 |
로그 로테이션 확인
설정 후 만든 컨테이너에는 옵션이 붙는다. 10MB에 닿기 전이라 아직 파일은 하나다.
$ docker inspect -f '{{.HostConfig.LogConfig}}' noisy
{json-file map[max-file:3 max-size:10m]}
$ sudo sh -c 'ls -lh $(dirname $(docker inspect -f "{{.LogPath}}" noisy))/*.log*'
-rw-r----- 1 root root 9.1M Oct 18 16:36 /var/lib/docker/containers/b3bd8a77efa9.../b3bd8a77efa9...-json.log
회전 동작은 컨테이너별 --log-opt로 크기를 1MB로 낮춰서 확인했다. 파일이 3개에서 더 늘지 않는다.
$ docker run -d --name noisy --log-opt max-size=1m --log-opt max-file=3 alpine sh -c 'while true; do head -c 1000 /dev/urandom | base64 -w0; echo; done'
06c462f85c81ed7d1c11238bd495e06fbda0b11b46c75842402da2c9349331fa
$ sudo sh -c 'ls -lh $(dirname $(docker inspect -f "{{.LogPath}}" noisy))/*.log*' | awk '{print $5, $NF}' | sed 's|/var/lib/docker/containers/[0-9a-f]*/[0-9a-f]\{12\}[0-9a-f]*|.../<id>|'
587K .../<id>-json.log
977K .../<id>-json.log.1
977K .../<id>-json.log.2
live-restore 비교
live-restore: false(기본값)일 때 데몬을 재시작하면 컨테이너가 SIGKILL(137)로 종료된다.
$ docker run -d --name web alpine sleep 3600
384459fc5bc1f16ea8fc11af831fedab3a253024c95a3692c710b95fac5dda51
$ sudo systemctl restart docker
$ docker ps -a --format '{{.Names}} {{.Status}}'
web Exited (137) 2 seconds ago
true로 두면 같은 재시작에도 컨테이너가 그대로 살아 있고 가동 시간도 이어진다.
$ docker ps --format '{{.Names}} {{.Status}}'
noisy Up 15 seconds
$ sudo systemctl restart docker
$ docker ps --format '{{.Names}} {{.Status}}'
noisy Up 19 seconds
네트워크 대역 변경 확인
새로 만드는 사용자 정의 네트워크가 지정한 풀에서 /24씩 받는다. 기존 docker0(bridge) 대역을 바꾸려면 bip 키를 따로 지정해야 한다.
$ docker network create appnet
e5d16babde5df0a08afe740963e267650cd379af73e7e3afa3f8fe2fa62d7a77
$ docker network inspect appnet --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
10.200.0.0/24
데이터 디렉터리 이동
루트 파티션이 작으면 이미지·볼륨이 쌓이는 /var/lib/docker를 큰 디스크로 옮긴다. docker.socket이 살아 있으면 요청이 들어올 때 데몬이 다시 깨어나므로 소켓까지 멈춘다.
$ sudo systemctl stop docker
Stopping 'docker.service', but its triggering units are still active:
docker.socket
$ sudo systemctl stop docker.socket
$ sudo rsync -aHAX /var/lib/docker/ /srv/docker/
daemon.json에 "data-root": "/srv/docker"를 추가하고 시작한다. 기존 이미지가 그대로 보이면 이동이 끝난 것이다.
$ sudo cat /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true,
"default-address-pools": [
{
"base": "10.200.0.0/16",
"size": 24
}
],
"data-root": "/srv/docker"
}
$ sudo systemctl start docker
$ docker info --format '{{.DockerRootDir}}'
/srv/docker
$ docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}'
alpine:latest 13MB
hello-world:latest 25.9kB
정상 동작을 확인한 뒤에 기존 /var/lib/docker를 지운다.
주의사항
| 증상 | 원인 | 조치 |
|---|---|---|
| usermod 후에도 permission denied | 그룹 변경은 새 로그인 세션부터 반영 | 재로그인 또는 newgrp docker / sg docker -c |
| docker 그룹 사용자가 호스트 파일을 읽음 | 소켓 접근 권한 = root 권한 | 신뢰 계정만 추가, 필요하면 rootless 모드 |
| 로그 설정이 기존 컨테이너에 적용되지 않음 | log-opts는 컨테이너 생성 시점에 고정 | 컨테이너 재생성(docker compose up -d –force-recreate 등) |
| daemon.json 수정 후 데몬이 안 뜸 | JSON 문법 오류, 키 충돌 | dockerd –validate –config-file로 먼저 검사 |
| systemctl restart docker가 start-limit-hit로 실패 | 1분 안에 3번 넘게 재시작(StartLimitBurst=3) | sudo systemctl reset-failed docker 후 다시 시작 |
| docker를 멈췄는데 다시 살아남 | docker.socket 소켓 활성화 | docker.socket도 함께 stop |
| live-restore를 켰는데 Swarm에서 거부 | Swarm 모드와 함께 쓸 수 없음 | Swarm 노드에서는 끄고 운용 |
설정을 바꾸며 데몬을 여러 번 재시작하다가 실제로 start-limit-hit를 만났다.
$ sudo systemctl restart docker
Job for docker.service failed.
See "systemctl status docker.service" and "journalctl -xeu docker.service" for details.
$ systemctl status docker --no-pager | head -5
× docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: enabled)
Active: failed (Result: start-limit-hit) since Sun 2026-10-18 16:37:18 UTC; 9s ago
Duration: 1.509s
TriggeredBy: × docker.socket
$ systemctl show docker -p StartLimitBurst -p StartLimitIntervalUSec
StartLimitIntervalUSec=1min
StartLimitBurst=3
$ sudo systemctl reset-failed docker
마무리
설치 직후에는 신뢰할 계정만 docker 그룹에 넣고, daemon.json에 로그 크기 제한과 live-restore를 켜 둔다. 사내망과 겹치면 주소 풀을, 루트 파티션이 작으면 data-root를 바꾼다. 수정할 때마다 dockerd --validate로 검사한 뒤 재시작한다.