Docker 가이드 - 도커 이미지의 이해
Docker 가이드 첫 번재 글에서는 컨테이너 기본적은 사용 방법과 컨테이너 생애주기에 대해서 알아보았습니다. 컨테이너를 사용할 때 빼먹을 수 없는 개념 중 하나가 바로 이미지입니다. 두 번째 글에서는 도커 이미지에 대해서 중점적으로 살펴보고, 외부 이미지를 받아올 수 있는 도커 허브에 대해서도 알아봅니다. 도커 이미지는 컨테이너만큼이나 중요한 개념이니 끝까지 따라와주세요.
Docker 이미지와 컨테이너#
여기까지 컨테이너를 조작하는 기본적인 방법을 알아보았습니다. 아직 이미지에 대해서는 설명을 하지 않았는데요. 여기서 Docker 이미지가 무엇인지 조금 더 짚어보고 넘어가도록 하겠습니다.
$ docker run -it --rm rockylinux:9 uname -a
위의 명령어에서 rockylinux:9이 이미지라는 것은 이미 이해하고 계실 겁니다. 이 이미지는 Rocky Linux의 9 버전 이미지입니다.
일반적으로 uname -a라는 명령어를 실행하면, 현재 시스템에서 이 명령어를 찾아서 실행합니다. 예를 들어 윈도우에는 uname이 없기 때문에 다음과 같이 에러가 출력됩니다.
$ uname -a
uname : 'uname' 용어가 cmdlet, 함수, 스크립트 파일 또는 실행할 수 있는 프로그램 이름으로 인
식되지 않습니다. 이름이 정확한지 확인하고 경로가 포함된 경우 경로가 올바른지 검증한 다음 다
시 시도하십시오.
맥OS에서는 다음과 같이 시스템 정보가 출력됩니다.
$ uname -a
Darwin MacBookPro.localdomain 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64
Docker 이미지는 어떤 명령어가 실행되는 환경을 제공합니다. 거칠게 설명하자면 가상 머신 이미지와도 비슷합니다. 다음 명령어는 rockylinux:9 이미지의 Rocky Linux 9 환경에서 uname -a를 실행하라는 의미입니다. 다음은 WSL 기반 윈도우 Docker Desktop에서 실행한 결과입니다.
$ docker run -it --rm rockylinux:9 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
분명 Windows에는 uname이라는 명령어가 없습니다만, Docker를 기반으로 uname 명령어를 실행할 수 있습니다. 앞에서는 docker run으로 실행한 프로세스를 컨테이너라고 부른다고 소개했습니다. 좀 더 넓은 의미에서 보면 컨테이너는 어떤 명령어를 실행하는 환경과 그 위에서 실행한 프로세스까지 포함하는 개념입니다.
rockylinux:9 리눅스에는 uname 명령어만 있는 게 아닙니다. 앞에서 직접 실행해보았듯이 bash 셸도 있습니다. 그 외에도 리눅스에 기본적으로 포함되어있는 다양한 실행 파일들이 있습니다. docker run의 마지막 인자를 바꿔주기만 하면 다른 명령어들도 실행할 수도 있습니다.
$ docker run -it rockylinux:9 uname -a
Linux 07cc0e89811e 5.15.90.1-microsoft-standard-WSL2 #1 SMP Fri Jan 27 02:56:13 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
$ docker run -it rockylinux:9 whoami
root
$ docker run -it rockylinux:9 locale
LANG=
LC_CTYPE="POSIX"
[...]
$ docker run -it rockylinux:9 date
Mon Jul 10 18:44:34 UTC 2023
docker run으로 실행한 명령어들은 rockylinux:9 이미지를 기반으로 실행되고, 결과를 출력하고 종료됩니다. docker run 명령어 하나가 하나의 컨테이너가 됩니다. 여기서는 일부러 --rm 옵션을 사용하지 않았습니다. docker ps -a를 실행해 컨테이너 목록을 확인해봅니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9ee95191c0fe rockylinux:9 "date" 37 seconds ago Exited (0) 30 seconds ago epic_galois
4ede26be7958 rockylinux:9 "locale" 43 seconds ago Exited (0) 36 seconds ago wizardly_dubinsky
32f508de3acb rockylinux:9 "whoami" 47 seconds ago Exited (0) 40 seconds ago funny_lumiere
07cc0e89811e rockylinux:9 "uname -a" 51 seconds ago Exited (0) 44 seconds ago blissful_hypatia
각각의 컨테이너들은 모두 독립적으로 실행되며, 상태를 공유하지 않습니다. 한 가지 실험을 해보겠습니다. 다음 명령어는 date 명령어를 삭제하고 date 명령어를 실행합니다. 여기서는 여러 명령어를 한 줄에서 실행하기 위해서 bash -c 명령어를 사용했습니다. 다음으로 따옴표 사이에 여러 명령어들을 ; 세미콜론으로 구분지어 주면 여러 명령어를 실행할 수 있습니다. 결과는 명령어를 찾을 수 없다고 에러가 출력됩니다.
$ docker run -it --rm rockylinux:9 bash -c "rm /usr/bin/date; date"
bash: line 1: date: command not found
그렇다면 다시, rockylinux:9 이미지로 date 명령어를 실행하면 성공할까요? 실패할까요?
$ docker run -it --rm rockylinux:9 date
Mon Jul 10 18:52:44 UTC 2023
정상적으로 동작합니다. 앞에서 설명한대로 컨테이너는 상태를 공유하지 않습니다. 즉, 어떤 컨테이너에서 파일을 삭제해도, 다른 컨테이너에는 영향을 끼치지 않습니다. 그리고 rockylinux:9 이미지에는 date 명령어가 있습니다. 컨테이너에서 이 이미지의 내용을 바꿀 수는 없습니다. 이 이미지로 컨테이너를 실행한다면 항상 date 파일이 있습니다. 따라서 Docker가 설치되어있고, 같은 Docker 이미지를 사용하면 항상 같은 결과를 얻을 수 있습니다. 이게 바로 Docker가 자랑하는 이식성입니다.
Docker에서 컨테이너와 이미지는 가장 핵심적인 기능입니다. 하지만 처음 Docker를 사용하면 이 개념들이 혼란스럽게 느껴질 수 있습니다. 다음 글에서는 컨테이너와 이미지에 대해서 상세히 알아보고, 두 개념의 차이에 대해서도 알아봅니다.
- 관련 글: Docker 컨테이너와 이미지란?
애플리케이션 이미지를 하나 생각해보겠습니다. rockylinux:9 이미지에는 nginx 서버가 설치되어있지 않습니다. nginx는 웹 서버 애플리케이션 중 하나입니다.
$ docker run -it --rm rockylinux:9 nginx
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "nginx": executable file not found in $PATH: unknown.
Docker가 마법 같지만 모든 일이 정말 마법처럼 동작하지는 않는 법입니다. 그렇다면 혹시 nginx가 설치된 nginx 이미지가 있지는 않을까요? 리눅스를 사용해보신 분들은 아시겠지만, 마치 패키지를 찾는 것처럼 약간의 상상력이 필요합니다.
$ docker run -d -p --name nginx-8080 8080:80 nginx
[...]
76b8c8c36f6421a62564c26ad7d7b8d5f76798870fd5143c0ac55f4f47ad0d19
잠시 하던 일을 멈추고, 웹 브라우저를 열어서 localhost:8080 혹은 127.0.0.1:8080 페이지를 열어봅니다. Welcome to nginx! 페이지가 나타나면 성공입니다.
- 관련 글: localhost:8080란?
아직 설명하지 않은 부분들이 있습니다만, 뒤에서 다시 돌아오겠습니다. 우선은 이 이미지가 어디서 오는지부터 같이 알아보죠.
이번 절에서는 예제를 위해서 일부러 컨테이너를 종료된 상태로 남겨두었습니다. 종료된 컨테이너가 많아진다고 당장 시스템이 느려지는 건 아닙니다만, 관리가 귀찮아지고 필요없는 컨테이너가 누적 됩니. 그 전에 컨테이너를 한 번 정리해주세요. 앞에서 배운 docker rm 명령어로 한땀한땀 지워보는 것도 좋은 경험입니다.
$ docker rm 07cc0e89811e
$ docker rm 32f508de3acb
$ docker rm 4ede26be7958
$ docker rm 9ee95191c0fe
아직은 괜찮습니다. 그런데 컨테이너가 수십, 그리고 수백개씩 쌓여있으면 어떨까요? 이 많은 컨테이너를 한땀한땀 지운다고 생각하면 정신이 아득해집니다. 이럴 때는 docker container prune 명령어를 사용해서 종료된 컨테이너를 일괄 삭제할 수 있습니다.
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
9ee95191c0feced3ede19a3a19fb7a55c590b5479daaa449c9b0c7d315d49db7
4ede26be7958e04a4d6a1e6c70ebcef053669306c93a3a8108470715664dddb7
32f508de3acb9278cf687e4ae6c35eba2eae4ada3f83795761083d886486e88d
07cc0e89811e64bde8ccb0b05316bdec4a07e72f44d9bc221ff1616283cf7721
Total reclaimed space: 34B
매우 유용한 명령어니 꼭 기억해주세요.
눈치 채신 분들도 있겠지만, 컨테이너를 삭제할 때 컨테이너 ID가 docker ps에서 보던 것보다 훨씬 더 기네요. 하지만 자세히 보면 앞 부분은 같습니다. docker ps에서 보여주는 컨테이너 ID도 사실은 축약된 ID입니다.
Docker 이미지와 Docker Hub#
지금까지 rockylinux:9, debian:12, 그리고 nginx를 이미지를 사용해보았습니다. 중간에 :가 있는 경우도 있고, 없는 경우도 있습니다. docker images 명령어를 사용하면, 현재 로컬에 저장된 이미지 목록을 확인할 수 있습니다.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 021283c8eb95 6 days ago 187MB
debian 12 3676c78a12ad 6 days ago 116MB
rockylinux 9 eeea865f4111 6 weeks ago 176MB
이 명령어는 docker image ls와 같습니다. 둘 다 자주 사용됩니다.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 021283c8eb95 6 days ago 187MB
debian 12 3676c78a12ad 6 days ago 116MB
rockylinux 9 eeea865f4111 6 weeks ago 176MB
앞에서 실행했던 이미지들도 이 목록에서 확인할 수 있습니다. 여기서 알 수 있는 건, 이미지 이름을 입력할 때 저장소(Repository)와 태그(Tag)를 :으로 연결주었다는 점입니다. 예를 들어 저장소가 debian이고 태그가 12이면 debian:12가 됩니다. nginx는 조금 특이한 케이스입니다. :으로 태그를 붙여주지 않는다면 최신을 의미하는 latest 값이 자동으로 사용됩니다. 즉, nginx는 nginx:latest와 같습니다.
Git을 사용해본 분들이라면 저장소라는 표현이 반가울 텐데요. Docker에서는 이미지가 저장되어있는 곳을 저장소라고 부릅니다. Docker에는 Git에서 따온 표현들이 많이 보입니다. 단, 주의해야할 점이 있습니다. 하나의 이미지 저장소에서는 다수의 태그가 있습니다. 저장소가 같더라도 태그가 다르면 그건 전혀 다른 이미지입니다. 저장소는 논리적인 단위일 뿐이고, 같은 저장소에서 태그로 구분된 이미지들이 어떤 연관성을 가져야하는 건 아닙니다.
추가적으로 (컨테이너 ID와 비슷한) 이미지 ID, 그리고 이미지가 만들어진 시점과 용량도 확인할 수 있습니다. 용량이 작지는 않은데요. 이미지로 컨테이너를 실행할 때마다 이 용량을 사용하는 건 아니니 안심하시기 바랍니다.
컨테이너들은 이미지를 공유해서 사용하기 때문에, 컨테이너를 실행한다고 용량이 추가로 필요하지는 않습니다. 대신 컨테이너들은 컨테이너들에서 사용하는 고유한 디스크를 가지고 있습니다. 컨테이너는 이 디스크에 사용하는 만큼만 추가적으로 용량을 차지합니다. 컨테이너가 사용하는 용량을 이해하려면, Docker의 유니온 마운트 개념을 이해해야합니다. 입문 단계에서 이 정도까지 이해할 필요는 없습니다만, Docker의 동작 원리가 궁금하다면 다음 글을 추천합니다.
이 이미지들은 Docker Hub에서 찾을 수 있습니다. debian 저장소에 가보면, 이 이미지에 대한 상세한 설명을 볼 수 있습니다.
Docker Hub에서도 배울 수 있는 게 많이 있습니다. 오른쪽 상단에는 이 이미지를 풀 받는 방법이 나와있습니다.
$ docker pull debian
앞에서 설명한대로 docker run 명령어는 로컬에 이미지가 없다면, docker pull을 실행해서 이미지를 다운로드 받습니다. docker pull 명령어를 사용하면 docker run과 별개로 이미지를 풀 받을 수 있습니다. 여기서 debian의 태그는, 앞에서 설명한대로 latest입니다. 따라서 이 명령어는 다음 명령어와 같습니다.
$ docker pull debian:latest
저장소의 설명을 보면 latest가 12 버전인 것을 알 수 있습니다. latest는 명시적으로 버전을 지정하지 않기 때문에 정확히 몇 버전인지 알기가 어렵습니다. 여기서는 11.7 버전을 풀 받아보겠습니다.
$ docker pull debian:11.7
11.7: Pulling from library/debian
34df401c391c: Pull complete
Digest: sha256:a648e10e02af129706b1fb89e1ac9694ae3db7f2b8439aa906321e68cc281bc0
Status: Downloaded newer image for debian:11.7
docker.io/library/debian:11.7
도커 이미지 목록을 출력해보면 debian:11.7이 추가된 것을 확인할 수 있습니다.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 021283c8eb95 6 days ago 187MB
debian 11.7 189a2f977ff1 6 days ago 124MB
debian 12 3676c78a12ad 6 days ago 116MB
rockylinux 9 eeea865f4111 6 weeks ago 176MB
debian:11.7 이미지로 /etc/debian_verison을 출력해서 버전을 확인해봅니다.
$ docker run -it --rm debian:11.7 cat /etc/debian_version
11.7
미리 이미지를 풀 받았기 때문에, 이미지 풀 받는 과정 없이 바로 실행이 됩니다. 도커 이미지를 다운로드 받는 docker pull 명령어에 대한 더 자세한 해설은 다음 글을 참고해주세요.
설명을 읽어보면 debian 저장소에 태그가 매우 많다는 것을 알 수 있습니다. 설명 페이지 외에도 Tags 탭에 가보면 현재 이미지의 모든 태그를 확인할 수 있습니다.
또 하나 중요한 점은 debian 이미지가 도커 공식 이미지(Docker Official Image)라는 점입니다. 이 말은 이 이미지를 믿고 사용할 수 있다는 의미입니다. 이미지를 풀 받을 때 출력을 자세히 보면 library/debian라는 표현을 찾아볼 수 있습니다.
$ docker pull debian:11.7
11.7: Pulling from library/debian
34df401c391c: Pull complete
Digest: sha256:a648e10e02af129706b1fb89e1ac9694ae3db7f2b8439aa906321e68cc281bc0
Status: Downloaded newer image for debian:11.7
docker.io/library/debian:11.7
debian의 조금 더 긴 이름(풀 네임은 따로 있습니다)이 바로 library/debian입니다. 태그까지 붙여보면 library/debian:11.7입니다. 여기서 / 구분자 앞의 library는 Docker Hub의 사용자명을 나타냅니다. library는 공식 이미지를 제공하는 특수한 사용자입니다. 그리고 library/는 생략할 수 있습니다. 일반 사용자가 Docker Hub에 업로드한 이미지라면 lainyzine/debian:11.7 같이 사용자명을 생략할 수 없습니다. 이미지 이름 규칙을 지금 다 알 필요는 없습니다. Docker Hub를 참고해서 이미지를 풀 받거나, docker run 명령어를 실행하는 정도면 충분합니다.
하나 놀라운 점은 이 이미지가 10억 회(1B+) 이상 풀 되었다는 점입니다. 정말 많이 사용되는 이미지입니다. 리눅스 배포판 이미지는 컨테이너로 직접 활용하기보다는, 다른 이미지를 만드는 베이스 이미지로 많이 사용 됩니다. 리눅스 배포판 위에 애플리케이션을 실행할 수 있는 환경을 구축하면 특정 애플리케이션을 바로 실행할 수 있는 Docker 이미지를 만들 수 있습니다. 이를 위해 Docker에서는 공식적으로 다양한 리눅스 배포판 이미지들을 제공하고 있습니다.
- 관련 글: Docker와 리눅스 배포판 공식 이미지들
nginx 이미지도 찾아보겠습니다.
nginx 이미지도 10억 회 이상 풀 된 Docker 공식 이미지 중 하나입니다. 태그를 자세히 보면 bookworm이 보입니다. bookwarm은 Debian 12의 코드 명입니다. nginx 이미지가 바로 리눅스 배포판 이미지를 기반으로 만들어진 애플리케이션 이미지인 셈입니다. 저장소 페이지에서는 태그를 비롯해 사용법 등 다양한 정보를 얻을 수 있으니, 새로운 이미지를 사용한다면 한 번은 확인해보시기 바랍니다. Docker Hub와 이미지 저장소를 보는 방법은 다음 글에서 더 자세히 소개하고 있으니 참고해주세요.
이미지는 디스크 용량을 차지합니다. 인터넷에 연결되어 있으면 언제든 다운로드 받을 수 있기 때문에 로컬에 꼭 필요하지 않은 이미지를 미리 다운로드 받아놓을 필요는 없습니다. 이미지를 삭제할 때는 docker rmi 혹은 docker image rm을 사용합니다. 아래 두 명령어는 같습니다. 이미지를 삭제하기 전에는, 먼저 이 이미지를 사용하는 모든 컨테이너를 종료해야합니다.
$ docker rmi debian:11.7
$ docker image rm debian:11.7
도커 이미지 관리는 은근히 까다로운 일입니다. 특히 컨테이너로 실행중인 이미지를 지울 때 조심해야합니다. 사용중인 컨테이너를 모두 종료하고 이미지를 삭제해주세요. rmi 명령어는 -f 옵션으로 이미지가 사용중이라도 삭제할 수 있는 옵션을 제공합니다만, 이는 강제 삭제가 아니라 이미지의 이름만 지울 뿐입니다. docker rmi 명령어의 동작에 대한 더 자세한 설명은 다음 글을 참고해주세요.
이미지 관련 명령어 복습#
다음으로 이미지 목록은 docker images 명령어로 확인할 수 있습니다. docker image ls도 같은 명령어입니다.
$ docker images
$ docker image ls
docker run을 실행하면 로컬에 이미지가 없으면 자동으로 풀 받습니다. 하지만 필요한 이미지를 미리 풀 받는 방법도 있습니다. docker pull 명령어를 사용합니다. docker image pull도 같은 명령어입니다.
$ docker pull debian:11.7
$ docker image pull debian:11.7
이미지 삭제는 rmi 혹은 docker image rm 명령어를 사용합니다.
$ docker rmi
$ docker image rm
벌써 컨테이너와 이미지에 관련된 14개 명령어를 사용해보았습니다. 중복을 제외해도 8개 명령어를 배웠습니다. 이 명령어들이 Docker를 사용하는 데 가장 많이 사용하고, 핵심적인 명령어이기도 합니다.
도커에는 컨테이너나 이미지를 조작하는 같은 명령어가 2가지씩 있는 경우가 있습니다. 이는 Docker 명령어의 컨텍스트를 명확하게 나타내기 위해서 docker image <COMMAND>와 docker container <COMMAND> 명령어들이 나중에 추가되었기 때문입니다. 예를 들어 rm은 컨테이너를 삭제하고, rmi는 이미지를 삭제하는 명령어입니다만, 명령어만 놓고보면 rm이 이미지를 삭제해도 이상하지 않습니다. rmi에서 i는 이미지인가요? 아마 그렇겠지만 정확한 건 알 수 없습니다. 반면에 docker container rm은 컨테이너를 삭제하고 docker image rm은 이미지를 삭제하는 명령어라서 의미가 명확합니다. 그럼에도 여전히 기존 명령어들이 더 많이 사용 됩니다. 직접 사용할 때는 선호하는 명령어를 사용하면 되지만, 두 가지 형식을 모두 익혀두는 것을 추천합니다.
여기까지 컨테이너를 조작해보고, 이미지에 대해서도 알아보았습니다. 도커의 가장 중요한 개념인 컨테이너와 이미지를 이해한다면 이제 기본은 갖추었다고 할 수 있습니다. 여기서부터는 실전입니다. 다음 글에서는 도커를 활용해서 실제로 컨테이너를 어떻게 사용하는지 알아보겠습니다. 세 번째 글에서는 도커 컨테이너를 포터블 앱으로 활용하는 방법을 소개합니다.