docker
도커(Docker)에 대한 기초를 알아보도록 하겠습니다. 도커를 사용하는 가장 큰 이유를 한문장으로 정리하면, Application의 실행 환경을 동일하게 구성하여 어디에서든 동일한 환경에서 실행하는 것을 보장하기 위해 사용한다고 할 수 있습니다.
Docker는 대표적인 컨테이너 소프트웨어 입니다. 컨테이너란 컴퓨터 내에 특정 시스템 환경을 별도로 패키징한 것을 의미합니다. 소프트웨어는 패키징된 환경에서 실행될 수 있으며, 이 환경은 시스템의 나머지 부분과 격리됩니다. 컨테이너는 가상머신과 비슷하지만, 가상머신은 아닙니다. 컨테이너는 가상머신보다 더 날렵하며 기동과 정지도 더 빠르고 유연성과 이동성도 더 뛰어납니다. 컨테이너는 단 몇 초 만에 확장하고 축소할 수 있으며, 클라우드와 같은 탄력적인 환경에서 애플리케이션을 더 쉽게 구동할 수 있습니다.
도커는 컨테이너를 위한 공통 툴 세트를 제공해 애플리케이션을 컨테이너 이미지로 패키징해 어디에서나 쉽게 배치하고 재사용할 수 있습니다. 컨테이너 이미지란 컨테이너를 만드는 모체로 쉽게 생각해 클래스/인스턴스 객체 간의 관계를 생각할 수 있습니다. 도커는 컨테이너 이미지를 생성하고 관리하고 공유하고 여러 곳으로 옮기고 도커 호환 호스트에 배치할 수 있게 함으로써, 컨테이너 구동 작업을 매우 간단한 것으로 만들어 버립니다.
$ docker create {이미지 이름}
$ docker create hello-world
docker 컨테이너를 생성하는 명령어입니다. docker run 생명주기 중 앞부분에 해당하며, 컨테이너 하드디스크에 파일 스냅샷을 올려주게 됩니다. 생성이 되면서 아래 라인에 생성된 컨테이너 ID 값을 출력해주는 걸 확인할 수 있습니다.
$ docker start {컨테이너 아이디/이름}
$ docker start a67cf6a54f15
docker 컨테이너를 실행하는 명령어로, docker run 생명주기 뒷부분에 해당하며, 컨테이너에 프로세스 실행 시작 명령어가 적용된다. docker start -a {컨테이너 아이디/이름} 의 형태인 경우 세부 출력문이 나오고, -a를 활용하지 않으면 아래처럼 그냥 입력한 값을 그대로 표출해줍니다. 컨테이너 아이디 위치엔 ID식별자 전체를 입력할 필요없이, 앞 부분만 잘라 넣어줘도 인식이 가능합니다.
> docker start 23358
23358
$ docker run {이미지 이름}
$ docker run hello-world
위의 생성(create)과 실행(start)을 동시에
이미지를 확보한 뒤에는 컨테이너 생성과 실행을 위한 실제 작업이 시작됩니다. 우선 이미지 내부에 포함된 파일 시스템 스냅샷을 기반으로, 가상의 컨테이너 파일 시스템이 구성되며, 이는 도커가 관리하는 격리된 환경 위에 구축됩니다. 이 과정에서 컨테이너는 별도의 커널을 가지지 않고, 호스트 운영체제의 커널을 공유하게 됩니다. 즉, 도커의 모든 컨테이너는 하나의 커널을 함께 사용하는 구조입니다.
다음으로, 해당 컨테이너가 정상적으로 동작하기 위해 필요한 리소스들이 할당됩니다. CPU, 메모리, 네트워크 등의 자원이 지정된 설정에 따라 배분되며, 이 리소스 기반 위에서 애플리케이션이 구동될 준비를 마칩니다. 이후 이미지 내부에 정의된 ENTRYPOINT나 CMD에 따라 애플리케이션이 실행됩니다. 예를 들어, docker run alpine ls와 같은 명령을 입력하면, alpine 이미지 안에서 ls 명령이 실행되어 파일 목록이 출력됩니다.
다만 모든 이미지가 동일한 방식으로 동작하는 것은 아닙니다. 예를 들어 hello-world와 같은 단순 이미지의 경우, 실행 가능한 쉘 환경이나 파일 시스템이 포함되어 있지 않기 때문에 ls 명령을 전달하면 "executable file not found"와 같은 오류가 발생할 수 있습니다. 즉, 사용 가능한 명령어는 이미지의 종류와 구조에 따라 달라지므로, 적절한 명령어 사용이 중요합니다.
이처럼 docker run 명령어는 이미지 확인, 다운로드, 컨테이너 구성, 리소스 할당, 명령 실행이라는 일련의 단계를 자동으로 처리하며, 사용자는 하나의 명령만으로 애플리케이션을 빠르고 간편하게 실행할 수 있습니다.
$ docker stop {컨테이너 아이디/이름}
$ docker stop 4f6213c6c5c0
$ docker kill {컨테이너 아이디/이름}
$ docker kill 4f6213c6c5c0
docker stop 사용 시, 대략 5초 이상의 딜레이 이후에 중지 되었고, docker kill 사용 시, 곧장 중지 됩니다. docker 컨테이너 중지 생명주기 중엔 SIGTERM(정리하는 시간)과 SIGKILL(중지) 구간이 있습니다. stop 명령어는 그동안 하던 작업들을 되도록 완료한 뒤에 컨테이너를 중지시키고, stop은 SIGTERM 을 거친 후, SIGKILL을 이행한다. kill 명령어는 어떠한 것도 기다리지 않고 바로 컨테이너를 중지시킨다. 이는 SIGTERM 없이, SIGKILL을 이행하는 것을 의미합니다.
# docker 컨테이너 삭제
$ docker rm {컨테이너 아이디/이름}
$ docker rm 4f6213c6c5c0
# 실행중인 docker 컨테이너 삭제
$ docker rm -f {컨테이너 아이디/이름}
$ docker rm -f 4f6213c6c5c0
# 모든 컨테이너 삭제
$ docker rm `docker ps -a -q`
# 도커 이미지 삭제
$ docker rmi {이미지 id}
기본적으로 Docker 컨테이너를 삭제하려면, 실행중인 컨테이너를 우선 중지한 후에 삭제가 가능합니다. docker rmi와 docker rm은 도커에서 각각 이미지와 컨테이너를 삭제하는 명령어이며, 목적과 대상이 다릅니다. docker rmi는 도커 이미지(Image)를 삭제하는 명령어입니다. 이미지는 컨테이너를 만들기 위한 일종의 설계도나 템플릿으로, 실행 가능한 상태는 아니지만 여러 컨테이너를 생성할 수 있는 기반이 됩니다. 따라서 docker rmi는 더 이상 필요하지 않거나 디스크 공간을 확보하기 위해 사용되지 않는 이미지를 제거할 때 사용됩니다. 단, 현재 어떤 컨테이너가 해당 이미지를 참조하고 있다면, 그 이미지의 삭제는 실패하게 됩니다. 먼저 관련된 컨테이너를 삭제해야만 이미지 삭제가 가능합니다.
반면 docker rm은 도커 컨테이너(Container)를 삭제하는 명령어입니다. 컨테이너는 이미지로부터 만들어진 실행 가능한 인스턴스로, 실제로 프로그램이 실행되는 환경입니다. 컨테이너는 실행 중이거나 중지된 상태일 수 있는데, 기본적으로 docker rm은 중지된 컨테이너만 삭제할 수 있으며, 실행 중인 컨테이너를 삭제하려면 -f 옵션을 사용하여 강제로 종료한 뒤 삭제해야 합니다. 일반적으로 로그가 쌓이거나 테스트용으로 생성한 컨테이너를 정리할 때 사용됩니다.
요약하자면, docker rmi는 이미지 자체를 삭제하는 데 쓰이고, docker rm은 그 이미지로부터 실행된 컨테이너를 삭제하는 데 쓰입니다. 이미지와 컨테이너는 서로 다른 개념이므로, 둘의 명령어도 용도와 작동 방식에서 분명한 차이가 있습니다.
docker system prune 명령어는 도커에서 사용하지 않는 컨테이너, 이미지, 네트워크 등을 한 번에 모두 정리할 수 있는 유용한 명령어입니다. 도커 환경을 더 이상 사용하지 않거나 전체를 초기화하고 싶을 때 매우 적합하게 사용할 수 있습니다. 다만, 주의할 점은 이 명령어 역시 실행 중인 컨테이너에는 영향을 주지 않으며, 오직 중지된 리소스들만 삭제된다는 점입니다.
또한, 도커 컨테이너 삭제는 컨테이너 생명주기에서 삭제 단계에 해당하며, 명령어를 실행하면 즉시 반영됩니다. 단, 중요한 점은 컨테이너가 실행 중인 상태일 경우 기본적으로 삭제할 수 없고, 삭제는 오직 중지된 컨테이너에만 적용된다는 사실을 기억해야 합니다.
도커를 사용할 때, 이미 실행 중인 컨테이너에 명령어를 전달하고 싶을 때는 docker exec 명령어를 활용합니다. 이는 매우 자주 사용되는 명령어 중 하나이며, 컨테이너 내부에서 디버깅을 하거나, 일시적으로 명령을 실행해야 할 때 특히 유용합니다.
예를 들어, 아래와 같이 입력하면 특정 컨테이너 안에서 ls 명령어를 실행해볼 수 있습니다.
$ docker exec {컨테이너 아이디} {명령어 키워드}
$ docker exec 5d4da2695441 ls
여기서 docker exec은 docker run과의 중요한 차이가 있습니다. docker run은 새로운 컨테이너를 생성하고 그 안에서 명령어를 실행합니다. 즉, 이미지로부터 새로운 인스턴스를 만들어내는 과정이 포함되어 있습니다. 반면, docker exec은 이미 실행 중인 컨테이너에 접근해서 명령어를 실행하는 방식이므로, 컨테이너의 상태를 유지한 채 필요한 작업만 추가로 수행할 수 있습니다.
또한 docker exec은 -it 옵션과 함께 자주 사용되며, 이는 인터랙티브 모드와 터미널 연결을 활성화시켜 줍니다. 이 조합은 쉘 환경을 직접 다루고자 할 때 매우 유용하며, 컨테이너 내부에서 수동으로 조작이 필요할 때 사용됩니다. 예를 들면 다음과 같습니다:
위 명령어는 실행 중인 컨테이너 안에 bash 쉘을 띄우고, 사용자가 직접 명령을 입력할 수 있는 상태로 전환해줍니다. /bin/bash는 bash 셸을 실행하겠다는 의미이며, 일부 컨테이너에서는 sh를 사용해야 할 수도 있습니다 (/bin/sh).
이러한 방식은 운영 중인 서비스 컨테이너에 접근하여 로그를 직접 확인하거나, 설정 파일을 점검하거나, 내부 동작을 실시간으로 테스트할 때 매우 효과적입니다. 단, 프로덕션 환경에서 컨테이너 내부에 직접 접속해 작업하는 것은 가능한 신중하게 해야 하며, 변경사항은 영구적으로 저장되지 않을 수 있으므로 주의가 필요합니다.
도커 이미지를 백업하거나 다른 시스템에 옮기고 싶을 때, 흔히 사용하는 방식 중 하나가 docker save 명령어를 사용하여 tar 파일로 저장하는 것입니다. 이 명령은 도커 이미지를 하나의 파일로 직렬화(serialize) 하여 압축 아카이브 형태로 만드는 기능을 제공합니다.
$ docker save -o [OUTPUT-FILE] IMAGE
$ docker save -o my-app-v1.tar my-app:v1
이 명령어는 로컬에 존재하는 my-app:v1 이미지를 my-app-v1.tar라는 이름의 tar 파일로 저장합니다. 결과적으로 my-app-v1.tar 파일이 생성되며, 이 파일은 다른 환경에서도 동일한 이미지로 복원할 수 있는 패키지가 됩니다.
docker load 명령어는 docker save로 생성한 .tar 이미지 파일을 다시 도커에 불러올 때 사용합니다. 이 명령어를 실행하면 .tar 파일 속 이미지가 도커 이미지 목록에 등록되어, 마치 docker pull로 다운받은 것처럼 사용할 수 있게 됩니다.
$ docker load -i [INPUT-FILE]
$ docker load -i my-app-v1.tar
$ docker images
이 명령어는 현재 로컬 도커 환경에 존재하는 모든 이미지를 리스트로 보여줍니다. 출력 결과에는 이미지의 REPOSITORY(이름), TAG(버전), IMAGE ID, 생성 시점(CREATED), 그리고 용량(SIZE) 등의 정보가 포함됩니다.
이 명령어를 통해 docker build, docker pull, docker load 등의 작업 후 이미지가 잘 등록되었는지 확인할 수 있으며, 불필요한 이미지를 찾아 삭제할 때도 참고됩니다.
도커에서 실행 중인 컨테이너나 이전에 실행한 컨테이너를 확인하려면 docker ps 명령어를 사용합니다. 실습을 제대로 해보려면 최소 두 개의 터미널 창이 있으면 좋습니다.
첫 번째 터미널에서 컨테이너를 실행합니다. 예를 들어 다음 명령을 입력해 봅시다.
$ docker run alpine
위 명령어는 alpine 이미지를 기반으로 컨테이너를 생성하고 실행합니다. 이제 두 번째 터미널을 열어 다음 명령을 실행해 봅니다.
$ docker ps
이 명령은 현재 실행 중인 컨테이너 목록만 보여줍니다. 만약 실행이 이미 종료된 컨테이너까지 모두 보고 싶다면, -a 옵션을 추가합니다.
$ docker ps -a
이렇게 하면 종료된 컨테이너, 실패한 컨테이너 등 모든 컨테이너의 내역을 확인할 수 있습니다. 아래는 docker ps나 docker ps -a 명령 실행 시 나타나는 주요 항목들의 의미입니다.
많은 컨테이너 정보 중 필요한 것만 보고 싶다면 --format 옵션을 활용할 수 있습니다. 예를 들어, 컨테이너 이름과 이미지 이름만 보고 싶다면 다음과 같이 입력합니다.
$ docker ps --format 'table{{.Names}}\t{{.Image}}'