도커 중급 강좌 #6 로깅과 디버깅
도커 중급 시리즈의 마지막 글입니다. 여러 컨테이너가 돌아가기 시작하면 가장 먼저 부딪히는 두 문제 — 로그가 어디서 나오는지, 그리고 무언가 안 되면 어디서 들여다볼지를 이번 글에 모았습니다.
도커 중급 강좌 시리즈에서 이번 글의 위치:
- #1 멀티스테이지 빌드와 이미지 슬리밍
- #2 빌드 캐시 — 레이어 순서 최적화
- #3 docker compose 기초 — web + db
- #4 compose 심화 — depends_on, healthcheck, profiles
- #5 환경변수와 secrets 관리
- #6 로깅과 디버깅 ← 이번 글
컨테이너 로깅의 한 줄 원칙 #
도커가 권장하는 로깅 패턴은 단순합니다.
앱은 stdout/stderr로만 로그를 냅니다. 그 흐름을 도커가 받아 처리합니다.
파일에 로그를 쓰지 마세요. 컨테이너의 파일시스템은 휘발성이고 (기초 #4), 운영에서 여러 컨테이너의 로그를 모으려면 표준 출력으로 흘려야 도구들이 잡을 수 있습니다.
이 한 원칙이 정착하면 — docker logs, log driver, fluentd / Loki 같은 외부 수집기까지 모두 같은 출구로 흘러갑니다.
언어별 짧은 노트 #
| 언어 / 프레임워크 | 점검할 포인트 |
|---|---|
| Python | print는 버퍼링됨 → PYTHONUNBUFFERED=1 환경변수로 끔. logging 모듈은 기본 stderr |
| Node.js | console.log/error는 stdout/stderr로 흐름 — OK |
| Go | log.Println은 stderr — OK |
| Django | LOGGING 설정에서 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 |
local | json-file의 효율적인 변형 (압축, 회전 기본) |
journald | systemd journal |
syslog | syslog daemon |
fluentd | fluentd daemon (외부 수집기) |
gelf | Graylog |
awslogs | AWS CloudWatch Logs |
gcplogs | GCP Cloud Logging |
none | 로그 버림 (docker logs도 안 됨) |
컨테이너별 driver 지정 #
docker run -d --log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
myappservices:
web:
image: myapp
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"운영에서 거의 필수인 옵션이 위 두 줄입니다.
max-size: 10m— 한 로그 파일이 10MB를 넘으면 회전(rotate)max-file: 3— 최대 3 개까지 보관, 그 이상은 삭제
이 옵션이 없으면 로그 파일이 무한정 커져 디스크가 차오릅니다. 도커 사고 1순위 중 하나입니다.
데몬 전역 기본값 #
매 서비스마다 로그 옵션을 적기 번거롭다면 데몬 설정에 둘 수 있습니다.
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}이러면 새로 만드는 모든 컨테이너에 위 기본값이 적용됩니다. 데몬 재시작이 필요하니 운영 환경 변경은 점검 시간에.
docker compose logs — 한곳에서
#
여러 서비스의 로그를 한곳에서 보는 것이 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 initializedup --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를 일회용으로 띄워 쓰는 패턴이 있습니다.
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 — 실시간 자원 사용량
#
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 / 4kBdocker stats는 기본으로 모든 컨테이너의 실시간 사용량을 흘립니다. 한 번만 찍고 싶으면:
docker stats --no-stream리소스 한계를 정해 두면 MEM %가 100% 에 닿으면 OOMKilled 됩니다. 운영에선 한계를 명시하는 게 정석입니다.
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기초 #3의 docker system prune과 짝지어 — 디스크가 차오를 때 첫 명령입니다.
이미지 안을 들여다보기 — dive
#
이미지 크기가 의심스럽거나, 어느 레이어에 무엇이 들어갔는지 볼 때 외부 도구 dive가 매우 편합니다.
brew install dive
dive myapp:latestTUI가 떠서 레이어별로 어떤 파일이 추가/수정/삭제됐는지를 한눈에 보여줍니다. 이미지 슬리밍의 단서를 가장 빠르게 찾는 도구 입니다. 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**로 합쳐진 정의를 검증 - 증상별로 첫 명령을 손에 익히면 추적이 빨라진다