Docekr 가이드 - 컨테이너 실행과 관리

Docker리눅스의 경량 가상화 기술인 리눅스 컨테이너 기능들을 쉽게 사용할 수 있도록 추상화해둔 리눅스 명령어이자 서버 애플리케이션입니다. 기본적으로 리눅스 컨테이너는 리눅스 커널의 기능을 활용하기 때문에 리눅스를 필요로 하지만, Docker Desktop을 사용하면 macOS나 윈도우에서도 도커를 심리스하게 활용할 수 있습니다.

이렇게 말하면 너무 테크니컬한가요? docker 명령어 하나로 이런 걸 할 수 있습니다. Docker는 가상 머신이 아니라 심지어 몇 초만에 실행이 됩니다. 리눅스 배포판에서 셸을 실행하거나,

$ docker run -it --rm rockylinux:9 bash

워드프레스 블로그를 실행하거나,

$ docker run -d --rm -p 8080:80 -d wordpress:latest

딥러닝 환경이 구축된 Jupyter 서버도 한 방에 실행할 수 있습니다.

$ docker run -d --rm --gpus all -p 8888:8888 tensorflow/tensorflow:latest-gpu-py3-jupyter

이건 빙산의 일각입니다. 리눅스에서 가능한 모든 작업이, docker 명령어 한 방에 가능해집니다. 무슨 일이 벌어지고 있는 건지 이해가 되지 않는다면, 잘 찾아오셨습니다 🤗. 날 잡고 이 글 하나만 따라해보신다면, Docker 명령어를 만났을 때 반갑게 느껴지실 겁니다.

Docker 가이드에서는 Docker를 처음 사용하는 사람이라면 누구나 따라하면서 Docker의 핵심 기능들을 사용해볼 수 있도록 작성한 시리즈입니다. 첫 번째 글에서는 Docker 설치부터 간단한 컨테이너 실행까지 배워봅니다.

Docker 설치하기(윈도우, 리눅스, macOS 모두 가능!)#

자, 그럼 함께 Docker를 시작해보겠습니다. 그런데, 잠깐만요. Docker, 설치는 하고 오셨나요?

Docker를 사용하려면 먼저 Docker Desktop이나 Docker Engine을 설치해야합니다. 윈도우나 맥OS를 사용한다면 Docker Desktop을 추천하고, 리눅스에서는 Docker Engine을 직접 설치하는 걸 추천합니다. 두 방식의 차이는 다음과 같습니다.

  • Docker Engine: 리눅스 서버에 직접 설치하는 Docker 서버와 클라이언트입니다.
  • Docker Desktop: 리눅스 커널을 사용하는 경량 가상 머신에 Docker Engine을 설치합니다. 윈도우나 맥OS 호스트 머신에서 심리스하게 Docker를 사용할 수 있도록 인터페이스를 제공하고 디스크 및 네트워크 설정을 관리합니다.

먼저 윈도우10/11의 모든 에디션에서는 현재 WSL2를 기반으로 Docker Desktop을 사용할 수 있습니다. 아래 글을 참고해서 Docker Desktop을 설치해주세요.

맥OS에서도 Dockekr Desktop을 설치해주세요.

리눅스를 사용한다면 Docker 설치 스크립트를 사용해 명령어 하나로 Docker Engine을 설치할 수 있습니다.

$ curl -fsSL https://get.docker.com | sudo sh

sudo 없이 docker 명령어를 사용하려면 사용자를 docker 그룹에 추가해야합니다. 다음 명령어를 실행하고 재부팅을 한 번 해주세요.

$ sudo groupadd docker
$ sudo usermod -aG docker $USER

리눅스에서 Docker를 설치하는 방법에 대한 더 자세한 해설은 다음 글을 참고해주세요.

시놀로지 DSM에서도 Docker를 사용할 수 있습니다. DSM에서는 컨테이너 매니저 패키지를 설치해야합니다.

이 튜토리얼을 시작하려면 터미널에서 docker 명령어를 실행할 수 있어야합니다. 터미널을 열고, docker version 명령어를 실행해서 현재 설치된 Client와 Engine의 버전을 확인해봅니다. 윈도우를 사용한다면 파워셸 앱이나 윈도우 터미널 앱을 사용해주세요. 윈도우 터미널에서 파워셸을 사용하는 걸 강력 추천합니다. 맥에서는 터미널 앱이나 iTerm2를 사용합니다. 리눅스에도 터미널 앱이 있습니다. 리눅스 환경에 익숙하다면 SSH로 원격에서 따라해도 무방합니다.

파워셸에서 docker version을 실행한 화면

정상적으로 Docker가 설치 및 실행되었다면 Clinet 버전과 Server 버전이 모두 출력될 것입니다. Docker Engine이 정상적으로 동작중인지 확인하려면 docker ps 명령어를 실행해보면 됩니다.

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

이렇게 나오면 정상 동작중입니다.

운영체제별로 메시지가 약간 다릅니다만, 아래와 비슷한 메시지가 출력된다면 Docker Engine이 실행되지 않은 상태입니다.

$ docker 
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

이 때는 Docker Desktop을 종료 후 재실행하거나, 리눅스라면 도커 서비스를 재시작해줍니다.

$ sudo systemctl restart docker.socket docker.service

그리고 다시 docker ps를 실행해서 정상 동작하는지 확인해봅니다.

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

시작이 절반이라고들 하죠? 도커는 설치가 절반입니다. 여기까지 성공했다면 이제 절반은 온 셈입니다.

docker run으로 시작하는 리눅스 컨테이너#

그럼 준비가 끝났으니, 진짜로 시작해보겠습니다. 튜토리얼의 모든 명령어는 직접 실행해보는 것을 추천 드립니다. 무작정 따라해보세요.

$ docker run -it --rm rockylinux:9 bash

처음 실행하면 30초 정도가 걸립니다. 여기서 중요한 건, 셸 프롬프트가 바뀐다는 점입니다. 예제에서는 PS C:\> 셸 프롬프트가, [root@36f72a7008df /]#로 바뀌었습니다.

rockylinux:9 이미지에서 bash 셸을 실행합니다

여기서 rockylinux:9는 레드햇 엔프라이즈 리눅스 계열의 Rocky Linux 9버전을 의미합니다. 우분투와 같은 리눅스 배포판 중 하나라고 이해해도 무방합니다. 새로운 셸에서 /etc/redhat-release 파일의 내용을 출력해봅니다.

[root@36f72a7008df /]# cat /etc/redhat-release
Rocky Linux release 9.2 (Blue Onyx)

Rocky Linux라고 출력 됩니다. 우리는 지금 Rocky Linux 셸에 있습니다. 즉, 호스트 머신과는 다른 리눅스에서 작업중이라는 의미입니다. 고작 30초만에요? 아닙니다. 사실 더 빠릅니다. 여기서는 exit로 셸을 종료하고, 다시 한 번 같은 명령어를 실행해보겠습니다.

다시 rockylinux:9에서 bash 셸을 실행합니다

직접 실행해보셨나요? 이번에는 좀 더 간소하게 실행됩니다. 확신하는데 1초가 안 걸렸을 겁니다. 그럼 이번에는 Debian 리눅스를 실행해보겠습니다. 다시 exit로 셸을 종료하고 다음 명령어를 실행해주세요.

$ docker run -it --rm debian:12 bash

이번에는 처음 실행했을 때처럼 Pulling 어쩌고 메시지들이 출력되면서 30초 정도 시간이 걸릴 것입니다.

debian:12 이미지에서 bash 셸을 실행합니다
$ docker run -it --rm debian:12 bash
Unable to find image 'debian:12' locally
12: Pulling from library/debian
d52e4f012db1: Pull complete
Digest: sha256:3d868b5eb908155f3784317b3dda2941df87bbbbaa4608f84881de66d9bb297b
Status: Downloaded newer image for debian:12
root@5dc4959b9ff5:/#

여기서 /etc/debian_version 파일을 출력해봅니다.

root@5dc4959b9ff5:/# cat /etc/debian_version
12.0
root@5dc4959b9ff5:/#

이제 우리는 Debian 리눅스 배포판 환경에서 작업중입니다. 정말로요? docker run 명령어에 대해서 더 자세히 알아봐야겠습니다.

docker run 명령어 이해하기#

docker 명령어는 대부분 서브 커맨드로 구성되어 있습니다. 여기까지 docker rundocker ps 명령어를 실행해보았습니다. 이와 같이 docker 명령어는 다음과 같은 형식으로 사용합니다. 이 형식은 대략적인 형식이니 참고용으로만 봐주세요. 여기서 ()안의 내용은 생략가능한 옵셔널 값입니다.

docker <SUBCOMMAND> (<OPTIONS>)

docker run 명령어의 형식은 다음과 같습니다.

docker run (<OPTIONS>) <IMAGENAME> (<COMMAND>)

자 그럼 앞에서 실행했던 명령어를 한 번 뜯어보겠습니다.

$ docker run -it --rm rockylinux:9 bash

docker run까지는 어렵지 않게 이해가 됩니다. 다음으로 -it는 2개의 옵션입니다. 풀어 쓰면 -i, -t가 됩니다. 리눅스에서는 한글자 옵션의 경우 -를 붙이고 한꺼번에 지정할 수 있습니다. 다음으로 --rm은 하나의 옵션입니다. 여기까지가 (<OPTIONS>)에 해당하는 부분입니다. 여기서 중요! 그 다음으로 오는 rockylinux:9<IMAGENAME>이 됩니다. 그럼 자연스럽게 bash(<COMMAND>)가 됩니다.

docker run 명령어 형식

자 옵션은 제쳐두고,지금 주목할 건 rockylinux:9 이 부분입니다. rockylinux:9은 Docker 이미지 이름입니다. 여기서 이미지는 그림 파일이라는 의미가 아닙니다. 가상 머신을 사용해보셨다면, OS 설치 디스크나 가상머신을 이미지라고 부르는 것을 들어보셨을 것입니다. 약간의 차이가 있습니다만, 지금은 비슷한 개념이라고 이해해도 무방합니다.

즉, 앞서 실행한 docker run 명령어는 rockylinux:9 이미지를 사용해서, 이어지는 bash 명령어를 실행하라는 의미입니다. 우리는 앞서 Rocky Linux 환경을 확인해보았습니다. 다시 볼까요?

[root@36f72a7008df /]# cat /etc/redhat-release
Rocky Linux release 9.2 (Blue Onyx)

Rocky Linux에서 Docker를 사용하는 게 아니라면, 이 파일은 호스트의 /etc/ 디렉터리에는 존재하지 않는 파일입니다.

자, 그럼 이해를 돕기 위해 Docker를 매우 고성능 가상 머신이라고 생각해주세요. docker run을 실행하면 rockylinux:9 이미지로 순식간에 가상머신을 실행합니다. 그런데 docker run은 사실 ��� 단계로 나뉩니다. 앞에서 Rocky Linux를 두 번 실행해보았습니다. 처음 실행할 때는 30초 정도 걸렸다면, 두 번째는 딜레이 없이 바로 실행되었을 겁니다.

$ docker run -it --rm rockylinux:9 bash
Unable to find image 'rockylinux:9' locally
9: Pulling from library/rockylinux
1a5eb4db1701: Pull complete
Digest: sha256:b07e21a7bbcecbae55b9153317d333d4d50808bf5dc0859db0180b6fbd7afb3d
Status: Downloaded newer image for rockylinux:9
[root@36f72a7008df /]#

이 때 처음 실행했을 때 자세히 보면, Unable to find image라는 문구를 볼 수 있습니다. 즉, Docker는 로컬에 지정한 이미지가 있는지 확인한 다음, 없으면 Docker Hub라는 외부 이미지 저장소에서 이미지를 다운로드 받습니다. 도커에서는 전문 용어로 이미지를 풀 받는다(pull)고 표현합니다. 그리고 이 이미지를 기반으로 bash 명령어를 실행합니다. 또 전문 용어 하나 나옵니다. docker run으로 실행한 프로세스를 컨테이너라고 부릅니다.

그래서 같은 명령어를 두 번째 실행할 때는 이미지를 풀 받는 과정이 없으므로, 딜레이 없이 바로 컨테이너가 실행 됩니다. 풀 받는 시간이 없다면, 실행 시간은 거의 제로입니다.

docker ps로 컨테이너 목록 확인하기#

그럼 마지막에 실행했던 명령어 docker run도 다시 한 번 살펴보겠습니다.

$ docker run -it --rm debian:12 bash

이제는 대충 어떻게 동작하는지 보입니다. debian:12 이미지를 기반으로 bash 컨테이너를 실행합니다. 자 Debian 셸을 그대로 두고, 터미널을 하나 더 열어주세요. 윈도우 터미널을 사용하고 있다면 Shift 키를 누른 채로 + 버튼을 클릭하면 터미널 창이 하나 더 만들어집니다.

컨테이너 목록을 확인합니다

왼쪽에는 bash 컨테이너가 실행된 상태입니다. 오른쪽의 터미널에서는 docker ps 명령어를 실행합니다. 앞에서는 이 명령어를 Docker Engine이 정상적으로 실행되었는지 확인하는 용도로 사용했습니다. 이 명령어는 컨테이너 목록을 확인할 때 사용합니다. docker container ls와 같습니다.

$ docker ps
CONTAINER ID   IMAGE       COMMAND   CREATED       STATUS       PORTS     NAMES
5dc4959b9ff5   debian:12   "bash"    3 hours ago   Up 3 hours             distracted_mclean

$ docker container ls
CONTAINER ID   IMAGE       COMMAND   CREATED       STATUS       PORTS     NAMES
5dc4959b9ff5   debian:12   "bash"    3 hours ago   Up 3 hours             distracted_mclean

IMAGE와 COMMAND는 docker run을 실행할 때 지정했습니다. CREATED는 이 컨테이너를 생성한 시간, STATUS는 현재 상태와 실행되어있는 시간을 의미합니다. 여기서 Up은 실행중이라는 의미입니다. 그럼 NAMES와 CONTAINER ID가 남는데요. 이 값들은 컨테이너를 구분하기 위한 고유값입니다. ID는 임의로 생성됩니다. NAMES의 값도 따로 지정을 하지 않으면 Docker가 랜덤하게 생성해줍니다. PORTS에 대해서는 서버 컨테이너를 실행할 때 다시 알아보겠습니다.

Docker 컨테이너의 생애주기#

여기까지 컨테이너를 실행하는 docker run과 컨테이너의 목록과 상태를 확인하는 docker ps 명령어를 공부했습니다. 그렇다면 컨테이너의 생애주기와 관련된 명령어도 잠깐 살펴보도록 하겠습니다. 컨테이너의 기본적인 생애주기는 “생성 -> 실행 -> 종료 -> 삭제”입니다. 여기서는 생성과 실행은 하나로 묶어서 실행으로 두겠습니다. 그렇다면 종료와 삭제가 남습니다.

앞에서 rockylinux:9debian:12 이미지로 Bash 셸을 실행해보았습니다. 일회성 프로그램들은 실행하면, 작업을 마치고 자동으로 종료됩니다. 하지만 셸은 명시적으로 종료할 때까지(exit) 명령어 입력을 기다��는 특수한 명령어입니다. 셸 프롬프트에서 exit 명령어를 실행해서 셸을 종료할 수도 있지만, docker stop 명령어를 사용해서 컨테이너를 종료시킬 수도 있습니다.

앞의 예제에서 왼쪽 창에는 Debian의 Bash 셸이 실행중인 상태입니다. docker stop로 이 컨테이너를 종료시켜보겠습니다. 이 때 종료할 컨테이너의 ID나 이름을 지정해줍니다. 즉, docker ps의 목록에서 해당 컨테이너의 ID가 5dc4959b9ff5인 것을 확인할 수 있습니다. 아래 두 명령어는 모두 같은 명령어입니다. 둘 중 맘에 드는 명령어를 실행해주세요.

$ docker stop distracted_mclean
$ docker stop 5dc4959b9ff5
실행중인 컨테이너를 강제 종료합니다

조금 다르게 실행할 수도 있습니다. 종료할 컨테이너 ID 값으로 5dc4959b9ff5이 아닌 5dc4959을 입력해도 결과는 동일합니다.

$ docker stop 5dc495

강제 종료는 kill 명령어를 사용합니다.

$ docker kill 5dc495

자세히 보면 5dc495docker ps에서 보이는 ID 값의 앞 여섯 글자입니다. ID 앞부분만으로 컨테이너를 구분할 수 있을 때는, ID 값을 앞부분 일부만 지정해도 컨테이너를 특정할 수 있습니다. 따라서 위의 세 명령어는 모두 같은 컨테이너를 종료하는 명령어입니다.

이 명령어를 실행하면 컨테이너가 종료됩니다. 스크린샷에서는 왼쪽 터미널에서 Debian 셸이 종료되고, 파워셸로 되돌아온 것을 확인할 수 있습니다. 그렇다면 docker ps로 상태를 확인해보겠습니다.

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

컨테이너 목록이 비어있습니다. 그런데 docker ps 명령어로는 종료한 컨테이너는 확인할 수 없습니다. 이 때는 -a 옵션을 사용해야 모든 컨테이너 목록이 출력됩니다.

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

하지만 -a 옵션을 사용해도 컨테이너 목록이 비어있습니다. 바로 여기에 앞에서 docker run을 실행할 때 지정했던 --rm 옵션의 비밀이 있습니다. 이 옵션은 컨테이너가 종료될 때, 컨테이너를 삭제해줍니다.

그렇다면 다른 예제로 종료된 컨테이너를 확인해보겠습니다. 이제 명령어를 보면 어떤 작업을 하는지 감이 오시나요?

$ docker run -it debian:12 uname -a
Linux a8683fd98c2b 5.15.90.1-microsoft-standard-WSL2 #1 SMP Fri Jan 27 02:56:13 UTC 2023 x86_64 GNU/Linux

먼저 이번에는 --rm 옵션을 사용하지 않았습니다. 두 번째로 debian:12 이미지를 사용합니다. 명령어는 uname -a를 지정했습니다. uname -a는 시스템 정보를 출력하고 바로 종료됩니다. 따라서 왼쪽 창에서 docker run을 실행하자마자 파워셸 프롬프트로 돌아온 것을 확인할 수 있습니다.

종료된 컨테이너의 상태를 확인해봅니다

그리고 오른쪽 창에서 docker ps -a를 실행해보면, 이번에는 Exited 상태의 컨테이너가 보입니다. 바로 우리가 방금 실행한 컨테이너입니다. 이 컨테이너는 docker rm 명령어로 삭제할 수 있습니다.

$ docker rm bdb151be27fc

다시 docker ps -a 명령어로 컨테이너 목록을 확인해봅니다.

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

다시 컨테이너 목록이 비어있습니다. 종료와 삭제를 묶어서 사용할 수도 있습니다. 예제를 위해 sleep 명령어로 컨테이너를 하나 실행해보겠습니다. 이 컨테이너는 1000초 후에 종료됩니다.

$ docker run -it debian:12 sleep 1000

docker rm-f 옵션을 붙여서 실행하면, 컨테이너가 실행중일 때 컨테이너를 강제 종료하고 삭제합니다.

$ docker rm -f 30740d
rm -f 명령어로 컨테이너를 강제 종료하고 삭제합니다

컨테이너가 종료되고, 삭제된 것까지 확인할 수 있습니다. 여기까지 간단히 컨테이너 생애주기를 살펴보았습니다.

컨테이너의 생애주기
  • docker run으로 컨테이너를 생성 및 실행하고, 명령어가 끝나면 컨테이너가 자동으로 종료됩니다.
  • docker stop 명령어로 컨테이너를 종료할 수 있습니다.
  • docker kill 명령어로 컨테이너를 강제 종료할 수 있습니다.
  • 종료 상태의 컨테이너는 docker rm 명령어로 삭제할 수 있습니다.
  • 컨테이너가 실행중이더라도 docker rm -f 명령어로 컨테이너를 강제 종료하고 삭제할 수 있습니다.

쉬어가는 코너: Docker 기초 명령어 복습#

벌써 많은 docker 명령어들을 공부했습니다. 잊어버리지 않도록 한 번 복습하고 지나가겠습니다. 먼저 컨테이너를 실행하는 docker run 명령어입니다. 이제 딱 보면, 어떤 일을 하는지 감이 오실 겁니다. 감이 와야합니다. 제일 중요한 명령어니 꼭 기억해주세요.

$ docker run -it --rm rockylinux:9 bash
$ docker run -it --rm debian:12 uname -a

다음으로 Docker 컨테이너 목록을 보는 docker ps 명령어입니다. docker psdocker container ls와 같습니다. 그리고 실행중이지 않은 모든 컨테이너를 보려면 -a 옵션을 사용합니다.

$ docker ps
$ docker container ls
$ docker ps -a

실행 중인 컨테이너를 종료할 때는 docker stopdocker kill 명령어를 사용합니다. docker ps에서 확인할 수 있는 컨테이너 ID, 컨테이너 이름을 지정해 종료할 수 있습니다. 참고로 docker stopdocker container stop은 같은 명령어입니다.

$ docker stop <CONTAINER>
$ docker container kill <CONTAINER>
$ docker stop <CONTAINER>
$ docker container kill <CONTAINER>

다음으로 docker rm은 종료된 컨테이너를 삭제할 때 사용합니다. 잠깐 컨테이너 생애주기도 다시 한 번 보고 가겠습니다.

복습: 컨테이너의 생애주기

실행중인 컨테이너를 종료하고 삭제까지 하려면 -f 옵션을 사용합니다. 그리고 docker rmdocker container rm과 같습니다.

$ docker rm <CONTAINER>
$ docker rm -f <CONTAINER>
$ docker contanier rm <CONTAINER>
$ docker contanier rm -f <CONTAINER>

중지된 모든 컨테이너를 삭제할 때는 docker contanier prune을 명령어를 사용합니다.

$ docker container prune

여기까지 함께 컨테이너를 실행하고 다루는 법에 대해서 알아보았습니다. 개발자를 위한 도커 시리즈는 다음 글 이미지 이야기로 이어집니다.