Docker (7) - 컨테이너 생명주기와 실행 옵션 정리
1. 개요
Docker(2)에서 docker run으로 컨테이너를 간단히 띄워봤지만, 실제로는 컨테이너가 어떤 상태를 거쳐 살아있고 사라지는지, run의 옵션들이 각각 무엇을 하는지, 컨테이너 내부와 파일을 어떻게 주고받는지를 알아야 제대로 다룰 수 있다.
이번 글에서는 이미지와 컨테이너의 관계부터 시작해서, 컨테이너의 생명주기, docker run 옵션, 파일 공유, exec/commit까지 컨테이너를 다루는 데 필요한 명령어들을 한 번에 정리한다.
2. 컨테이너란 무엇인가
이미지는 읽기 전용의 불변 값이고, Docker 엔진은 이 이미지 위에 읽고 쓰기 가능한 레이어를 하나 더 얹어서 컨테이너를 만든다. 즉 컨테이너는 이미지의 스냅샷이라고 볼 수 있다.
컴퓨터에서 동작하는 애플리케이션이 프로세스로 실행되듯, 컨테이너는 격리된 공간에서 실행되는 프로세스에 가깝다. 이런 프로세스 격리 방식은 리눅스 파운데이션의 지원을 받는 OCI(Open Container Initiative) 표준을 따르고 있어서, Docker가 아닌 다른 런타임에서도 호환되어 동작할 수 있다.
3. 컨테이너를 수동으로 제어하기
docker run 한 번으로 컨테이너를 바로 만들고 실행할 수도 있지만, 아래처럼 생성과 실행을 단계별로 나눠서 다룰 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 이미지 다운로드
docker pull ubuntu:14.04
# 컨테이너 생성 (아직 실행 X)
docker create -it --name container-test1 ubuntu:14.04
# 실행 중인 컨테이너 확인
docker ps
# 중지된 것까지 전부 확인
docker ps -a
# 컨테이너 실행
docker start container-test1
# 컨테이너 내부로 접속
docker attach container-test1
# 컨테이너 삭제
docker rm container-test1
4. 컨테이너 생명주기: 실행 → 중지 → 파기
- 실행:
docker run이나docker start로 지정한 이미지를 기반으로 컨테이너가 생성되면, 그 이미지를 만들 때 Dockerfile에 정의해둔CMD/ENTRYPOINT명령이 실행된다. 데몬처럼 계속 떠있는 컨테이너도 있고, 한 번 작업만 하고 종료되는 컨테이너도 있다. - 중지: 사용자가 명시적으로 멈추거나, 애플리케이션이 정상/비정상 종료되면 컨테이너는 중지 상태가 된다. 이때 직전 상태가 그대로 보존되기 때문에 다시 시작할 수 있다.
- 파기: 중지된 컨테이너는 명시적으로 지우지 않는 한 디스크에 그대로 남아있다. 파기한 컨테이너는 다시 되돌릴 수 없으므로, 더 이상 필요 없는 컨테이너는 정리해주는 것이 좋다.
5. docker run 옵션 정리
docker run은 docker image pull + docker container create + docker container start를 한 번에 처리해주는 명령이라서, 로컬에 이미지가 없으면 자동으로 다운로드까지 수행한다.
1
docker run [옵션] 이미지이름 [인자]
| 옵션 | 의미 |
|---|---|
--name 이름 | 컨테이너 이름 지정 (생략 시 임의 부여) |
-p [호스트포트]:[컨테이너포트] | 포트포워딩. 외부에서 접근해야 하는 서비스는 필수 |
-P, --publish-all | 컨테이너가 쓰는 모든 포트를 임의 포트로 매핑 |
-v(--volume) 호스트경로:컨테이너경로 | 호스트 경로를 컨테이너에 바인딩 (볼륨 마운트) |
--net=네트워크이름 | 지정한 네트워크에 연결 |
-i(--interactive) | 대화식 모드 연결 |
-t(--tty) | 터미널 할당 |
-d(--detach) | 백그라운드 실행. 데몬으로 동작시키려면 필수이며 -it와 같이 쓰는 경우가 많음 |
-e(--env) | 환경 변수 지정 |
--rm | 컨테이너 종료 시 자동 파기 |
--restart=[no\|on-failure\|on-failure:횟수\|always] | 종료 시 재시작 정책 |
-h 이름 | 컨테이너 호스트 이름 지정 (미지정 시 컨테이너 ID) |
--link=컨테이너ID | 지정한 컨테이너와 이름으로 통신 가능하도록 연결 |
-d는 컨테이너를 백그라운드로 돌리는 옵션이고, -i/-t는 내부 터미널에 접속하기 위한 옵션이라 역할이 다르다. -d 없이 실행하면 포그라운드로 떠서 컨테이너 사용이 끝날 때까지 다음 명령을 칠 수 없고, -i/-t 없이는 컨테이너 내부 파일 시스템에 직접 접근할 수 없다.
6. 실행 중인 컨테이너 조회 (docker ps)
| 항목 | 의미 |
|---|---|
| CONTAINER ID | 컨테이너 실행 시 부여되는 ID (원래 64자, 12자만 표시) |
| IMAGE | 사용한 이미지 |
| COMMAND | 컨테이너 실행 시 수행하는 명령 |
| CREATED | 생성 후 경과 시간 |
| STATUS | 현재 상태 (실행 중이면 UP) |
| PORTS | 포트포워딩 결과 (호스트포트 -> 컨테이너포트, 두 포트가 같으면 뒤 번호 생략) |
| NAMES | 컨테이너 이름 |
| 옵션 | 의미 |
|---|---|
-a | 중지된 것까지 전체 조회 |
-q | ID만 표시 |
-aq | 모든 컨테이너의 ID만 표시 |
-l | 가장 최근에 만든 컨테이너 |
-n 숫자 | 최근 N개 |
-s | 컨테이너 크기 표시 |
--no-trunc | ID를 자르지 않고 전체 표시 |
--filter | 조건 검색, 예: docker ps --filter status=running |
--format | 출력 포맷 지정, 예: docker ps --format " " |
중지와 삭제
1
2
3
4
docker stop {컨테이너ID 또는 이름}
docker stop $(docker ps -aq)
docker rm {컨테이너ID 또는 이름}
실습: nginx 컨테이너 생성
1
docker run -d --name nginx -p 8080:80 nginx:1.18
아파치는 80번 포트로 서비스하므로, 아래처럼 호스트 9001번 포트와 매핑해서 접근할 수 있다.
1
docker run --name apacheweb -d -p 9001:80 httpd
컨테이너는 기본적으로 호스트와 격리되어 있기 때문에, 외부에서 접근이 필요한 서비스는 반드시 -p 호스트포트:컨테이너포트로 포트포워딩을 열어줘야 한다.
7. 자주 쓰는 베이스 이미지
| 분류 | 이미지 |
|---|---|
| 운영체제 | ubuntu, centos, debian, fedora, busybox, alpine |
| 웹 서버 | httpd(apache), nginx |
| 데이터베이스 | mysql, postgres, mariadb, redis, mongodb |
| 언어 런타임 | openjdk, python, php, ruby |
| 기타 | registry(도커 레지스트리), nextcloud(private cloud) |
우분투 컨테이너 접속 예시
우분투처럼 대화식으로 다뤄야 하는 이미지는 -it 없이 -d만 주면 바로 종료돼버린다.
1
2
3
4
docker run -d --name ubuntu_server ubuntu # 바로 종료됨
docker run -dit --name ubuntu_server ubuntu # 정상 동작
docker attach ubuntu_server
컨테이너 리소스/로그 확인
1
2
docker stats 컨테이너이름 # 리소스 사용량
docker logs -f 컨테이너이름 # 로그 확인 (시작 안 되거나 바로 종료될 때 원인 파악용)
8. 호스트와 컨테이너 간 파일 공유 (docker cp)
1
2
docker cp 호스트경로 컨테이너이름:컨테이너경로 # 호스트 -> 컨테이너
docker cp 컨테이너이름:컨테이너경로 호스트경로 # 컨테이너 -> 호스트
앞에 쓴 쪽이 원본, 뒤에 쓴 쪽이 목적지다.
아파치 메인 페이지 교체 예시 (index.html 경로: /usr/local/apache2/htdocs/index.html)
1
2
3
4
docker run --name webserver -d -p 9000:80 httpd
docker cp webserver:/usr/local/apache2/htdocs/index.html index.html
# index.html 수정 후
docker cp index.html webserver:/usr/local/apache2/htdocs/index.html
nginx 메인 페이지 교체 예시 (index.html 경로: /usr/share/nginx/html/index.html)
1
2
docker run -d --name nginx_server -p 9001:80 nginx
docker cp index.html nginx_server:/usr/share/nginx/html/index.html
9. 컨테이너에 명령 실행하기 (docker exec)
1
docker exec -it 컨테이너이름 명령
docker attach 대신 docker exec -it 컨테이너이름 /bin/bash처럼 셸 명령을 바로 실행할 수도 있다. attach는 컨테이너의 메인 프로세스에 직접 붙는 것이고, exec는 이미 실행 중인 컨테이너 안에서 별도의 명령을 하나 더 실행하는 것이라는 차이가 있다.
파이썬 스크립트 실행 예시
1
2
3
4
5
6
7
8
# 컨테이너 생성
docker run -dit --name python_test python
# 파일을 컨테이너로 복사
docker cp lotto.py python_test:/
# 컨테이너 안에서 명령 실행
docker exec -it python_test python /lotto.py
Node.js 서버 실행 예시
1
2
3
4
5
6
7
8
# 컨테이너 생성 (포트포워딩 포함)
docker run -dit -p 8000:8000 --name=nodejs node
# 파일 복사
docker cp index.js nodejs:/
# 실행
docker exec -it nodejs node /index.js
10. 컨테이너를 이미지로 만들기 (docker commit)
컨테이너 내부에서 파일을 바꾸거나 프로그램을 설치한 뒤, 그 상태를 그대로 새 이미지로 굳히고 싶을 때 쓴다.
1
docker commit 컨테이너이름 이미지이름[:태그]
11. 정리
컨테이너는 이미지 위에 쓰기 가능한 레이어를 얹은 실행 스냅샷이며, run(=pull+create+start) → 중지 → 파기의 흐름으로 생명주기가 관리된다. -d, -it, -p, -v, --rm 같은 옵션들이 각각 백그라운드 실행, 터미널 접속, 포트포워딩, 볼륨, 자동 삭제라는 서로 다른 역할을 한다는 걸 구분해두면 docker run 명령을 조합해서 쓰기가 훨씬 수월해진다.
컨테이너 안팎으로 파일을 옮길 때는 docker cp, 이미 떠있는 컨테이너에 명령을 추가로 넣고 싶을 때는 docker exec, 지금 상태를 그대로 이미지로 남기고 싶을 때는 docker commit을 쓴다는 식으로 상황별 명령을 나눠서 기억해두면 실무에서 헷갈릴 일이 줄어든다.