도커 중급 강좌 #6 로깅과 디버깅

7 분 소요

도커 중급 시리즈의 마지막 글입니다. 여러 컨테이너가 돌아가기 시작하면 가장 먼저 부딪히는 두 문제 — 로그가 어디서 나오는지, 그리고 무언가 안 되면 어디서 들여다볼지를 이번 글에 모았습니다.

도커 중급 강좌 시리즈에서 이번 글의 위치:

컨테이너 로깅의 한 줄 원칙 #

도커가 권장하는 로깅 패턴은 단순합니다.

앱은 stdout/stderr로만 로그를 냅니다. 그 흐름을 도커가 받아 처리합니다.

파일에 로그를 쓰지 마세요. 컨테이너의 파일시스템은 휘발성이고 (기초 #4), 운영에서 여러 컨테이너의 로그를 모으려면 표준 출력으로 흘려야 도구들이 잡을 수 있습니다.

이 한 원칙이 정착하면 — docker logs, log driver, fluentd / Loki 같은 외부 수집기까지 모두 같은 출구로 흘러갑니다.

언어별 짧은 노트 #

언어 / 프레임워크점검할 포인트
Pythonprint는 버퍼링됨 → PYTHONUNBUFFERED=1 환경변수로 끔. logging 모듈은 기본 stderr
Node.jsconsole.log/error는 stdout/stderr로 흐름 — OK
Golog.Println은 stderr — OK
DjangoLOGGING 설정에서 handler가 StreamHandler 인지 확인
Nginx기본은 /var/log/nginx/...access_log /dev/stdout; error_log /dev/stderr;로 redirect 하는 패턴이 정석

docker logs 다시 보기 #

기초 #3에서 짚었던 명령을 운영 시각으로 다시.

자주 쓰는 형태
docker logs -f myapp                      # follow
docker logs --tail 200 myapp              # 최근 200 줄
docker logs --since 30m myapp             # 최근 30분
docker logs --since 2026-04-18T10:00 myapp  # 절대 시각
docker logs --until 1h myapp              # 1시간 전까지
docker logs -t myapp                      # 타임스탬프 포함

장애 시각 부근을 잘라보고 싶을 땐 --since + --until 콤보가 유용합니다.

장애 시각 추적
docker logs --since 14:00 --until 14:10 myapp

도커가 stdout/stderr를 어떻게 저장하는지는 그 다음 주제 — log driver입니다.

Log driver — 로그가 어디로 가는가 #

도커는 컨테이너 stdout/stderr를 log driver 라는 모듈을 통해 어딘가로 흘려보냅니다. 기본은 json-file — 호스트의 디스크에 JSON으로 쌓이는 형태입니다.

Driver어디로
json-file (기본)/var/lib/docker/containers/<id>/<id>-json.log
localjson-file의 효율적인 변형 (압축, 회전 기본)
journaldsystemd journal
syslogsyslog daemon
fluentdfluentd daemon (외부 수집기)
gelfGraylog
awslogsAWS CloudWatch Logs
gcplogsGCP Cloud Logging
none로그 버림 (docker logs도 안 됨)

컨테이너별 driver 지정 #

docker run
docker run -d --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp
compose.yaml
services:
  web:
    image: myapp
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

운영에서 거의 필수인 옵션이 위 두 줄입니다.

  • max-size: 10m — 한 로그 파일이 10MB를 넘으면 회전(rotate)
  • max-file: 3 — 최대 3 개까지 보관, 그 이상은 삭제

이 옵션이 없으면 로그 파일이 무한정 커져 디스크가 차오릅니다. 도커 사고 1순위 중 하나입니다.

데몬 전역 기본값 #

매 서비스마다 로그 옵션을 적기 번거롭다면 데몬 설정에 둘 수 있습니다.

/etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

이러면 새로 만드는 모든 컨테이너에 위 기본값이 적용됩니다. 데몬 재시작이 필요하니 운영 환경 변경은 점검 시간에.

docker compose logs — 한곳에서 #

여러 서비스의 로그를 한곳에서 보는 것이 Compose의 큰 장점입니다.

Compose 로그 명령군
docker compose logs                # 모든 서비스
docker compose logs -f             # follow
docker compose logs --tail 100     # 최근 100 줄 (서비스별 각각)
docker compose logs web pg         # 특정 서비스만
docker compose logs --since 10m -f web   # 조합
docker compose logs --no-color     # 색깔 없이 (파일 리다이렉트 시)
docker compose logs --no-log-prefix web  # "web | " prefix 없이

서비스마다 색깔 있는 prefix가 붙어, 어느 서비스의 로그인지 한눈에 보입니다.

출력 예
web-1  | INFO     127.0.0.1 - - [20/May/2026 14:32:01] "GET / HTTP/1.1" 200
pg-1   | LOG:  database system is ready to accept connections
redis-1| 1:M 20 May 2026 14:32:00.123 # Server initialized

up --attach / --no-attach #

docker compose up에서 어떤 서비스의 로그를 따라갈지 고를 수도 있습니다.

로그 필터
docker compose up --attach web --attach worker     # 두 서비스만
docker compose up --no-attach pg --no-attach redis # DB/캐시 빼고

DB 로그가 너무 시끄럽거나, 디버깅 중인 서비스에만 집중하고 싶을 때 유용합니다.

외부 로그 집계 — 한 단락 #

운영 컨테이너가 늘어나면 호스트의 로그 파일을 일일이 보는 게 불가능해집니다. 한 단락으로만 짚어두면:

  • Loki + Promtail + Grafana — 가벼운 자체 호스팅. compose 한 묶음으로 띄울 수 있음
  • Elastic Stack (ELK) — 강력하지만 무거움. Elasticsearch + Logstash + Kibana
  • Datadog / New Relic / Grafana Cloud — 매니지드 SaaS
  • CloudWatch Logs / Cloud Logging — 클라우드 네이티브 환경

대부분 컨테이너 stdout을 받아 인덱싱 → 검색 / 알람으로 잇는 구조입니다. 이번 시리즈의 한 발 너머 주제라 이름만 짚고 갑니다.

디버깅의 첫 도구 — docker exec #

기초 #3에서 본 명령이지만, 디버깅 관점에서 한 번 더 짚겠습니다.

컨테이너 안에서 환경 점검
docker compose exec web sh

# 안에서
ps aux                    # 프로세스 트리
env | sort                # 환경변수
cat /etc/resolv.conf      # DNS 설정
ls -la /app               # 작업 디렉터리

docker compose exec는 떠 있는 컨테이너 안으로 들어가는 명령입니다. distroless / scratch 베이스라면 셸이 없어 안 됩니다. (중급 #1의 트레이드오프.) 그럴 땐 debug image를 일회용으로 띄워 쓰는 패턴이 있습니다.

distroless 컨테이너 옆에 디버그 컨테이너
docker run -it --rm \
  --network container:myapp-web-1 \
  --pid container:myapp-web-1 \
  nicolaka/netshoot

--network container:X--pid container:X로 대상 컨테이너의 네트워크와 프로세스 네임스페이스를 공유합니다. 그러면 nicolaka/netshoot (네트워크 디버깅 툴 모음) 안에서 curl localhost:8000 같은 명령이 대상 컨테이너의 8000 포트로 닿습니다. 격리는 풀고 도구만 빌리는 패턴.

docker inspect — 정밀 진단 #

JSON으로 펼쳐진 상태 정보를 들여다볼 때 씁니다.

자주 뽑는 값들
# 상태
docker inspect myapp-web-1 --format '{{.State.Status}}'

# Health
docker inspect myapp-pg-1 --format '{{json .State.Health}}' | jq

# 네트워크
docker inspect myapp-web-1 --format '{{json .NetworkSettings.Networks}}' | jq

# 마운트
docker inspect myapp-web-1 --format '{{json .Mounts}}' | jq

# 시작 시각
docker inspect myapp-web-1 --format '{{.State.StartedAt}}'

# OOM으로 죽었는지
docker inspect myapp-web-1 --format '{{.State.OOMKilled}}'

OOMKilled: true가 보이면 메모리 한계로 컨테이너가 죽은 것 — 다음에 볼 docker stats와 함께 추적하면 답이 나옵니다.

docker stats — 실시간 자원 사용량 #

모든 컨테이너의 CPU/메모리/IO
docker stats
# CONTAINER ID   NAME           CPU %   MEM USAGE / LIMIT     MEM %   NET I/O
# a1b2c3d4...    myapp-web-1    1.2%    120MiB / 7.7GiB       1.5%    12kB / 8kB
# e5f6g7h8...    myapp-pg-1     0.1%    35MiB / 7.7GiB        0.4%    5kB / 4kB

docker stats는 기본으로 모든 컨테이너의 실시간 사용량을 흘립니다. 한 번만 찍고 싶으면:

한 스냅샷만
docker stats --no-stream

리소스 한계를 정해 두면 MEM %가 100% 에 닿으면 OOMKilled 됩니다. 운영에선 한계를 명시하는 게 정석입니다.

compose.yaml — 자원 한계
services:
  web:
    image: myapp
    mem_limit: 512m
    cpus: 1.0
    # 또는 deploy 키 (Swarm) — 일반 compose에선 위 형태가 동작

디스크 점유 — docker system df #

이미지, 컨테이너, 볼륨이 디스크를 얼마나 차지하는지 한눈에 확인할 수 있습니다.

한곳 요약
docker system df
# TYPE           TOTAL   ACTIVE   SIZE      RECLAIMABLE
# Images         18      4        3.2GB     2.1GB (65%)
# Containers     6       1        12MB      5MB (41%)
# Local Volumes  8       3        420MB     280MB (66%)
자세히
docker system df -v

기초 #3docker system prune과 짝지어 — 디스크가 차오를 때 첫 명령입니다.

이미지 안을 들여다보기 — dive #

이미지 크기가 의심스럽거나, 어느 레이어에 무엇이 들어갔는지 볼 때 외부 도구 dive가 매우 편합니다.

dive 설치 (Homebrew)
brew install dive

dive myapp:latest

TUI가 떠서 레이어별로 어떤 파일이 추가/수정/삭제됐는지를 한눈에 보여줍니다. 이미지 슬리밍의 단서를 가장 빠르게 찾는 도구 입니다. CI에서 비대화 회귀를 막는 게이트로도 쓰입니다 (이미지 효율 점수가 임계 이하면 빌드 실패).

docker compose top — 컨테이너 안 프로세스 #

안의 프로세스 트리
docker compose top
# myapp-web-1
# UID    PID    PPID    CMD
# root   1234   1200    python app.py
# root   1456   1234    python app.py (worker)

exec로 들어가 ps aux를 보는 것과 비슷하지만, 한 명령으로 모든 서비스를 묶어 봅니다. 좀비 프로세스나 의도치 않은 자식 프로세스가 있는지 빠르게 확인할 수 있습니다.

docker events — 도커가 일어난 일을 흘림 #

장애 추적이나 자동화에 가끔 쓰이는 명령입니다.

실시간 이벤트 스트림
docker events --filter container=myapp-web-1
# 2026-04-18T14:32:01 ... container die ...
# 2026-04-18T14:32:01 ... container start ...

컨테이너가 자꾸 다시 떴다 죽었다를 반복할 때, 이걸로 패턴을 잡습니다.

자주 만나는 디버깅 흐름 #

증상첫 명령
컨테이너가 자꾸 종료됨docker logs <c>, docker inspect <c> --format '{{.State.OOMKilled}}'
외부에서 포트가 안 닿음docker port <c>, 컨테이너 안에서 0.0.0.0 바인딩 확인
컨테이너 사이 통신 안 됨같은 네트워크인지(inspect), 서비스 이름 오타
디스크가 가득 참docker system df, prune
빌드가 너무 큼dive, docker history
Compose가 의도대로 안 합쳐짐docker compose config
환경변수가 안 박힘docker compose run web env, #5의 우선순위 표

시리즈를 마무리하며 #

중급 시리즈에서 다진 도구를 한 그림으로 정리하면 다음과 같습니다.

도커 중급의 도구 지도
   빌드 효율                     실행 / 운영
   ────────                       ─────────
   #1 멀티스테이지                #3 compose — 여러 컨테이너
   #2 BuildKit 캐시               #4 healthcheck, depends_on, profiles
   #2 mount cache                 #5 환경변수와 secrets
   #2 외부 캐시                   #6 로깅과 디버깅

기초 시리즈에서 잡은 한 컨테이너의 사이클 위에, 중급 시리즈는 여러 컨테이너 + 운영 감각을 얹었습니다. compose 한 파일로 한 명령으로 띄우고, healthcheck로 의미 있는 시작 순서를 잡고, 비밀을 이미지 바깥에 두고, 로그를 한곳에서 보고 디버깅하는 단계까지 왔습니다.

다음 시리즈는 도커 고급 입니다. BuildKit의 더 깊은 기능, 멀티 아키텍처 빌드, 이미지 보안(non-root, distroless, Trivy 스캔, SBOM, cosign 서명), 리소스 제한과 cgroups, 그리고 프로덕션 운영의 잔주름을 다룹니다. 이번 시리즈에서 잡은 compose 위에 한 단계 더 올라가는 내용입니다.

정리 #

이번 글에서 잡은 그림:

  • 앱은 stdout/stderr로만 로그를 내고, 도커가 처리한다 — 파일 로깅 금지
  • docker logs --since/--until/--tail로 시각 / 분량을 제한해 본다
  • max-size + max-file 로그 옵션이 운영 필수 — 디스크 폭주 방지
  • docker compose logs로 여러 서비스를 한곳에서, prefix 색깔로 구분
  • **docker exec + inspect + stats**가 디버깅 첫 세 도구. distroless 면 --network container: 트릭으로 디버그 컨테이너 옆에 띄우기
  • **dive**로 이미지 안을, **docker system df + prune**으로 디스크를, **docker compose config**로 합쳐진 정의를 검증
  • 증상별로 첫 명령을 손에 익히면 추적이 빨라진다
X