nginx 컨테이너를 실행했는데 docker ps에는 보이지 않습니다. 지운 줄 알고 다시 실행했더니 이번에는 같은 이름을 쓸 수 없다는 충돌 에러가 납니다.
컨테이너는 실행과 삭제 사이에 Created, Running, Paused, Exited 같은 상태를 거칩니다. 이 상태 전환을 모르면 컨테이너가 사라진 것처럼 보일 때마다 새로 만들거나 강제로 지우게 됩니다. 포트 바인딩까지 함께 이해해야 브라우저에서 접근되지 않는 문제도 직접 진단할 수 있습니다.
컨테이너 생명주기와 포트 바인딩
컨테이너는 단순히 실행하고 멈추는 것이 아닙니다. 생성(Created)부터 제거(Removed)까지 명확한 상태 전환 사이클을 가지며, 이를 이해해야 컨테이너를 안정적으로 운영할 수 있습니다. 또한 컨테이너의 네트워크 격리를 돌파해 외부에서 접근할 수 있게 만드는 포트 바인딩을 마스터합니다.
컨테이너를 단순히 켜고 끄는 수준을 넘어, 생명주기 전체를 제어하고 포트를 통해 외부와 연결하는 방법을 마스터합니다.
- 1컨테이너의 5가지 상태(Created/Running/Paused/Exited/Removed)와 전환 규칙
- 2docker run / stop / start / rm 명령어와 각 상태 변화
- 3-p HOST:CONTAINER 포트 바인딩으로 외부 트래픽 수신
- 4-it 플래그와 docker exec로 실행 중인 컨테이너 내부 접속
- 5docker stop(SIGTERM) vs docker kill(SIGKILL) 차이
이전 챕터(docker/container-paradigm)에서 Docker 설치를 완료했다면 바로 진행할 수 있습니다.
docker --versiondocker psdocker pull nginx:alpine컨테이너 라이프사이클 — 5가지 상태와 전환
docker ps에 분명히 실행했던 컨테이너가 보이지 않습니다. docker ps -a로 확인하면 Exited 상태로 남아있습니다. 컨테이너는 단순히 켜고 끄는 것이 아니라 Created, Running, Paused, Exited, Removed의 5가지 상태를 오가는 상태 머신입니다. 이 전환 규칙을 이해해야 컨테이너가 사라진 것처럼 보일 때 당황하지 않고 원인을 찾을 수 있고, docker stop과 docker rm을 언제 어떻게 써야 하는지 판단할 수 있습니다. 이 ConceptBlock에서는 컨테이너의 5가지 상태와 각 상태 전환에 사용되는 명령어를 다룹니다.

컨테이너의 상태 머신
Docker 컨테이너는 다음 5가지 상태를 가지며, 명령어에 따라 상태가 전환됩니다.
docker create
┌─────────────────────────────┐
▼ │
┌─────────┐ docker start ┌───┴─────┐
│ Created │──────────────────▶│ Running │
└─────────┘ └────┬────┘
│
┌────────────────────┼────────────────────┐
│ │ │
docker stop docker pause (프로세스 종료)
│ │ │
▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌─────────┐
│ Stopped │ │ Paused │ │ Exited │
│ (Exited) │ └────┬────┘ └────┬────┘
└────┬─────┘ │ │
│ docker unpause docker start
docker start │ │
│ ▼ ▼
└──────────────▶ Running ◀───────────────┘
│
docker rm
│
▼
(Removed)
각 상태 설명
Created
docker create 명령어로 컨테이너가 생성되었지만 아직 시작되지 않은 상태입니다. 파일시스템 레이어가 설정되고 메타데이터가 저장되었지만, 실제 프로세스는 실행 중이지 않습니다.
Running
docker start 또는 docker run 으로 컨테이너 내 메인 프로세스(PID 1)가 실행 중인 상태입니다. docker ps에서 확인할 수 있습니다.
Paused
docker pause로 컨테이너의 모든 프로세스에 SIGSTOP 신호를 보내 일시 정지한 상태입니다. CPU를 사용하지 않지만 메모리는 여전히 점유합니다. docker unpause로 재개할 수 있습니다.
Stopped (Exited)
docker stop으로 중단되거나 메인 프로세스가 자연 종료된 상태입니다. 컨테이너 파일시스템은 보존되어 있으므로 docker start로 재시작 가능합니다. docker ps -a에서만 확인됩니다.
Removed
docker rm으로 완전히 삭제된 상태입니다. 컨테이너 파일시스템 레이어도 삭제됩니다. 단, Named Volume에 저장된 데이터는 유지됩니다.
상태 확인 방법
docker ps와 docker ps -a가 기본 진단 명령어입니다. STATUS 열에서 컨테이너 상태와 종료 코드를 확인하고, docker inspect로 상세 메타데이터를 조회합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/docker/part2/exam_5 && cd /tmp/docker/part2/exam_5
# 실행 중인 컨테이너만 표시
docker ps
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# a1b2c3d4e5f6 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp my-nginx
# 모든 상태의 컨테이너 표시
docker ps -a
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# a1b2c3d4e5f6 nginx ... 2 min ago Up 2 minutes ... my-nginx
# b2c3d4e5f6a7 hello-world "/hello" 10 min ago Exited (0) 10 min ago magical_tesla
# 특정 상태만 필터링
docker ps -a --filter "status=exited"
docker ps -a --filter "name=my-nginx"
컨테이너 상세 정보
# 컨테이너의 모든 메타데이터 확인
docker inspect my-nginx
# 특정 필드만 추출 (Go 템플릿 사용)
docker inspect my-nginx --format '{{.State.Status}}'
docker inspect my-nginx --format '{{.NetworkSettings.IPAddress}}'
포트 바인딩 — 컨테이너 네트워크 격리 돌파하기
컨테이너 안에서 Nginx가 잘 실행 중인데 curl localhost:80이 연결을 거부합니다. docker ps의 포트 열도 비어있습니다. 컨테이너는 기본적으로 네트워크가 격리되어 있어 -p 옵션 없이는 외부에서 접근할 수 없습니다. 어떤 포트를 어느 인터페이스에 바인딩할지 정확히 이해해야, '포트는 열었는데 외부에서 안 들어온다'는 상황을 직접 진단할 수 있습니다. 이 ConceptBlock에서는 컨테이너 네트워크 격리의 원리와 포트 바인딩 메커니즘을 다룹니다.

컨테이너 네트워크 격리
컨테이너는 자체 네트워크 네임스페이스를 가지므로, 기본적으로 외부에서 직접 접근할 수 없습니다. 컨테이너 내부에서 실행 중인 Nginx가 80번 포트를 리스닝하더라도, 호스트에서 curl localhost:80을 실행하면 연결이 거부됩니다.
호스트 네트워크 컨테이너 네트워크
───────────────────── ─────────────────────
eth0: 192.168.1.10 eth0: 172.17.0.2
lo: 127.0.0.1
Nginx 리스닝: 0.0.0.0:80
포트 바인딩 메커니즘
-p 옵션으로 호스트 포트와 컨테이너 포트를 연결합니다. Docker는 iptables 규칙을 자동으로 생성하여 트래픽을 라우팅합니다.
외부 클라이언트 → 호스트:8080 → iptables NAT 규칙 → 컨테이너:80 → Nginx
포트 바인딩 형식
-p 옵션의 기본 형식은 호스트포트:컨테이너포트입니다. 호스트 IP를 앞에 추가하면 특정 네트워크 인터페이스로만 바인딩을 제한할 수 있어 노출 범위를 좁힐 수 있습니다.
# 형식: -p [호스트IP:]호스트PORT:컨테이너PORT[/프로토콜]
# 기본: 모든 인터페이스의 8080 → 컨테이너 80
docker run -p 8080:80 nginx
# 특정 IP 바인딩: localhost에서만 접근 가능
docker run -p 127.0.0.1:8080:80 nginx
# 랜덤 호스트 포트 자동 할당
docker run -p 80 nginx
# UDP 프로토콜 지정
docker run -p 53:53/udp bind9
# 여러 포트 동시 바인딩
docker run -p 80:80 -p 443:443 nginx
포트 확인 방법
# 컨테이너의 포트 바인딩 확인
docker port my-nginx
# 80/tcp -> 0.0.0.0:8080
# docker ps에서도 확인 가능
docker ps
# PORTS: 0.0.0.0:8080->80/tcp
Docker 네트워크 드라이버
포트 바인딩은 기본 bridge 드라이버에서 동작합니다. 고성능이 필요하거나 완전한 네트워크 격리가 필요한 경우 다른 드라이버를 선택합니다.
| 드라이버 | 설명 | 용도 |
|---|---|---|
bridge | 기본값. 가상 브리지 네트워크 | 단일 호스트 컨테이너 통신 |
host | 호스트 네트워크 직접 사용 | 성능 중요 시, 포트 바인딩 불필요 |
none | 네트워크 비활성화 | 완전 격리 필요 시 |
overlay | 멀티 호스트 가상 네트워크 | Docker Swarm, Kubernetes |
# host 네트워크 사용 (포트 바인딩 없이 호스트 포트 직접 사용)
docker run --network host nginx
# 이 경우 호스트의 80번 포트에서 바로 Nginx 접근 가능
iptables 규칙 확인
Docker가 생성하는 iptables 규칙을 직접 확인할 수 있습니다:
# DOCKER 체인의 NAT 규칙 확인
sudo iptables -t nat -L DOCKER --line-numbers
# 특정 포트에 대한 규칙 확인
sudo iptables -t nat -L -n | grep 8080
목표
docker run -d -p 8080:80 nginx로 백그라운드에서 Nginx를 실행하고, 컨테이너 상태를 확인하며 웹 요청을 테스트합니다.
이 실습은 공개 이미지(nginx)를 사용하는 순수 Docker 명령어 실습입니다. 별도 파일 생성 없이 바로 진행할 수 있습니다.
실습 단계
1단계: Nginx 이미지 백그라운드 실행
실습 전 디렉토리와 예제 파일을 먼저 준비합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/docker/part1/exam_2 && cd /tmp/docker/part1/exam_2
# 샘플 Dockerfile 생성
cat > Dockerfile << 'EOF'
FROM alpine:3.18
CMD ["sleep", "infinity"]
EOF
이제 실습을 진행합니다.
# -d: detached 모드(백그라운드 실행)
# -p 8080:80: 호스트 8080 → 컨테이너 80 포트 바인딩
# --name: 컨테이너에 이름 부여
docker run -d -p 8080:80 --name my-nginx nginx
# 출력: 컨테이너 ID (긴 해시값)
# 예: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
2단계: 실행 상태 확인
# 실행 중인 컨테이너 확인
docker ps
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# a1b2c3d4e5f6 nginx "/docker-entrypoint.…" 10 seconds ago Up 9 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp my-nginx
3단계: 웹 접속 확인
# curl로 HTTP 요청
curl http://localhost:8080
# 응답 헤더 확인
curl -I http://localhost:8080
# HTTP/1.1 200 OK
# Server: nginx/1.x.x
# ...
# 브라우저에서 접속: http://localhost:8080
4단계: 컨테이너 로그 확인
# 컨테이너 로그 출력
docker logs my-nginx
# 실시간 로그 모니터링 (-f: follow)
docker logs -f my-nginx
# 최근 10줄만 표시
docker logs --tail 10 my-nginx
# 타임스탬프 포함
docker logs -t my-nginx
5단계: 컨테이너 내부 탐색 (-it 플래그)
# 실행 중인 컨테이너에 bash 셸 접속
docker exec -it my-nginx bash
# 컨테이너 내부에서 실행 가능한 명령어들
ls /usr/share/nginx/html/ # Nginx 웹 루트
cat /etc/nginx/nginx.conf # Nginx 설정 파일
ps aux # 실행 중인 프로세스
# 셸 종료
exit
6단계: 컨테이너 중지, 재시작, 삭제
# 컨테이너 중지 (SIGTERM 전송, 기본 10초 대기)
docker stop my-nginx
# 중지 확인
docker ps -a
# STATUS: Exited (0)
# 컨테이너 재시작
docker start my-nginx
# 즉시 강제 종료
docker kill my-nginx
# 컨테이너 삭제 (중지 후 삭제)
docker stop my-nginx && docker rm my-nginx
# 실행 중인 컨테이너 강제 삭제
7단계: 모든 중지된 컨테이너 일괄 정리
# 중지된 컨테이너 모두 삭제
docker container prune
# 또는 커맨드 치환 방식
docker rm $(docker ps -aq --filter status=exited)
실행 중인 컨테이너 강제 삭제
안전한 실행 조건: 실습용 컨테이너를 정리하는 상황이며 저장해야 할 데이터가 없는 경우에만 실행합니다.
실행 전 반드시 확인
- docker ps -a로 삭제 대상 컨테이너 이름을 확인했다
- 중요한 로그나 컨테이너 내부 변경사항이 필요 없음을 확인했다
- docker stop 후 docker rm으로 정리할 수 없는 상황인지 확인했다
docker rm -f my-nginx위 항목을 모두 확인한 후 복사할 수 있습니다
중지된 컨테이너 일괄 삭제
안전한 실행 조건: 삭제 대상이 실습 또는 임시 컨테이너뿐임을 확인한 경우에만 실행합니다.
실행 전 반드시 확인
- docker ps -a --filter status=exited로 삭제 대상을 확인했다
- 장애 분석에 필요한 중지 컨테이너가 없음을 확인했다
- 팀 공유 Docker 호스트가 아닌지 확인했다
docker container prune위 항목을 모두 확인한 후 복사할 수 있습니다
- docker ps 출력에서 my-nginx가 Up 상태이고 8080->80 포트 매핑이 보이는가?
- curl http://localhost:8080 실행 시 Nginx 응답 또는 200 OK 헤더가 확인되는가?
- docker stop 후 docker ps -a 출력에서 STATUS가 Exited로 바뀌는가?
- docker start 후 같은 컨테이너 이름으로 다시 응답을 받을 수 있는가?
목표
--name 플래그로 컨테이너에 의미 있는 이름을 부여하고, 이름 기반으로 컨테이너를 관리합니다.
이름 없는 컨테이너의 문제점
Docker는 이름을 지정하지 않으면 자동으로 무작위 이름을 생성합니다 (magical_tesla, happy_curie 같은 형태). 여러 컨테이너를 운영할 때 이런 이름은 관리가 어렵습니다.
실습
이름 지정으로 컨테이너 생성
# 웹 서버
docker run -d -p 8080:80 --name web-server nginx
# 데이터베이스 (환경 변수로 패스워드 설정)
docker run -d -p 5432:5432 \
--name postgres-db \
-e POSTGRES_PASSWORD=mypassword \
-e POSTGRES_USER=myuser \
-e POSTGRES_DB=mydb \
postgres:15
# Redis 캐시
docker run -d -p 6379:6379 --name redis-cache redis:7-alpine
이름으로 컨테이너 조작
# 이름으로 중지/시작/삭제
docker stop web-server
docker start web-server
docker restart web-server
# 이름으로 exec
docker exec -it postgres-db psql -U myuser -d mydb
# 이름으로 로그 확인
docker logs redis-cache
# 이름으로 상세 정보
docker inspect postgres-db
이름 기반 컨테이너 강제 삭제
안전한 실행 조건: 같은 이름 충돌을 해결하기 위한 실습용 컨테이너 정리일 때만 실행합니다.
실행 전 반드시 확인
- docker ps -a --filter name=web-server로 대상을 확인했다
- 삭제 대상이 현재 사용 중인 서비스가 아님을 확인했다
- 필요한 로그 확인이 끝났다
docker rm -f web-server위 항목을 모두 확인한 후 복사할 수 있습니다
이름 충돌 처리
# 같은 이름으로 두 번 실행하면 에러 발생
docker run -d --name my-app nginx
docker run -d --name my-app nginx
# Error response: Conflict. The container name "/my-app" is already in use
# 해결: 기존 컨테이너 삭제 후 재생성
docker run -d --name my-app nginx
이름 충돌 컨테이너 강제 삭제
안전한 실행 조건: 기존 my-app 컨테이너가 실습용이며 재생성해도 되는 경우에만 실행합니다.
실행 전 반드시 확인
- docker ps -a --filter name=my-app로 기존 컨테이너 상태를 확인했다
- 컨테이너 내부에 보존해야 할 데이터가 없음을 확인했다
- 재생성할 이미지와 실행 옵션을 알고 있다
docker rm -f my-app위 항목을 모두 확인한 후 복사할 수 있습니다
자동 재시작 정책 설정
# 항상 재시작 (장애 시에도)
docker run -d --restart always --name my-app nginx
# 종료 코드가 0이 아닐 때만 재시작 (최대 3회)
docker run -d --restart on-failure:3 --name my-app nginx
# 재시작 정책 확인
docker inspect my-app --format '{{.HostConfig.RestartPolicy}}'
목표
-i(stdin 연결)와 -t(pseudo-TTY 할당) 플래그를 이해하고, 다양한 인터랙티브 시나리오에서 활용합니다.
-i와 -t 플래그 이해
-i (--interactive): 컨테이너의 표준 입력(stdin)을 열어 둠
-t (--tty): pseudo-TTY를 할당하여 터미널처럼 동작
둘을 함께 사용(-it)하면 실제 터미널처럼 컨테이너와 상호작용할 수 있습니다.
실습
새 컨테이너를 인터랙티브로 실행
# Ubuntu 컨테이너를 인터랙티브 bash 셸로 실행
docker run -it ubuntu:22.04 bash
# Alpine Linux (더 작은 이미지)
docker run -it --rm alpine:3.18 sh
# Python 인터랙티브 셸
docker run -it --rm python:3.11-slim python3
# Node.js REPL
docker run -it --rm node:20-alpine node
실행 중인 컨테이너에 셸 접속
# Nginx 컨테이너가 이미 실행 중일 때
docker run -d --name my-nginx nginx
# bash 셸로 접속
docker exec -it my-nginx bash
# bash가 없으면 sh 사용 (Alpine 기반 이미지)
docker exec -it my-nginx sh
# 특정 명령어만 실행 (인터랙티브 불필요)
docker exec my-nginx nginx -t # 설정 파일 문법 검사
docker exec my-nginx nginx -s reload # 설정 리로드
인터랙티브 컨테이너에서의 작업 예시
# Ubuntu 컨테이너에서 패키지 설치 및 테스트
docker run -it --rm ubuntu:22.04 bash
# 컨테이너 내부에서
apt-get update && apt-get install -y curl
curl https://httpbin.org/get
exit # --rm 옵션으로 자동 삭제
컨테이너 내부 파일 복사
# 호스트 → 컨테이너 파일 복사
docker cp ./config.conf my-nginx:/etc/nginx/conf.d/
# 컨테이너 → 호스트 파일 복사
docker cp my-nginx:/var/log/nginx/access.log ./access.log
문제 상황
$ docker run -d -p 8080:80 nginx
docker: Error response from daemon: driver failed programming external connectivity
on endpoint nginx-test: Bind for 0.0.0.0:8080 failed: port is already allocated.
원인 분석
이 에러는 호스트의 8080번 포트가 이미 다른 프로세스나 컨테이너에 의해 사용 중일 때 발생합니다.
진단 방법
# 방법 1: ss 명령어로 포트 사용 확인 (modern Linux)
ss -tlnp | grep 8080
# 방법 2: netstat 명령어
netstat -tlnp | grep 8080
# 방법 3: lsof 명령어
sudo lsof -i :8080
# 방법 4: fuser 명령어
sudo fuser 8080/tcp
# Docker 컨테이너가 포트를 사용 중인지 확인
docker ps | grep 8080
해결 방법
방법 1: 다른 호스트 포트 사용
# 8080 대신 8081 사용
docker run -d -p 8081:80 --name nginx-new nginx
방법 2: 기존에 해당 포트를 사용하는 컨테이너 중지
# 8080 포트를 사용하는 컨테이너 찾기
docker ps | grep "8080"
# 해당 컨테이너 중지
docker stop <container_id_or_name>
# 새 컨테이너 실행
docker run -d -p 8080:80 --name nginx-new nginx
방법 3: 호스트 프로세스가 포트를 점유한 경우
# 포트를 점유한 프로세스 확인
sudo lsof -i :8080
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# node 1234 ubuntu 22u IPv4 12345 0t0 TCP *:http-alt (LISTEN)
# 프로세스 종료 (PID 사용)
sudo kill 1234
# 또는 서비스 중지
sudo systemctl stop your-service
방법 4: 포트 자동 할당 사용
# 호스트 포트를 자동으로 할당받기
docker run -d -p 80 --name nginx-auto nginx
# 할당된 포트 확인
docker port nginx-auto
# 80/tcp -> 0.0.0.0:32768
예방 방법
# 컨테이너 시작 전 포트 사용 여부 확인 스크립트
PORT=8080
if ss -tlnp | grep -q ":$PORT "; then
echo "포트 $PORT 이미 사용 중"
ss -tlnp | grep ":$PORT "
else
docker run -d -p $PORT:80 nginx
echo "Nginx 시작됨: http://localhost:$PORT"
fi
문제 상황
docker run -d myapp
docker ps
# (아무것도 표시되지 않음)
docker ps -a
# CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
# a1b2c3d4e5f6 myapp "..." 5 seconds ago Exited (1) 4 seconds ago ...
컨테이너를 백그라운드(-d)로 실행했는데 즉시 종료됩니다.
원인 및 해결
원인 1: 포그라운드 프로세스가 없음
Docker 컨테이너는 CMD/ENTRYPOINT로 지정된 메인 프로세스(PID 1)가 종료되면 함께 종료됩니다.
# 잘못된 예: 백그라운드로 실행하면 쉘이 즉시 종료됨
CMD ["bash", "-c", "nginx &"]
# 올바른 예: 포그라운드로 실행
CMD ["nginx", "-g", "daemon off;"]
원인 2: 에러로 인한 종료
# 로그 확인이 핵심
docker logs a1b2c3d4e5f6
# 종료 코드 확인
docker inspect a1b2c3d4e5f6 --format '{{.State.ExitCode}}'
# 0: 정상 종료, 1: 에러, 137: SIGKILL, 143: SIGTERM
원인 3: 환경 변수 누락
# 필요한 환경 변수 없이 실행 → 앱이 에러로 종료
docker run -d myapp
# ERROR: DATABASE_URL environment variable is required
# 해결: 필요한 환경 변수 제공
docker run -d -e DATABASE_URL=postgres://... myapp
디버깅 순서
# 1. 종료된 컨테이너 로그 확인
docker logs <container_id>
# 2. 인터랙티브 모드로 실행하여 직접 확인
docker run -it myapp bash
# 3. 컨테이너 내에서 앱 직접 실행
./myapp # 실제 에러 메시지 확인
# 4. --rm 없이 실행하여 종료 후 inspect
docker run -d myapp
docker inspect <container_id>
실제 운영 환경에서의 컨테이너 관리
무중단 배포(Zero-Downtime Deployment) 패턴
프로덕션 환경에서 컨테이너를 업데이트할 때는 순단 없이 전환해야 합니다.
# 1. 새 버전 이미지 pull
docker pull myapp:v2.0
# 2. 새 컨테이너 다른 포트로 시작
docker run -d -p 8081:3000 --name myapp-v2 myapp:v2.0
# 3. 헬스체크 확인
curl http://localhost:8081/health
# 4. 로드밸런서 트래픽 전환 (Nginx/HAProxy 설정 변경)
# 5. 기존 컨테이너 정리
docker stop myapp-v1
docker rm myapp-v1
헬스체크 기반 자동 관리
# 헬스체크가 있는 컨테이너 실행
docker run -d \
--name myapp \
--health-cmd "curl -f http://localhost:3000/health || exit 1" \
--health-interval 30s \
--health-timeout 10s \
--health-retries 3 \
myapp:latest
# 헬스 상태 확인
docker ps
# STATUS: Up 5 minutes (healthy)
# STATUS: Up 5 minutes (unhealthy)
docker inspect myapp --format '{{.State.Health.Status}}'
컨테이너 리소스 제한
# CPU와 메모리 제한 설정 (프로덕션 필수)
docker run -d \
--name myapp \
--memory="512m" \
--memory-swap="1g" \
--cpus="1.5" \
myapp:latest
# 리소스 사용량 실시간 모니터링
docker stats
docker stats --no-stream myapp # 한 번만 출력
현업에서 자주 쓰는 패턴
개발 환경: docker run --rm -it 패턴으로 일회성 테스트
# 테스트 후 자동 삭제되는 임시 컨테이너
docker run --rm -it -v $(pwd):/workspace python:3.11 python test.py
로그 수집: 중앙 로그 시스템 연동
# 로그를 파일로 저장 (log rotation 포함)
docker run -d \
--log-driver json-file \
--log-opt max-size=100m \
--log-opt max-file=3 \
myapp
이러한 패턴들은 Docker Compose나 Kubernetes에서도 동일한 개념으로 적용되므로, 컨테이너 생명주기를 잘 이해해두면 더 복잡한 오케스트레이션 도구로 자연스럽게 넘어갈 수 있습니다.
docker run 핵심 플래그 — 언제 무엇을 쓰는가
처음에는 docker run nginx만 알면 될 것 같지만, 실제로 쓰다 보면 옵션이 없어서 생기는 문제들을 만납니다. -d 없이 실행했다가 터미널을 닫으면 컨테이너도 죽고, --name 없이 실행했다가 컨테이너를 못 찾고, -p 없이 실행했다가 포트가 외부로 노출되지 않습니다. 각 플래그가 없을 때 어떤 일이 벌어지는지를 먼저 이해하면 복잡해 보이는 옵션들이 자연스럽게 납득됩니다. 이 ConceptBlock에서는 실무에서 자주 쓰는 docker run 플래그와 상황별 조합 패턴을 다룹니다.

docker run 명령어 구조
docker run은 이미지를 컨테이너로 실행하는 가장 기본 명령입니다. 플래그 순서는 반드시 이미지 이름 앞에 와야 합니다.
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
가장 많이 쓰는 플래그들을 용도별로 정리합니다.
실행 모드 플래그
# -d (--detach): 백그라운드 실행
# 서버 프로세스처럼 계속 돌아야 할 컨테이너에 사용
docker run -d nginx
# -it: 대화형 터미널 (Interactive + TTY)
# 셸에 직접 접속하거나 대화형 프로그램 실행 시
docker run -it ubuntu bash
docker run -it python:3.11 python # Python REPL 실행
# --rm: 컨테이너 종료 시 자동 삭제
# 일회성 실행, 임시 도구 사용 시 (docker ps -a에 쌓이지 않음)
docker run --rm ubuntu echo "hello"
docker run --rm -it alpine sh # 종료하면 바로 삭제
이름과 환경변수 플래그
--name은 컨테이너를 ID 대신 의미 있는 이름으로 참조하게 해줍니다. -e와 --env-file은 이미지에 설정을 주입하는 가장 일반적인 방법으로, .env 파일에 민감 정보를 모아두고 버전 관리에서 제외하는 패턴과 함께 씁니다.
# --name: 컨테이너 이름 지정
# 이름이 없으면 Docker가 random_name을 붙임 (예: loving_turing)
docker run -d --name my-nginx nginx
docker logs my-nginx # 이름으로 참조 가능
# -e / --env: 환경변수 주입
docker run -e DB_HOST=localhost -e DB_PORT=5432 myapp
docker run -e NODE_ENV=production myapp
# --env-file: 파일에서 환경변수 읽기
# .env 파일에 여러 변수를 모아두고 주입
docker run --env-file .env myapp
.env 파일 형식:
# .env
DB_HOST=db
DB_PORT=5432
DB_PASSWORD=secret123
자주 쓰는 조합 패턴
플래그 여러 개를 조합해 쓰는 것이 실무 패턴입니다. 개발 환경, 운영 환경, 일회성 작업마다 일반적으로 쓰이는 조합이 다릅니다.
# 개발 환경: 소스코드 마운트 + 로그 실시간 확인
docker run -it --rm \
-v $(pwd):/app \
-p 3000:3000 \
-e NODE_ENV=development \
node:20 node server.js
# 운영 환경: 백그라운드 + 재시작 + 이름 지정
docker run -d \
--name api-server \
--restart unless-stopped \
-p 8080:8080 \
--env-file /etc/myapp/prod.env \
myapp:2.1.0
# 일회성 DB 마이그레이션 실행
docker run --rm \
--env-file .env \
--network myapp_network \
myapp:2.1.0 python manage.py migrate
# Alpine으로 빠른 셸 도구 사용
docker run --rm -it alpine sh
docker run --rm busybox wget -qO- http://example.com
플래그 요약표
자주 쓰는 플래그와 사용 시점을 정리하면 처음에 헷갈리던 조합들이 패턴으로 잡힙니다.
| 플래그 | 용도 | 언제 사용 |
|---|---|---|
-d | 백그라운드 실행 | 서버 프로세스, 장기 실행 |
-it | 대화형 터미널 | 셸 접속, REPL, 디버깅 |
--rm | 종료 시 자동 삭제 | 임시 실행, 일회성 작업 |
--name | 컨테이너 이름 | 관리 편의, 로그 추적 |
-e | 환경변수 주입 | 설정 전달 |
--env-file | 파일로 환경변수 | 여러 변수, 민감 정보 |
-p | 포트 바인딩 | 외부 접근 허용 |
-v | 볼륨/bind mount | 데이터 영속화 |
--restart | 재시작 정책 | 운영 안정성 |
--network | 네트워크 지정 | 서비스 간 통신 |
컨테이너 로그와 종료 코드 — 문제 발생 시 첫 번째 확인

컨테이너가 왜 죽었는지 확인하는 방법
컨테이너를 실행했는데 바로 종료되거나, 잠시 후 사라지는 경험을 하게 됩니다. 이때 당황하지 않고 원인을 찾는 순서를 익혀두면 됩니다.
Step 1: docker ps -a 로 상태 확인
종료된 컨테이너는 docker ps에 나타나지 않습니다. docker ps -a가 첫 번째 확인 명령이고, STATUS 열의 괄호 안 숫자가 종료 코드입니다.
docker ps -a
# CONTAINER ID IMAGE COMMAND CREATED STATUS
# a1b2c3d4e5f6 myapp "node ..." 5 min ago Exited (1) 5 min ago
# ↑ 종료코드 1 = 오류
STATUS의 괄호 안 숫자가 **종료 코드(Exit Code)**입니다.
Step 2: docker logs 로 오류 메시지 확인
종료 코드가 1 이상이면 로그에 오류 메시지가 남아 있을 가능성이 높습니다. 컨테이너 ID가 없어도 이름으로 조회할 수 있습니다.
# 전체 로그 출력
docker logs a1b2c3d4e5f6
# 마지막 50줄만
docker logs --tail 50 a1b2c3d4e5f6
# 실시간 로그 추적 (tail -f 와 동일)
docker logs -f my-container
# 특정 시간 이후 로그만
docker logs --since 10m my-container # 최근 10분
docker logs --since "2026-04-05T10:00:00" my-container
종료 코드 해석
종료 코드 범위로 원인을 빠르게 분류할 수 있습니다. 137과 143은 각각 강제 종료(SIGKILL)와 정상 종료 요청(SIGTERM)의 시그널 번호에서 128을 더한 값입니다.
| 코드 | 의미 | 원인 |
|---|---|---|
0 | 정상 종료 | 작업 완료 (배치 작업 등) |
1 | 오류 종료 | 앱 내부 에러 |
2 | 잘못된 사용 | 잘못된 명령어 인수 |
125 | Docker 자체 오류 | 잘못된 docker run 옵션 |
126 | 명령어 실행 불가 | 권한 없음 또는 경로 오류 |
127 | 명령어 없음 | COMMAND를 찾을 수 없음 |
137 | SIGKILL | 메모리 부족(OOM) 또는 docker kill |
143 | SIGTERM | docker stop (정상 종료 요청) |
# 종료 코드 직접 확인
docker inspect a1b2c3d4e5f6 --format '{{.State.ExitCode}}'
# 137 → OOM 또는 강제 종료
# OOMKilled 여부 확인
docker inspect a1b2c3d4e5f6 --format '{{.State.OOMKilled}}'
# true → 메모리 부족으로 강제 종료됨
가장 흔한 초보자 실수 — 컨테이너가 바로 Exited 되는 경우
# 잘못된 예: bash는 대화형 입력이 없으면 바로 종료됨
docker run ubuntu bash # 바로 Exited!
# 해결: -it 플래그로 대화형 터미널 연결
docker run -it ubuntu bash # 셸 프롬프트 진입
# 잘못된 예: CMD가 순간에 끝나는 명령이면 컨테이너도 종료
docker run ubuntu echo "hello" # echo 실행 후 Exited
# 서버 프로세스는 포그라운드로 실행해야 컨테이너가 유지됨
# nginx: 기본 CMD가 'nginx -g "daemon off;"' → 포그라운드 실행 ✓
# node server.js → 서버가 포그라운드에서 실행 중 → 유지 ✓
정리
핵심 명령어 요약
# 백그라운드 실행 + 포트 바인딩 + 이름 지정
docker run -d -p 8080:80 --name my-nginx nginx
# 상태 확인
docker ps # 실행 중
docker ps -a # 전체
# 생명주기 제어
docker stop my-nginx # 정상 종료 (SIGTERM)
docker start my-nginx # 재시작
docker restart my-nginx # 중지 후 재시작
# 내부 접속
docker exec -it my-nginx bash
# 로그 확인
docker logs -f my-nginx
# 포트 확인
docker port my-nginx
핵심 요약의 강제 삭제 명령
안전한 실행 조건: 실습용 컨테이너 정리처럼 대상이 명확하고 데이터 손실이 없는 경우에만 실행합니다.
실행 전 반드시 확인
- 삭제할 컨테이너 이름을 docker ps -a에서 확인했다
- 컨테이너 내부 데이터가 휘발되어도 괜찮다
- 가능하면 docker stop 후 docker rm 순서를 먼저 고려했다
docker rm -f my-nginx위 항목을 모두 확인한 후 복사할 수 있습니다
다음 챕터 예고
다음 모듈에서는 Docker 이미지의 내부 구조인 레이어 아키텍처를 심층적으로 다루고, Docker Hub에서 이미지를 pull/push하는 방법과 태그 관리의 중요성을 학습합니다.