팀에 합류한 첫날, 선배가 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 infodocker ps -adocker images이미지, 컨테이너, 레지스트리 — 세 개념의 관계

세 가지 핵심 개념
Docker를 처음 배울 때 가장 헷갈리는 것이 이미지와 컨테이너의 차이입니다. 친숙한 비유로 설명합니다.
소스코드 → 실행 파일 → 실행 중인 프로그램에 빗대면:
| Docker 개념 | 비유 | 특성 |
|---|---|---|
| 이미지 | 실행 파일(.exe, 바이너리) | 읽기 전용, 불변, 저장 가능 |
| 컨테이너 | 실행 중인 프로그램 인스턴스 | 읽기/쓰기 가능, 일시적 |
| 레지스트리 | 앱스토어 / 아티팩트 저장소 | 이미지를 push/pull |
하나의 이미지로 여러 컨테이너를 동시에 실행할 수 있습니다. nginx 이미지 하나로 포트 8080, 8081, 8082에 컨테이너 3개를 띄울 수 있습니다.
레지스트리 (Docker Hub / ECR / GCR)
│
│ docker pull
▼
이미지 (로컬 캐시)
nginx:latest
ubuntu:22.04
myapp:v1.2.3
│
│ docker run (여러 개 가능)
▼
컨테이너 A 컨테이너 B 컨테이너 C
(web-prod) (web-staging) (web-test)
포트 8080 포트 8081 포트 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가 순서대로 다음을 수행합니다:
- 로컬 이미지 캐시에서
nginx:latest검색 - 없으면 Docker Hub에서 자동으로 pull
- 이미지를 기반으로 새 컨테이너 생성 (쓰기 가능한 레이어 추가)
- 네트워크 설정 (가상 NIC 생성, IP 할당)
- 포트 바인딩 (호스트 8080 → 컨테이너 80)
- 컨테이너 내부에서 nginx 프로세스 실행
docker run 옵션 완전 해부
docker run 명령은 옵션 하나가 빠지면 전혀 다른 상황이 펼쳐집니다. -d 없이 실행하면 터미널이 점유되어 Ctrl+C로 서비스를 끄는 실수를 하고, --name 없이 실행하면 컨테이너 ID나 무작위 이름을 외워야 합니다. -p 없이 실행하면 서비스가 외부에서 접근되지 않아 왜 연결이 안 되는지 찾아 헤매게 됩니다. 각 옵션이 없을 때 어떤 상황이 되는지를 먼저 이해하면, 왜 이 옵션들이 거의 항상 함께 쓰이는지 자연스럽게 납득됩니다. 이 ConceptBlock에서는 실무에서 가장 자주 사용하는 docker run 옵션과 그 의미를 다룹니다.

가장 자주 쓰는 옵션들
-d (detached, 백그라운드 실행)
# 포어그라운드 실행 — 터미널 점유, Ctrl+C로 종료
docker run nginx
# 백그라운드 실행 — 컨테이너 ID만 출력하고 프롬프트 반환
docker run -d nginx
# a3f4b1c2d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5 ← 컨테이너 ID
-p (포트 바인딩)
# 형식: -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 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 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 (볼륨, 데이터 영구 보존)
# 컨테이너 내부 데이터는 컨테이너 삭제 시 사라짐
# 볼륨 마운트로 영구 보존
# 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 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 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
# 모든 컨테이너 (정지된 것 포함)
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)은 다음 모듈(컨테이너 생명주기)에서 자세히 다룹니다.
기본 실습
직접 nginx 웹 서버를 컨테이너로 실행하고 관리해봅니다.
# 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 ps 출력에서 my-nginx 컨테이너가 Up 상태로 보이는가?
- PORTS 열에 0.0.0.0:8080->80/tcp 매핑이 표시되는가?
- curl http://localhost:8080 실행 시 HTML 응답이 출력되는가?
- docker logs my-nginx 에 HTTP 요청 로그가 남는가?
환경변수로 설정을 주입하는 실습:
# 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 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 -a가 docker ps와 다른 결과를 보여줄 때, 정지된 컨테이너가 남아있는 것입니다. 개발 중에는 docker container prune으로 정지된 컨테이너를 한 번에 정리하는 것이 편합니다.
증상
docker run으로 컨테이너를 실행했는데, docker ps에 아무것도 나타나지 않습니다.
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 종료 → 컨테이너 종료
해결
# 방법 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 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)
해결
# 방법 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
시나리오: 신규 서비스 로컬 개발 환경 세팅 — "README 없이도 팀원 누구나 5분 안에 실행"
신규 팀원이 합류했을 때 "환경 세팅하는 데 이틀이 걸렸어요"라는 말을 없애고 싶습니다. 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 흐름을 따라가며 이미지가 어떻게 만들어지고 공유되는지 확인합니다.