Docker 기초 (3) - Installation and First Run
OS별 Docker 설치부터 hello.py와 Dockerfile로 만드는 첫 image의 build, run, 삭제까지 실습으로 정리합니다.
Docker 기초 시리즈의 3편입니다. 전체 목차는 0편에 있습니다.
Installation
macOS
1
2
brew install --cask docker # 또는 공식 사이트의 dmg
open -a Docker # Docker Desktop 앱을 실행해야 daemon이 뜬다
Windows
1
winget install Docker.DockerDesktop # 또는 공식 사이트의 설치 파일
Linux
1
curl -fsSL https://get.docker.com
Version
1
2
docker --version
# Docker version 27.1.1, build ...
버전이 출력되면 설치된 것입니다. 단, macOS와 Windows에서는 Docker Desktop 앱이 실행 중이어야 container를 띄울 수 있습니다. 버전은 나오는데 docker run에서 Cannot connect to the Docker daemon 에러가 나면 앱이 꺼져 있는 것입니다. CLI는 daemon에 요청을 보내는 창구일 뿐이라는 2편의 구조가 여기서 바로 확인됩니다.
First Run
설치를 확인했으니 hello world를 출력하는 image를 직접 만들어(build) 실행(run)해 봅니다.
Setting
아래와 같이 폴더 구조를 만들고 실습을 진행해보겠습니다.
1
2
3
hello-docker/
├── Dockerfile
└── hello.py
hello.py에는 hello world를 출력할 코드를 넣겠습니다.
1
print("hello world")
Dockerfile에는 해당 코드를 실행하기 위한 환경을 설치하는 코드를 넣겠습니다.
1
2
3
4
5
6
# FROM: 바탕이 될 base image를 지정
FROM python:3.12-slim
# COPY: hello.py를 image 안으로 복사
COPY hello.py .
# CMD: 이 image로 container를 시작할 때 실행할 명령
CMD ["python", "hello.py"]
Dockerfile은 image를 만드는 파일입니다. 이름에 주의할 점이 있는데, Dockerfile은 확장자가 아니라 그 자체로 완전한 파일명입니다. docker build가 폴더에서 정확히 이 이름의 파일을 찾도록 약속되어 있어서, Dockerfile.txt처럼 확장자를 붙여 저장하면 찾지 못합니다.
이제 아래 명령을 hello-docker 폴더 안에서 실행합니다.
1
2
3
docker build -t hello . # 현재 폴더(.)의 Dockerfile로 image를 빌드, 이름은 hello
docker images # 만들어진 image 목록 확인
docker run hello
처음 빌드하면 base image를 내려받는 출력이 길게 이어지고, image가 만들어진 다음 hello world가 나옵니다. 만들어진 image는 실습 폴더가 아니라 Docker가 관리하는 로컬 저장소에 저장됩니다.
1
2
3
4
5
6
7
8
$ docker build -t hello .
[+] Building ...
=> [1/2] FROM docker.io/library/python:3.12-slim # base image를 pull
=> [2/2] COPY hello.py .
=> => naming to docker.io/library/hello
$ docker run hello
hello world
실행한 명령들을 하나씩 뜯어보고, 함께 알아둘 ps와 rmi까지 이어서 봅니다.
docker build
1
2
docker build -t hello .
① ② ③ ④
- ①
docker: Docker CLI를 실행합니다 - ②
build: Dockerfile로 image를 만드는 하위 명령입니다 - ③
-t hello: 만들 image의 이름입니다. tag를 생략하면 latest가 붙습니다 (2편의 이름 규칙) - ④
.: 빌드 재료인 Dockerfile과 COPY 할 파일을 찾을 폴더(build context)입니다. 점을 빼먹는 것이 첫 빌드에서 가장 흔한 실수입니다
출력의 [1/2], [2/2]는 전체 2단계 중 몇 번째인지를 뜻하는 진행 표시입니다. 파일 시스템을 만지는 FROM과 COPY가 한 단계씩으로 세어지고, CMD는 실행 설정만 기록해서 세지 않습니다.
그 밖에 자주 쓰는 옵션은 다음과 같습니다.
| 옵션 | 역할 |
|---|---|
-f 파일명 | 기본 이름(Dockerfile)이 아닌 파일로 빌드 |
--no-cache | cache를 무시하고 전부 재빌드 (cache 원리는 6편) |
--platform | 대상 CPU 아키텍처 지정 (8편의 함정) |
--progress=plain | 생략되는 빌드 출력을 전부 표시 |
전체 옵션은 docker build --help로 확인합니다.
docker build를 한 번 더 실행하면 이번에는 순식간에 끝납니다. base image가 이미 로컬에 있고, 2편의 layer 저장 방식 덕분에 다시 받을 것이 없기 때문입니다. Dockerfile 명령어들의 정확한 동작과 cache의 원리는 6편에서 다룹니다.
docker images
빌드가 잘 됐는지는 로컬 image 목록으로 확인합니다. 결과물이 실습 폴더에 파일로 남지 않으므로 확인 창구는 이 명령입니다.
1
docker images
1
2
3
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 3f2a8c91d7e4 10 seconds ago 130MB
python 3.12-slim 9d1c46a8f9b2 2 weeks ago 130MB
방금 만든 hello가 latest tag로 보이고, base로 쓴 python:3.12-slim도 pull 되어 함께 있습니다. 두 image의 SIZE가 비슷한 것은 디스크를 두 배로 쓴다는 뜻이 아닙니다. hello는 python의 layer를 그대로 공유하고 hello.py가 든 layer 하나만 얹었기 때문에, 실제로 새로 쓰인 용량은 몇 KB뿐입니다. 표시되는 SIZE는 공유 layer까지 합친 값이고, 2편의 layer 공유가 여기서도 작동하고 있는 것입니다.
목록의 REPOSITORY와 TAG 두 열이 곧 image의 이름입니다. 2편에서 본 저장소:tag 구조가 목록에서는 두 열로 나뉘어 보이는 것입니다. 로컬 저장소를 이름 기준으로 그려보면 아래와 같은 모양이 됩니다.
1
2
3
4
5
6
7
8
로컬 image 저장소
├── hello # 저장소 하나
│ ├── 1.0 # tag
│ └── 2.0
├── nginx
│ └── latest
└── mysql
└── 8.0
저장소는 같은 소프트웨어의 여러 버전을 tag로 묶어 두는 단위입니다. hello:1.0과 hello:2.0은 이름만 비슷한 남남이 아니라 같은 저장소의 다른 버전이고, 공통 layer는 그대로 공유합니다.
위 그림은 이름이 어떻게 묶이는지를 그린 것이고, 디스크에 hello 폴더나 1.0 폴더가 생긴다는 뜻은 아닙니다. 파일은 layer 단위로 저장되고, hello:1.0 같은 이름은 그 layer들을 가리키는 이름표입니다. 이름이 폴더가 아니어야 여러 image가 같은 layer를 한 벌만 두고 공유할 수 있습니다.
docker run
1
2
docker run hello
① ② ③
- ①
docker: Docker CLI를 실행합니다 - ②
run: image로 container를 만들어 시작하는 하위 명령입니다 - ③
hello: 실행할 image의 이름입니다. 로컬에 없으면 자동으로 pull 합니다
run 하면 image의 CMD에 적힌 명령이 실행되고, 그 프로세스가 끝나면 container도 함께 종료됩니다.
| 옵션 | 역할 |
|---|---|
[명령] (image 이름 뒤) | CMD 대신 실행할 명령 (docker run hello python --version) |
--rm | 종료와 동시에 container 삭제 |
--name 이름 | container 이름 지정. 이후 rm, logs에서 이 이름을 쓴다 |
서버를 띄울 때 쓰는 옵션들(-d, -p, -e, -v, -it)은 4편에서 정리합니다.
docker ps
run이 만든 container가 지금 어떤 상태인지는 이 명령으로 확인합니다.
1
2
docker ps
① ②
- ①
docker: Docker CLI를 실행합니다 - ②
ps: container 목록을 보여주는 하위 명령입니다. 프로세스 목록을 보는 리눅스 명령 ps에서 온 이름입니다
기본으로는 실행 중인 container만 보여줍니다. hello는 출력을 마치는 순간 종료되므로 docker ps에는 비어 있고, -a를 붙여야 종료된 것까지 보입니다.
1
2
3
4
5
6
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 hello "python hello.py" 30 seconds ago Exited (0) 29 seconds ago gifted_moore
STATUS는 container의 생명주기 상태를 보여줍니다.
| 값 | 뜻 |
|---|---|
Up 2 minutes | 실행 중. 시작한 지 2분 됐다는 뜻이다 |
Exited (0) 29 seconds ago | 종료됨. 괄호 안은 exit code로, 0이면 정상 종료, 그 외는 에러로 끝난 것이다 |
Created | 만들어졌지만 아직 시작 안 됨 (docker create를 썼을 때) |
Restarting, Paused | 재시작 중, 일시정지. 초반에는 볼 일이 거의 없다 |
hello는 출력 즉시 끝나기 때문에 이번 실습에서는 Exited만 보입니다. Up은 서버처럼 계속 떠 있는 container를 띄우는 4편에서 보게 됩니다.
NAMES 열도 하나 알아둘 것이 있습니다. --name 없이 만들면 무작위 이름이 자동으로 붙고, 이후 rm, logs에서 ID 대신 이 이름을 쓸 수 있습니다.
| 옵션 | 역할 |
|---|---|
-a | 종료된 container까지 전체 표시 |
-q | ID만 출력. docker rm $(docker ps -aq)처럼 다른 명령과 조합할 때 쓴다 |
--filter | 조건에 맞는 것만 표시 (--filter status=exited) |
docker rmi
1
2
docker rmi hello
① ② ③
- ①
docker: Docker CLI를 실행합니다 - ②
rmi: image를 삭제하는 하위 명령입니다 (remove image) - ③
hello: 삭제할 image의 이름입니다
그 image로 만든 container가 남아 있으면 삭제가 거부됩니다. container를 먼저 지우고(바로 아래 절의 rm) image를 지우는 순서입니다. base로 쓴 python:3.12-slim은 다른 image들이 layer를 공유할 수 있으므로, 정말 쓸 일이 없을 때만 따로 지웁니다.
종료된 container의 상태와 정리
종료된 container는 사라진 것이 아니라, 위 docker ps -a에서 본 대로 Exited 상태로 남아 있습니다. 삭제하는 명령은 rm입니다.
1
docker rm <이름|ID> # 종료된 container 삭제
두 가지를 잡아두면 됩니다.
- run은 매번 새 container를 만듭니다. 위 명령을 세 번 실행하면 Exited container가 세 개 쌓입니다
- 일회성 실행에는
--rm을 붙입니다. 종료와 동시에 container가 삭제되어 뒤처리가 필요 없습니다
1
docker run --rm hello
실습을 마치면 image까지 지워 정리합니다.
1
docker rmi hello # hello image 삭제
다음 편에서 서버처럼 계속 떠 있는 container를 띄우고, 포트 연결과 로그 확인 등 container를 다루는 명령들을 익힙니다.
다음 글: Docker 기초 (4) - Working with Containers: run, logs, and exec