포스트

Docker 기초 (3) - Installation and First Run

OS별 Docker 설치부터 hello.py와 Dockerfile로 만드는 첫 image의 build, run, 삭제까지 실습으로 정리합니다.

Docker 기초 (3) - Installation and First 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-cachecache를 무시하고 전부 재빌드 (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까지 전체 표시
-qID만 출력. 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

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.