infra
Platform

모듈 맵

[Docker] 컨테이너 실행 옵션과 네임스페이스 작동 원리

0 / 27 완료

펼치기
0 / 27 완료0%

도커 & 컨테이너 · 02 / 27

[Docker] 컨테이너 실행 옵션과 네임스페이스 작동 원리

docker run의 모든 옵션을 이해하고, 이미지·컨테이너·레지스트리의 관계를 파악합니다

🚨INCIDENT ALERT
HIGH

팀에 합류한 첫날, 선배가 docker run 명령어 하나를 던져줬습니다. 복붙해서 실행했더니 컨테이너는 떠 있는데 브라우저에서는 연결이 안 됩니다.

확인해보니 -p 옵션이 빠져 있었고, 이름을 붙이지 않아 어떤 컨테이너를 봐야 하는지도 헷갈렸습니다. docker run 한 줄 안에는 이미지 다운로드, 컨테이너 생성, 네트워크 설정, 프로세스 실행이 모두 압축되어 있습니다. 옵션 하나가 빠지면 전혀 다른 상황이 됩니다.

docker run 완전 해부 — 이미지, 컨테이너, 레지스트리

팀에 합류한 첫 날, 선배가 docker run 명령어 하나를 던져줬다. 복붙해서 실행했더니 뭔가 돌아가긴 했는데 — 브라우저에선 연결이 안 됐다. -p 옵션이 빠져 있어서였다. 포트 포워딩이 뭔지 몰랐기 때문에 원인을 찾는 데 한 시간이 걸렸다. docker run 한 줄 안에는 이미지 다운로드, 컨테이너 생성, 네트워크 설정, 프로세스 실행이 압축돼 있다. 옵션 하나가 빠지면 전혀 다른 상황이 된다. 명령어를 외우기 전에 각 옵션이 없으면 어떤 일이 벌어지는지를 먼저 알면, 이후엔 절대 잊지 않는다.

docker run 한 줄로 서비스가 뜹니다. 하지만 그 한 줄 안에는 이미지 다운로드, 컨테이너 생성, 네트워크 설정, 프로세스 실행까지 여러 단계가 압축되어 있습니다. 이 모듈에서는 docker run의 각 옵션이 어떤 의미인지, 이미지와 컨테이너와 레지스트리가 어떤 관계인지 완전히 이해합니다.

이번 챕터에서 배울 것
  • 1이미지, 컨테이너, 레지스트리의 관계를 소스코드와 바이너리와 저장소에 비유해 설명할 수 있다
  • 2docker run의 필수 옵션 -d, -p, --name, -e, -v를 해부해 활용할 수 있다
  • 3docker ps, docker logs, docker exec으로 컨테이너를 조회하고 접속할 수 있다
  • 4이름 없이 실행했을 때 생기는 문제를 통해 --name의 중요성을 설명할 수 있다
실습 환경 준비
Docker 실행 여부 확인
docker info
기존 컨테이너 목록 확인
docker ps -a
로컬에 저장된 이미지 목록 확인
docker images
💡개념

이미지, 컨테이너, 레지스트리 — 세 개념의 관계

레지스트리·이미지·컨테이너 관계 — 레지스트리(Docker Hub 등)는 이미지를 보관하는 저장소, 이미지는 실행에 필요한 파일·설정을 담은 읽기 전용 템플릿, 컨테이너는 그 이미지로 실행한 살아있는 인스턴스. docker pull로 이미지를 받고 docker run으로 컨테이너를 띄움(이미지 하나로 여러 컨테이너 가능)확대

세 가지 핵심 개념

Docker를 처음 배울 때 가장 헷갈리는 것이 이미지와 컨테이너의 차이입니다. 친숙한 비유로 설명합니다.

소스코드 → 실행 파일 → 실행 중인 프로그램에 빗대면:

Docker 개념비유특성
이미지실행 파일(.exe, 바이너리)읽기 전용, 불변, 저장 가능
컨테이너실행 중인 프로그램 인스턴스읽기/쓰기 가능, 일시적
레지스트리앱스토어 / 아티팩트 저장소이미지를 push/pull

하나의 이미지로 여러 컨테이너를 동시에 실행할 수 있습니다. nginx 이미지 하나로 포트 8080, 8081, 8082에 컨테이너 3개를 띄울 수 있습니다.

이미지 하나가 여러 컨테이너로 실행되는 흐름입니다.

  1. 레지스트리 (Docker Hub / ECR / GCR) → docker pull
  2. 이미지 (로컬 캐시)nginx:latest, ubuntu:22.04, myapp:v1.2.3docker run (여러 개 가능)
  3. 컨테이너 — 같은 이미지로 여러 개:
    • 컨테이너 A (web-prod) — 포트 8080
    • 컨테이너 B (web-staging) — 포트 8081
    • 컨테이너 C (web-test) — 포트 8082

docker run 실행 시 일어나는 일

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/docker/part1/exam_2 && cd /tmp/docker/part1/exam_2

docker run -d -p 8080:80 --name my-nginx nginx:latest

이 명령을 실행하면 Docker가 순서대로 다음을 수행합니다:

  1. 로컬 이미지 캐시에서 nginx:latest 검색
  2. 없으면 Docker Hub에서 자동으로 pull
  3. 이미지를 기반으로 새 컨테이너 생성 (쓰기 가능한 레이어 추가)
  4. 네트워크 설정 (가상 NIC 생성, IP 할당)
  5. 포트 바인딩 (호스트 8080 → 컨테이너 80)
  6. 컨테이너 내부에서 nginx 프로세스 실행
💡개념

docker run 한 줄이 컨테이너가 되기까지 — 이미지에서 PID 1까지 6단계

docker run -d -p 8080:80 --name web nginx 한 줄. Enter를 누르면 몇 초 뒤 docker psweb 컨테이너가 Up으로 뜹니다. 그 짧은 사이 Docker는 이미지 확인 → 쓰기 레이어 생성 → 네트워크 설정 → 볼륨 마운트 → PID 1 프로세스 시작을 순서대로 밟습니다. 이 흐름을 단계로 알면 "왜 안 뜨지", "왜 바로 죽지"를 어느 단계에서 끊겼는지로 좁힐 수 있습니다. docker run은 단순히 "이미지를 켜는 것"이 아니라 아래 6단계의 결과입니다.

TEXT
[호스트]  docker run -d -p 8080:80 --name web nginx
   │
   ① 이미지 확인        로컬에 nginx 있나? → 없으면 레지스트리에서 pull
   │
   ② 쓰기 레이어 생성    읽기전용 이미지 위에 얇은 쓰기 레이어(RW)를 얹어 컨테이너 생성
   │
   ③ 네트워크 설정      새 net namespace + veth 쌍 → docker0 브리지 연결 → IP 할당
   │                    -p 8080:80 은 iptables NAT로 호스트 8080 → 컨테이너 80 매핑
   │
   ④ 볼륨·마운트        -v / --mount 로 지정한 볼륨을 컨테이너 파일시스템에 연결
   │
   ⑤ PID 1 시작        ENTRYPOINT + CMD 를 새 PID namespace 안의 PID 1로 exec
   │
   ⑥ 실행 유지         PID 1이 살아있으면 Running, 끝나면 컨테이너도 종료
   ▼
[결과]  docker ps 에 web (Up) — 실패하면 이 단계 전에 끊긴다

각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:

단계하는 일여기서 막히면
① 이미지 확인로컬 캐시에서 태그를 찾고, 없으면 레지스트리에서 pull오타·비공개·없는 태그 → pull access denied / manifest unknown, 네트워크 불가 시 Unable to find image 후 실패
② 쓰기 레이어읽기전용 이미지 위에 컨테이너 전용 쓰기 레이어를 얹어 생성(created)디스크 부족 → no space left on device
③ 네트워크veth·브리지·IP 할당, -p는 iptables NAT로 포트 매핑호스트 포트 사용 중 → port is already allocated
④ 볼륨 마운트-v·--mount로 지정한 볼륨·호스트 경로를 컨테이너에 연결잘못된 경로·권한 문제로 마운트 실패
⑤ PID 1 시작ENTRYPOINT+CMD를 PID 1로 exec실행 파일 없음 → exec: "..." : executable file not found / ENTRYPOINT 스크립트 오류
⑥ 실행 유지PID 1이 살아있는 동안만 RunningPID 1이 바로 끝남 → Exited (0)(할 일 끝남·-it 없는 bash) / 에러면 Exited (1) 등 비정상

"컨테이너가 떴다"는 ①~⑥이 모두 통과했다는 뜻입니다. 안 뜰 때는 에러 메시지로 단계를 좁힙니다 — Unable to find image·pull access denied는 ①, port is already allocated는 ③, executable file not found는 ⑤, docker ps엔 없고 docker ps -aExited면 ⑥입니다. 특히 ⑥의 Exited (0)은 에러가 아니라 "PID 1이 할 일을 마치고 정상 종료"라는 뜻이라 docker logs에 오류가 없습니다 — 이때는 포그라운드로 계속 살아있을 프로세스(nginx, sleep infinity)나 -it 셸이 필요합니다.

💡개념

docker run 옵션 완전 해부

docker run 명령은 옵션 하나가 빠지면 전혀 다른 상황이 펼쳐집니다. -d 없이 실행하면 터미널이 점유되어 Ctrl+C로 서비스를 끄는 실수를 하고, --name 없이 실행하면 컨테이너 ID나 무작위 이름을 외워야 합니다. -p 없이 실행하면 서비스가 외부에서 접근되지 않아 왜 연결이 안 되는지 찾아 헤매게 됩니다. 각 옵션이 없을 때 어떤 상황이 되는지를 먼저 이해하면, 왜 이 옵션들이 거의 항상 함께 쓰이는지 자연스럽게 납득됩니다. 이 ConceptBlock에서는 실무에서 가장 자주 사용하는 docker run 옵션과 그 의미를 다룹니다.

docker run 주요 옵션 — -d, -p, --name, -e, -v확대

가장 자주 쓰는 옵션들

-d (detached, 백그라운드 실행)

Docker
# 포어그라운드 실행 — 터미널 점유, Ctrl+C로 종료
docker run nginx

# 백그라운드 실행 — 컨테이너 ID만 출력하고 프롬프트 반환
docker run -d nginx
# a3f4b1c2d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5  ← 컨테이너 ID

-p (포트 바인딩)

Docker
# 형식: -p 호스트포트:컨테이너포트
docker run -d -p 8080:80 nginx
# 브라우저에서 http://localhost:8080 접속 → 컨테이너의 80포트

# 여러 포트 바인딩
docker run -d -p 8080:80 -p 8443:443 nginx

# 특정 IP에만 바인딩 (보안)
docker run -d -p 127.0.0.1:8080:80 nginx
# 로컬호스트에서만 접근 가능, 외부 접근 차단

# 호스트 포트를 지정하지 않으면 랜덤 포트
docker run -d -p 80 nginx
docker port <container_id>  # 할당된 포트 확인

--name (컨테이너 이름 지정)

Docker
# 이름 없이 실행 — Docker가 무작위 이름 생성
docker run -d nginx
# 이름: "romantic_turing" 같은 무작위 이름 → 관리하기 어려움

# 이름 지정 — 이름으로 참조 가능
docker run -d --name web-server nginx

# 이름으로 조작 가능
docker stop web-server
docker start web-server
docker logs web-server
docker exec -it web-server bash

-e (환경변수)

Docker
# 환경변수 설정
docker run -d \
  --name db \
  -e POSTGRES_PASSWORD=mysecret \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_DB=mydb \
  postgres:15

# .env 파일에서 읽기
docker run -d --env-file .env myapp:latest

-v (볼륨, 데이터 영구 보존)

Docker
# 컨테이너 내부 데이터는 컨테이너 삭제 시 사라짐
# 볼륨 마운트로 영구 보존

# Named Volume (Docker가 관리)
docker run -d -v db-data:/var/lib/postgresql/data postgres:15

# Bind Mount (호스트 경로 직접 마운트)
docker run -d -v /home/user/myapp:/app myapp:latest
# 호스트 /home/user/myapp ↔ 컨테이너 /app 동기화

--rm (실행 후 자동 삭제)

Docker
# 일회성 명령 실행 후 자동 정리
docker run --rm ubuntu:22.04 echo "Hello, World!"
docker run --rm python:3.11 python -c "import sys; print(sys.version)"

# --rm 없이 실행하면 컨테이너가 Exited 상태로 남아있음
docker run ubuntu:22.04 echo "Hello"
docker ps -a  # Exited 상태 컨테이너 확인

-it (인터랙티브 + 터미널)

Docker
# 컨테이너 안에서 직접 명령 실행 (쉘 접속)
docker run -it ubuntu:22.04 bash
# 컨테이너 쉘 프롬프트가 열림
# root@a3f4b1c2:/# ls /
# root@a3f4b1c2:/# apt-get update
# exit 또는 Ctrl+D로 나옴

# 실행 중인 컨테이너에 접속
docker exec -it web-server bash
💡개념

컨테이너 조회와 디버깅 — ps, logs, exec

컨테이너를 실행한 후에는 상태 확인, 로그 조회, 내부 접속 세 가지 동작이 가장 자주 필요합니다. ps, logs, exec는 이 세 가지를 담당하는 기본 진단 명령어입니다.

docker ps / logs / exec — 상태 확인·로그·내부 접속확대

로컬 터미널
# 실행 중인 컨테이너 목록
docker ps

# 모든 컨테이너 (정지된 것 포함)
docker ps -a

# 컨테이너 로그 확인
docker logs web-server

# 실시간 로그 스트리밍 (tail -f처럼)
docker logs -f web-server

# 최근 50줄만
docker logs --tail 50 web-server

# 실행 중인 컨테이너 내부에서 명령 실행
docker exec web-server ls /etc/nginx
docker exec -it web-server bash  # 인터랙티브 쉘

# 컨테이너 상세 정보 (IP, 포트, 환경변수, 마운트 등)
docker inspect web-server

# 컨테이너 리소스 사용량 실시간 모니터링
docker stats

컨테이너 정지(stop/kill)와 상태 전환(start/rm)은 다음 모듈(컨테이너 생명주기)에서 자세히 다룹니다.

1nginx 컨테이너를 실행하고 run→ps→curl→logs→stop/start 흐름 익히기

직접 nginx 웹 서버를 컨테이너로 실행하고 관리해봅니다. 실행→상태확인→접속→로그→재시작→정리까지 한 사이클을 돌리며 각 명령의 출력을 눈에 익힙니다.

Docker
# 1. nginx 컨테이너 실행
docker run -d \
  --name my-nginx \
  -p 8080:80 \
  nginx:alpine

# 2. 실행 확인
docker ps
# CONTAINER ID   IMAGE          COMMAND                  STATUS         PORTS
# a3f4b1c2d0e9   nginx:alpine   "/docker-entrypoint.…"   Up 5 seconds   0.0.0.0:8080->80/tcp

# 3. 접속 테스트
curl http://localhost:8080
# HTML 응답이 오면 성공

# 4. 로그 확인
docker logs my-nginx
# 127.0.0.1 - - [11/Apr/2026:10:00:00 +0000] "GET / HTTP/1.1" 200 ...

# 5. 컨테이너 내부 파일 확인
docker exec my-nginx ls /etc/nginx
docker exec -it my-nginx sh  # Alpine은 bash 대신 sh

# 6. 컨테이너 정지 후 재시작
docker stop my-nginx
docker ps -a  # Exited 상태 확인
docker start my-nginx
curl http://localhost:8080  # 다시 응답

# 7. 정리
docker stop my-nginx
docker rm my-nginx
docker images  # nginx 이미지는 로컬에 캐시됨
docker run -d --name my-nginx -p 8080:80 nginx:alpine
🔍실행 후 확인할 것
  • 먼저 docker ps 에서 STATUS 열을 확인한다 — 그 다음 PORTS 열의 0.0.0.0:8080->80/tcp 형식을 읽는다. 0.0.0.0은 모든 인터페이스에서 수신한다는 뜻이고, 127.0.0.1:8080->80/tcp이면 localhost에서만 접근 가능하다. 클라우드 서버에서 외부 접근이 안 되면 이 차이를 먼저 확인한다
  • docker ps -a 에서 STATUS가 Exited (0)이면 정상 종료, Exited (1)이면 에러 종료, Exited (137)이면 OOM(메모리 부족)으로 강제 종료된 것이다 — 137은 128+9(SIGKILL)로, docker logs 에서 'Killed' 메시지와 함께 나타난다
  • docker ps 와 docker ps -a 의 출력 행 수가 다르면 → 종료된 컨테이너가 존재하는 것이다. 행 수 차이가 5 이상이면 --rm 없이 실행된 일회성 컨테이너가 쌓인 것으로, docker container prune 으로 정리한다

환경변수로 설정을 주입하는 실습:

Docker
# PostgreSQL 컨테이너 실행 (환경변수로 설정 주입)
docker run -d \
  --name my-postgres \
  -e POSTGRES_PASSWORD=mypassword \
  -e POSTGRES_USER=myuser \
  -e POSTGRES_DB=mydb \
  -p 5432:5432 \
  postgres:15-alpine

# 실행 확인
docker ps
docker logs my-postgres  # 초기화 로그 확인

# 접속 테스트 (psql 클라이언트가 있는 경우)
docker exec -it my-postgres psql -U myuser -d mydb

# 또는 다른 컨테이너에서 접속
docker run --rm \
  --link my-postgres:db \
  postgres:15-alpine \
  psql -h db -U myuser -d mydb -c "SELECT version();"

# 정리
docker stop my-postgres
docker rm my-postgres

docker run --name web nginx를 다시 실행하면 같은 이름의 컨테이너가 이미 있다는 에러가 납니다. 이전에 실행했다가 정지된 컨테이너가 남아있기 때문입니다.

Docker
# 에러 상황
docker run -d --name web nginx
# Error response from daemon: Conflict. The container name "/web" is already in use
# by container "a3f4b1c2...". You have to remove (or rename) that container to be
# able to reuse that name.

# 원인 파악: 정지된 컨테이너 확인
docker ps -a | grep web
# a3f4b1c2  nginx  ...  Exited (0) 5 minutes ago  web

# 해결 1: 기존 컨테이너 삭제 후 재실행
docker rm web
docker run -d --name web nginx

# 해결 2: 기존 컨테이너 강제 삭제 후 재실행 (실행 중이어도)
docker run -d --name web nginx

# 해결 3: 스크립트에서 항상 깔끔하게 재시작하는 패턴
docker run -d --name web -p 80:80 nginx

# 예방: --rm 옵션 사용 (종료 시 자동 삭제)
# 단, --rm은 서버 데몬(-d)과 함께 쓰기엔 부적합
# 일회성 실행에만 사용
docker run --rm --name temp-job myapp:latest python run_job.py
위험 명령어컨테이너가 즉시 종료되고 컨테이너 내부 임시 데이터가 사라질 수 있습니다.

실행 중인 컨테이너 강제 삭제

안전한 실행 조건: 삭제 대상이 실습용 컨테이너이며 필요한 로그나 데이터가 남아있지 않은 경우에만 실행합니다.

실행 전 반드시 확인

  • docker ps -a로 컨테이너 이름이 web인지 확인했다
  • 운영 또는 공유 개발 컨테이너가 아님을 확인했다
  • 필요한 로그와 데이터 백업이 끝났다
docker rm -f web

위 항목을 모두 확인한 후 복사할 수 있습니다

핵심: docker ps -adocker ps와 다른 결과를 보여줄 때, 정지된 컨테이너가 남아있는 것입니다. 개발 중에는 docker container prune으로 정지된 컨테이너를 한 번에 정리하는 것이 편합니다.

증상

docker run으로 컨테이너를 실행했는데, docker ps에 아무것도 나타나지 않습니다.

Docker
docker run ubuntu
# (아무 출력 없음, 또는 환경 변수 출력 후 종료)

docker ps
# CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES
# (비어있음)

docker ps -a
# CONTAINER ID   IMAGE    COMMAND       STATUS
# abc123         ubuntu   "/bin/bash"   Exited (0) 3 seconds ago

원인

컨테이너는 CMD/ENTRYPOINT에 지정된 프로세스가 살아있는 동안만 실행됩니다. ubuntu 이미지의 기본 CMD는 /bin/bash인데, -it(대화형 터미널) 없이 실행하면 bash가 입력을 받을 곳이 없어서 즉시 종료됩니다.

docker run ubuntu   → bash 실행 → 입력 없음 → bash 종료 → 컨테이너 종료

해결

Docker
# 방법 1: 대화형 터미널로 실행
docker run -it ubuntu bash
# root@abc123:/#  ← 대화형 쉘 접속됨

# 방법 2: 백그라운드에서 지속적으로 실행될 명령 지정
docker run -d ubuntu sleep infinity
docker ps  # 이제 Running 상태로 보임

# 방법 3: 웹서버 등 포어그라운드로 실행되는 프로세스
docker run -d nginx  # nginx는 포어그라운드 실행 → 컨테이너 유지됨

# 방법 4: 일회성 명령 실행 후 종료 (의도한 동작)
docker run --rm ubuntu echo "Hello"
# Hello
# → --rm 옵션으로 종료 후 컨테이너 자동 삭제

증상

포트를 지정해서 컨테이너를 실행했는데, 포트 바인딩 오류가 납니다.

Docker
docker run -d -p 5432:5432 postgres:15
# docker: Error response from daemon: driver failed programming external connectivity
# on endpoint postgres: Bind for 0.0.0.0:5432 failed: port is already allocated.

원인 진단

로컬 터미널
# 해당 포트를 사용하는 프로세스 확인
ss -tlnp | grep :5432
# LISTEN  0  128  0.0.0.0:5432  0.0.0.0:*  users:(("postgres",pid=1234,fd=5))

# 또는
sudo lsof -i :5432
# COMMAND  PID     USER   FD   TYPE  DEVICE  SIZE/OFF  NODE  NAME
# postgres 1234  postgres  5u  IPv4  123456       0t0  TCP  *:postgresql (LISTEN)

해결

Docker
# 방법 1: 다른 포트로 바인딩 (호스트 포트만 변경)
docker run -d -p 5433:5432 postgres:15
# → 호스트 5433 포트로 접근

# 방법 2: 충돌하는 로컬 서비스 중지 후 실행
sudo systemctl stop postgresql  # 로컬 PostgreSQL 중지
docker run -d -p 5432:5432 postgres:15

# 방법 3: 이미 실행 중인 컨테이너 확인 (같은 이름으로 재실행한 경우)
docker ps -a | grep postgres
docker stop <컨테이너 ID> && docker rm <컨테이너 ID>
docker run -d -p 5432:5432 postgres:15

심화 — docker run이 실제로 만드는 것: 네임스페이스와 PID 1

💡개념

심화: 컨테이너는 격리된 프로세스다 — 네임스페이스·cgroup·PID 1

docker run이 '가벼운 VM을 하나 띄운다'고 이해하면, 정작 신호(signal) 처리·좀비 프로세스·자원 제한에서 예상 못 한 동작을 만납니다. 컨테이너는 별도 OS가 아니라 호스트 커널을 공유하는, 격리된 하나의 프로세스입니다. 그 '격리'가 어떻게 만들어지는지 알아야 컨테이너의 한계와 함정이 보입니다.

  • 네임스페이스(namespace) = 격리: docker run은 새 PID·NET·MNT·UTS·IPC 네임스페이스를 만들어 프로세스에 '격리된 시야'를 줍니다. 그래서 컨테이너 안에서 ps를 하면 자기 프로세스만 보이고(PID 네임스페이스), 자기 네트워크 인터페이스만 보이며(NET 네임스페이스), 자기 파일시스템만 보입니다(MNT 네임스페이스). 하지만 커널은 호스트와 공유합니다 — 그래서 VM보다 가볍고, 대신 호스트 커널 버전에 종속됩니다.
  • cgroup = 제한: --memory·--cpus는 네임스페이스가 아니라 cgroup으로 걸립니다. '무엇이 보이는가(격리)'와 '얼마나 쓸 수 있는가(제한)'는 서로 다른 축입니다.
  • PID 1의 특수성: 컨테이너의 첫 프로세스는 그 PID 네임스페이스 안에서 PID 1로 뜹니다. 리눅스에서 PID 1은 특별합니다 — (1) 신호 핸들러를 직접 등록하지 않으면 SIGTERM 같은 기본 종료 신호가 무시되고, (2) 부모를 잃은 고아 프로세스를 거둬들이는(reap) 책임을 집니다. 앱을 아무 준비 없이 PID 1로 띄우면 graceful shutdown이 안 되고 좀비 프로세스가 쌓일 수 있습니다.

즉 컨테이너를 '작은 VM'이 아니라 '네임스페이스로 격리되고 cgroup으로 제한된 프로세스'로 이해해야, docker stop이 왜 신호를 보내는지·왜 PID 1 설계가 중요한지가 한 번에 이어집니다.

상황: docker stop web을 하면 프롬프트가 정확히 10초 동안 멈췄다가 종료됩니다. 앱 로그에는 '연결 정리 완료' 같은 graceful shutdown 메시지가 전혀 남지 않고, 진행 중이던 요청은 끊깁니다.

원인: docker stop은 PID 1에 먼저 SIGTERM을 보내고 기본 10초를 기다린 뒤, 그래도 살아있으면 SIGKILL로 강제 종료합니다. Dockerfile의 CMD를 셸 형식(CMD node app.js)으로 쓰면 실제로는 /bin/sh -c 가 PID 1이 되고 앱은 그 자식으로 뜹니다. sh는 받은 SIGTERM을 자식에게 전달하지 않기 때문에, 앱은 종료 신호를 못 받고 10초 유예가 지난 뒤 SIGKILL로 강제로 죽습니다 — 그래서 매번 정확히 10초입니다.

진단: docker exec web ps -o pid,comm 으로 PID 1이 무엇인지 확인합니다. PID 1이 앱(node 등)이 아니라 sh이면 신호 전달 문제가 확정입니다. docker stop 대신 docker kill --signal=TERM web으로 직접 SIGTERM을 보내도 앱이 반응하지 않는지 함께 봅니다.

해결: CMD를 exec 형식(JSON 배열 형식, 대괄호 안에 실행 파일과 인자를 각각 넣는 방식)으로 바꿔 앱이 직접 PID 1이 되게 합니다. 앱이 여러 자식을 거느리거나 신호 처리를 맡기고 싶으면 docker run --init(내부적으로 tini 사용)으로 작은 init을 PID 1에 두어 신호 전달·좀비 reaping을 대신 처리합니다. 그다음 앱에 SIGTERM 핸들러를 두어 연결을 드레이닝한 뒤 종료하도록 만들면, docker stop이 10초가 아니라 즉시 깔끔하게 끝납니다.

💼
실무 맥락
현업 패턴

시나리오: 신규 서비스 로컬 개발 환경 세팅 — "README 없이도 팀원 누구나 5분 안에 실행"

신규 팀원이 합류했을 때 "환경 세팅하는 데 이틀이 걸렸어요"라는 말을 없애고 싶습니다. Docker를 활용해 로컬 개발 환경을 표준화하는 패턴입니다.

Docker
# 기존 방식: README에 적힌 20단계 설치 과정
# 1. Python 3.9 설치
# 2. venv 생성
# 3. pip install -r requirements.txt
# 4. PostgreSQL 설치
# 5. DB 초기화
# ...
# 에러가 나면 "저 환경에서는 됐는데요"

# Docker 방식: 한 줄로 모든 의존성 실행
docker run -d \
  --name dev-postgres \
  -e POSTGRES_PASSWORD=dev \
  -e POSTGRES_USER=dev \
  -e POSTGRES_DB=myapp_dev \
  -p 5432:5432 \
  postgres:15-alpine

docker run -d \
  --name dev-redis \
  -p 6379:6379 \
  redis:7-alpine

# 앱만 로컬에서 실행 (핫리로드 등 개발 편의 유지)
# 의존 서비스(DB, 캐시, 메시지큐)는 컨테이너로
DATABASE_URL=postgresql://dev:dev@localhost:5432/myapp_dev \
REDIS_URL=redis://localhost:6379 \
python main.py

# 팀원 온보딩 체크리스트 (Docker로 간소화)
echo "Docker 설치 확인..."
docker --version

echo "개발용 DB 시작..."
docker run -d --name dev-postgres \
  -e POSTGRES_PASSWORD=dev \
  -p 5432:5432 \
  postgres:15-alpine

echo "개발용 Redis 시작..."
docker run -d --name dev-redis -p 6379:6379 redis:7-alpine

echo "준비 완료! 앱을 시작하세요: python main.py"

# 퇴근 시 정리 (선택)
docker stop dev-postgres dev-redis

# 출근 시 재시작 (이미지 재다운로드 없음)
docker start dev-postgres dev-redis

실무 포인트: 개발 의존 서비스(DB, 캐시, 큐)를 컨테이너화하면 팀원마다 "다른 버전의 PostgreSQL"로 인한 버그를 없앨 수 있습니다. 단, 앱 자체는 로컬에서 직접 실행하면서 핫리로드와 디버거를 활용하는 패턴이 개발 편의성과 환경 표준화를 동시에 달성합니다.

핵심 요약

명령용도
docker run -d --name X -p H:C image컨테이너 백그라운드 실행
docker ps / docker ps -a실행중 / 전체 컨테이너 목록
docker stop / docker start정지 / 재시작
docker rm / docker rm -f삭제 / 강제 삭제
docker logs -f NAME실시간 로그 확인
docker exec -it NAME bash컨테이너 쉘 접속
docker inspect NAME컨테이너 상세 정보
docker stats리소스 사용량 모니터링

관련 모듈로 더 깊이:

다음 모듈에서는 컨테이너의 실행 재료인 이미지를 직접 다룹니다. pull, build, tag, push 흐름을 따라가며 이미지가 어떻게 만들어지고 공유되는지 확인합니다.

지식 확인

퀴즈 — 8문제

Q1

docker run -d -p 8080:80 --name web nginx 명령에서 '-d' 옵션의 의미는?

Q2

docker run -p 8080:80 에서 포트 바인딩의 의미로 올바른 것은?

Q3

실행 중인 컨테이너를 삭제하려면 어떻게 해야 하나?

Q4

docker run --rm 옵션의 효과는?

Q5

docker ps -a 와 docker ps 의 차이는?

Q6

백그라운드(-d)로 띄운 web 컨테이너 안에서 셸을 열어 설정 파일을 직접 확인하려면?

Q7

[심화] 컨테이너 안에서 ps aux를 실행하면 호스트의 다른 프로세스는 보이지 않고 자기 프로세스만 보인다. 이 격리를 만드는 리눅스 커널 기능은?

Q8

[심화] docker stop web 을 하면 매번 정확히 10초 멈췄다가 종료되고, 앱 로그에 graceful shutdown 메시지가 남지 않는다. Dockerfile의 CMD가 셸 형식(CMD node app.js)일 때 가장 가능성 높은 원인은?

0 / 8 답변

🧪 실습으로 확인하기

Docker 네트워크 드라이버 — bridge/host/overlay 비교

중급

bridge, host, 커스텀 bridge 네트워크를 직접 생성하고 비교해 컨테이너 격리 vs 성능 트레이드오프와 드라이버 선택 기준을 이해한다.

40📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요