Docker 시리즈 ③ Container — 컨테이너의 생애 주기
컨테이너가 이미지와 어떻게 다른지, docker run 옵션의 의미, 컨테이너 생명주기와 실무에서 자주 쓰는 명령어를 초보자 눈높이로 정리했다.
Docker 시리즈 ③ Container — 컨테이너의 생애 주기
시리즈 순서: Host → Image → Container → Network → Volume → Dockerfile → Compose → Security → Swarm
들어가며
이전 글에서 이미지가 "붕어빵 틀"이라고 했다면, 컨테이너는 그 틀로 찍어낸 붕어빵이다. 한 틀로 100개 찍을 수 있고, 각 붕어빵은 서로 영향을 주지 않는다.
이 글에선 컨테이너가 어떤 상태를 거치고, 그 안에선 뭐가 일어나는지 살펴본다.
컨테이너 = 이미지 + 쓰기 가능한 레이어
이미지는 읽기 전용이다. 그러면 컨테이너 안에서 파일을 만들면 어디에 저장될까?
답은 컨테이너 위에 얹힌 새로운 레이어다.
┌──────────────────────────────────┐
│ Container Layer (R/W) │ ← 컨테이너에서 변경된 모든 것
├──────────────────────────────────┤
│ Image Layer 4 (Read-Only) │
├──────────────────────────────────┤
│ Image Layer 3 (Read-Only) │ ← 이미지 (모든 컨테이너가 공유)
├──────────────────────────────────┤
│ Image Layer 2 (Read-Only) │
├──────────────────────────────────┤
│ Image Layer 1 (Read-Only) │
└──────────────────────────────────┘이걸 Copy-on-Write라고 한다. 이미지 안의 파일을 수정하려고 하면, 먼저 컨테이너 레이어로 복사된 후 거기서 수정된다. 원본 이미지는 절대 안 바뀐다.
중요한 결과: 컨테이너가 삭제되면 컨테이너 레이어도 같이 사라진다. 즉, 컨테이너 안에서 만든 파일/로그는 다 날아간다. 영속성이 필요하면 볼륨을 써야 한다 (다음 글에서 다룸).
컨테이너의 생애 주기
컨테이너는 여러 상태를 거치며 산다.
docker create
┌───────┐ ┌────────┐
│ Image │ ─────────────────▶ │Created │
└───────┘ └────┬───┘
│ docker start
▼
┌────────┐ docker pause ┌────────┐
│ Paused │ ◀──────────────── │Running │
└────┬───┘ docker unpause └────┬───┘
│ │ exit / docker stop
│ ▼
│ ┌────────┐
└──────────────────────▶│Exited │
└────┬───┘
│ docker rm
▼
(제거됨)| 상태 | 설명 |
|---|---|
| Created | 컨테이너 생성됐지만 아직 실행 안 함 |
| Running | 프로세스 실행 중 |
| Paused | 프로세스가 일시 정지(메모리는 유지) |
| Exited | 프로세스가 끝나서 종료된 상태 (컨테이너는 남아있음) |
| Dead | 비정상 종료 |
docker run은 사실 두 명령의 합성
docker run nginx
# 위 명령은 사실 아래와 동일
docker create nginx # Created 상태로 만들고
docker start <id> # Running 상태로 시작대부분 docker run만 쓰지만, 내부에선 두 단계를 거친다는 걸 알아두면 좋다.
docker run 옵션 분해
가장 자주 쓰는 옵션들이다.
docker run -d -p 3000:3000 --name my-app -e NODE_ENV=production --restart unless-stopped my-image:latest-d (detached) — 백그라운드 실행
이 옵션이 없으면 터미널이 컨테이너에 묶여서 종료할 때까지 못 빠져나온다.
docker run nginx # 터미널이 묶임 (Ctrl+C로 종료)
docker run -d nginx # 백그라운드로 떠서 터미널 자유-it (interactive + tty) — 대화형 셸
쉘에 들어가야 할 때 쓴다. -i는 입력 받기, -t는 터미널처럼 동작.
docker run -it ubuntu bash # ubuntu 컨테이너 안의 bash로 접속-p (publish) — 포트 매핑
컨테이너 내부 포트를 호스트 포트와 연결한다.
-p 3000:3000 # 호스트:컨테이너 (외부 3000 → 컨테이너 3000)
-p 8080:3000 # 호스트 8080 → 컨테이너 3000
-p 127.0.0.1:3000:3000 # 로컬에서만 접근 가능(자세한 네트워크 동작은 Network 글에서)
--name — 컨테이너 이름
지정 안 하면 friendly_einstein 같은 랜덤 이름이 붙는다. 명령어로 다룰 때 헷갈리니 운영용은 명시하는 게 좋다.
-e (environment) — 환경변수 주입
-e NODE_ENV=production
-e DATABASE_URL=postgres://...
--env-file .env # 파일로 한꺼번에-v (volume) — 볼륨 마운트
-v /host/path:/container/path # 호스트 경로 → 컨테이너에 연결
-v my-volume:/data # 이름 있는 볼륨--restart — 재시작 정책
컨테이너가 죽었을 때 어떻게 할지.
| 값 | 동작 |
|---|---|
no (기본) | 죽으면 그대로 두기 |
on-failure | 비정상 종료(exit code != 0)일 때만 재시작 |
always | 항상 재시작 (Docker 재시작 시에도) |
unless-stopped | 사용자가 직접 멈춘 게 아니면 재시작 (가장 많이 씀) |
--rm — 종료 시 자동 삭제
일회성 컨테이너에 유용하다. 안 그러면 Exited 상태로 계속 남는다.
docker run --rm -it ubuntu bash # 셸 빠져나오면 컨테이너도 사라짐컨테이너 다루기: 자주 쓰는 명령어
보기
docker ps # 실행 중인 컨테이너만
docker ps -a # 모든 컨테이너 (Exited 포함)
docker ps -q # ID만 (스크립트용)
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"들여다보기
docker logs my-app # 로그 보기
docker logs -f my-app # 실시간 스트리밍 (tail -f처럼)
docker logs --tail 100 my-app # 마지막 100줄
docker inspect my-app # 모든 메타데이터 (JSON)
docker inspect --format '{{.NetworkSettings.IPAddress}}' my-app # 특정 필드만
docker stats # 실시간 리소스 사용량
docker top my-app # 컨테이너 안의 프로세스컨테이너 안으로 들어가기
docker exec -it my-app sh # alpine 기반
docker exec -it my-app bash # debian/ubuntu 기반
docker exec my-app ls /app # 명령 한 번만 실행`exec` vs `attach`: -exec는 컨테이너 안에서 새 프로세스를 실행 -attach는 컨테이너의 메인 프로세스에 붙음 (Ctrl+C 누르면 컨테이너 자체가 죽을 수 있어 위험)
제어
docker stop my-app # 정상 종료 (SIGTERM, 10초 후 SIGKILL)
docker kill my-app # 즉시 강제 종료 (SIGKILL)
docker restart my-app # 재시작
docker pause my-app # 일시 정지
docker unpause my-app # 재개
docker rm my-app # 삭제 (Exited 상태일 때)
docker rm -f my-app # 실행 중이어도 강제 삭제정리
# 멈춘 컨테이너 모두 삭제
docker container prune
# 모든 컨테이너 삭제
docker rm -f $(docker ps -aq)실수하기 쉬운 포인트
❓ "컨테이너가 자꾸 바로 종료돼요"
Docker는 메인 프로세스가 끝나면 컨테이너도 끝난다. 백그라운드 프로세스를 데몬화하는 식으로 띄우면 메인 프로세스가 바로 끝나서 컨테이너도 같이 죽는다.
# ❌ 잘못된 예
CMD ["service", "nginx", "start"] # service 명령 끝나면 컨테이너 종료
# ✅ 올바른 예: 포어그라운드로 실행
CMD ["nginx", "-g", "daemon off;"]❓ "왜 같은 이미지로 띄운 컨테이너끼리 데이터가 공유 안 돼요?"
컨테이너 레이어는 각 컨테이너마다 따로다. 이미지만 공유할 뿐, 컨테이너 간엔 서로 격리되어 있다. 데이터 공유가 필요하면 공유 볼륨을 써야 한다.
❓ "컨테이너가 메모리 무한정 먹어요"
기본값으론 호스트 메모리를 다 쓸 수 있다. 운영 환경에선 반드시 제한해야 한다.
docker run -d --memory="512m" --cpus="0.5" my-app❓ "Exited 컨테이너가 쌓여서 디스크가 가득 찼어요"
Exited 컨테이너도 파일 시스템 레이어를 차지한다. 주기적으로 정리하거나 --rm을 활용하자.
docker container prune -f # 일괄 정리❓ "PID 1 문제"
컨테이너의 메인 프로세스는 PID 1로 실행되는데, PID 1은 좀비 프로세스를 거두지 못한다. Node.js 같은 일부 런타임에선 시그널 처리도 다르다.
해결책: --init 옵션을 쓰거나, tini 같은 init 시스템을 이미지에 포함.
docker run --init my-app정리
- 컨테이너 = 이미지 + 쓰기 가능한 레이어 (Copy-on-Write)
- 생애 주기: Created → Running → (Paused) → Exited → Removed
docker run옵션 중-d,-p,--name,-e,--restart는 거의 항상 쓴다- 컨테이너 삭제 시 데이터도 사라지니, 영속성은 볼륨으로 해결
- 메인 프로세스가 끝나면 컨테이너도 끝난다 (포어그라운드로 실행 필수)
다음 글에선 컨테이너끼리 어떻게 통신하고, 외부와는 어떻게 연결되는지 — 네트워크를 다룬다.
이전 글: Docker 시리즈 ② Image 다음 글: Docker 시리즈 ④ Network — 컨테이너는 어떻게 통신하는가
Related Posts
같이 읽으면 좋은 글
Docker 개념 정리 — 컨테이너부터 Dockerfile까지
VM과 컨테이너의 차이, 이미지·컨테이너·레지스트리 구조, Dockerfile 작성 방법까지 Docker 핵심 개념을 정리했다.
Docker 시리즈 ⑨ Swarm — 여러 호스트로 확장하기, 그리고 Kubernetes로
Docker Swarm의 핵심 개념(Service, Task, Stack)을 정리하고, 왜 결국 Kubernetes로 넘어가게 되는지 실제 프로젝트 경험을 바탕으로 정리했다.
Docker 시리즈 ⑧ Security — 컨테이너 보안의 기본
컨테이너 보안의 기초 — non-root 실행, 시크릿 관리, .dockerignore, 이미지 스캔, 리소스 제한까지 초보자도 챙겨야 할 핵심을 정리했다.