Ubuntu에 Docker Engine 설치 후 설정

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-file10m / 3컨테이너당 로그를 10MB × 3개로 제한(최대 약 30MB)
live-restoretruedockerd 재시작·업그레이드 중에도 컨테이너 유지
default-address-pools10.200.0.0/16, size 24docker 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로 검사한 뒤 재시작한다.

참고

답글 남기기