컨테이너와 Docker의 기본 개념 이해하기
왜 컨테이너가 필요할까
개발을 하다 보면 이런 말을 자주 듣습니다.
"내 컴퓨터에서는 잘 되는데 서버에서는 안 됩니다."
이 문제는 대개 애플리케이션 코드만의 문제가 아닙니다. 실행 환경이 조금씩 다르기 때문에 생깁니다. 개발자의 노트북에는 Node.js 22가 설치되어 있는데 서버에는 Node.js 20이 있을 수 있습니다. 개발 환경의 데이터베이스 버전과 운영 서버의 버전이 다를 수도 있습니다. 어떤 서버에는 필요한 라이브러리가 없고, 어떤 서버에는 환경 변수가 빠져 있을 수도 있습니다.
컨테이너는 이런 차이를 줄이기 위해 등장했습니다. 애플리케이션을 실행하는 데 필요한 파일, 라이브러리, 설정, 실행 명령을 하나의 묶음으로 다룹니다. Docker 공식 문서에서는 Docker를 애플리케이션을 개발하고, 전달하고, 실행하기 위한 플랫폼으로 설명하며, 컨테이너를 애플리케이션을 느슨하게 격리된 환경에서 실행하는 단위로 설명합니다.1
중요한 점은 "코드만 옮기는 것"이 아니라 "코드가 실행되는 데 필요한 환경을 함께 다룬다"는 것입니다. 물론 컨테이너가 컴퓨터 전체를 그대로 복사하는 것은 아닙니다. 하지만 애플리케이션이 의존하는 파일, 라이브러리, 설정, 실행 방식을 이미지에 담아 두면 개발자의 컴퓨터, 테스트 서버, CI 서버, 운영 서버에서 더 비슷한 조건으로 애플리케이션을 실행할 수 있습니다.
AI 생성 이미지
컨테이너는 무엇인가
컨테이너는 애플리케이션 프로세스를 격리해서 실행하는 방식입니다. 여기서 프로세스란 실행 중인 프로그램을 뜻합니다. 예를 들어 웹 서버, API 서버, 데이터베이스 서버는 모두 실행되면 프로세스가 됩니다.
일반적으로 한 서버에서 여러 프로그램을 실행하면 서로 같은 운영체제 환경을 공유합니다. 이때 프로그램들이 같은 파일 경로, 같은 포트, 같은 라이브러리 버전을 사용하려 하면 충돌이 생길 수 있습니다.
컨테이너는 각 애플리케이션에 독립된 실행 공간을 제공합니다. 컨테이너 안의 애플리케이션은 자기만의 파일 시스템, 네트워크 설정, 실행 환경을 가진 것처럼 동작합니다. 예를 들어 하나의 컴퓨터에서 다음과 같은 구성을 동시에 실행할 수 있습니다.
web컨테이너: 웹 애플리케이션 실행db컨테이너: PostgreSQL 실행redis컨테이너: 캐시 서버 실행
각 컨테이너는 서로 분리되어 있지만, 필요하면 Docker 네트워크를 통해 통신할 수 있습니다. 웹 애플리케이션은 db 컨테이너에 접속해 데이터를 읽고, redis 컨테이너에 접속해 캐시를 사용할 수 있습니다.
컨테이너를 처음 배울 때는 "작은 서버처럼 보이는 실행 공간"이라고 생각하면 이해가 쉽습니다. 다만 정확히 말하면 컨테이너는 완전한 서버나 가상머신이 아니라, 격리된 환경에서 실행되는 프로세스입니다. 서버 한 대를 새로 만드는 것이 아니라, 호스트 운영체제 위에서 특정 프로세스를 다른 프로세스들과 분리해 실행하는 방식에 가깝습니다.
가상머신과 컨테이너의 차이
Docker를 처음 배울 때 가장 자주 비교되는 대상은 가상머신입니다. 가상머신은 하나의 물리 서버 위에 여러 개의 가상 서버를 만드는 기술입니다. 각 가상머신은 운영체제를 통째로 포함합니다.
예를 들어 한 노트북에서 Ubuntu 가상머신을 실행하면, 그 안에는 Ubuntu 운영체제 전체가 들어갑니다. 커널, 시스템 라이브러리, 기본 도구, 애플리케이션이 모두 포함됩니다.
반면 컨테이너는 보통 호스트 운영체제의 커널을 공유합니다. 커널은 운영체제의 핵심 부분으로, 프로세스 실행, 파일 접근, 네트워크, 메모리 관리 같은 일을 담당합니다. 컨테이너마다 운영체제 전체를 새로 띄우는 것이 아니라, 애플리케이션 실행에 필요한 파일과 사용자 공간의 실행 환경을 분리해서 사용합니다.
차이를 간단히 정리하면 다음과 같습니다.
| 구분 | 가상머신 | 컨테이너 |
|---|---|---|
| 포함하는 범위 | 운영체제 전체와 애플리케이션 | 애플리케이션과 실행에 필요한 파일 |
| 커널 사용 | 가상머신마다 별도 커널 | 보통 호스트 커널 공유 |
| 격리 수준 | 강한 격리 | 프로세스 기반 격리 |
| 실행 속도 | 비교적 느림 | 비교적 빠름 |
| 용량 | 큼 | 작음 |
이 차이 때문에 컨테이너는 빠르게 시작하고, 적은 자원으로 여러 개를 실행하기 좋습니다. 웹 애플리케이션 하나를 테스트하기 위해 운영체제 전체를 부팅할 필요가 없습니다. 필요한 이미지를 가져와 컨테이너를 실행하면 됩니다.
다만 컨테이너가 가상머신보다 항상 더 안전하거나 더 좋은 것은 아닙니다. 목적이 다릅니다. 운영체제 전체를 더 강하게 분리해야 하는 경우에는 가상머신이 더 적합할 수 있습니다. 반대로 애플리케이션 실행 환경을 빠르고 일관되게 만들고 싶다면 컨테이너가 좋은 선택입니다.
한 가지 주의할 점도 있습니다. 이 책에서 주로 다루는 것은 Linux 컨테이너입니다. Linux 서버에서는 컨테이너가 Linux 커널 기능을 직접 사용합니다. macOS나 Windows에서 Docker Desktop으로 Linux 컨테이너를 실행할 때는 내부적으로 Linux 환경을 제공하는 계층이 사용됩니다. 사용자가 별도의 Linux 서버를 직접 관리하지 않더라도, Docker Desktop이 필요한 환경을 뒤에서 준비해 주는 식입니다. 그래서 Docker를 사용하는 입장에서는 대부분 같은 명령과 같은 흐름으로 컨테이너를 다룰 수 있습니다.
Docker를 이루는 네 가지 기본 요소
Docker를 이해하려면 먼저 네 가지 단어를 구분해야 합니다.
이미지
컨테이너
레지스트리
Docker Engine
각각을 따로 외우기보다, 하나의 실행 흐름으로 이해하는 편이 좋습니다.
이미지: 실행 환경의 설계도
이미지는 컨테이너를 만들기 위한 읽기 전용 템플릿입니다. 애플리케이션 코드, 실행에 필요한 라이브러리, 기본 설정, 실행 명령 등이 이미지 안에 들어갑니다. Docker 공식 문서에서도 이미지를 컨테이너를 만들기 위한 읽기 전용 템플릿으로 설명합니다.2
예를 들어 nginx 이미지는 Nginx 웹 서버를 실행하는 데 필요한 파일과 기본 설정을 포함합니다. postgres 이미지는 PostgreSQL 데이터베이스를 실행하는 데 필요한 파일과 설정을 포함합니다.
이미지는 실행 중인 상태가 아닙니다. 프로그램 설치 파일이나 압축된 실행 패키지에 가깝습니다. 이미지를 실행하면 그때 컨테이너가 만들어집니다.
컨테이너: 이미지가 실행된 상태
컨테이너는 이미지를 바탕으로 만들어진 실행 인스턴스입니다. 같은 이미지에서 여러 개의 컨테이너를 만들 수 있습니다.
예를 들어 nginx 이미지 하나로 다음과 같이 여러 컨테이너를 실행할 수 있습니다.
docker run --name web1 nginx
docker run --name web2 nginx두 컨테이너는 같은 이미지에서 출발했지만 서로 다른 실행 상태를 가집니다. web1을 멈춰도 web2는 계속 실행될 수 있습니다.
이미지와 컨테이너의 관계는 클래스와 객체보다, 더 쉽게 말해 "빵틀과 빵"에 가깝습니다. 이미지는 빵틀이고, 컨테이너는 그 빵틀로 찍어낸 실제 빵입니다. 같은 빵틀로 여러 개의 빵을 만들 수 있듯이, 같은 이미지로 여러 컨테이너를 만들 수 있습니다.
레지스트리: 이미지를 보관하는 저장소
레지스트리는 Docker 이미지를 저장하고 내려받는 장소입니다. 가장 대표적인 공개 레지스트리는 Docker Hub입니다. Docker는 필요한 이미지가 로컬 컴퓨터에 없으면 설정된 레지스트리에서 이미지를 내려받고, 만든 이미지를 레지스트리에 올릴 수도 있습니다.3
예를 들어 다음 명령은 nginx 이미지를 내려받습니다.
docker pull nginx나중에 직접 만든 이미지를 팀원이나 서버에서 사용하려면 레지스트리에 올립니다.
docker push myname/myapp:1.0레지스트리는 팀 작업과 배포에서 중요합니다. 개발자가 만든 이미지를 레지스트리에 올리면, CI 서버나 운영 서버는 같은 이미지를 내려받아 실행할 수 있습니다.
Docker Engine: 이미지를 컨테이너로 실행하는 엔진
Docker Engine은 Docker의 핵심 실행 환경입니다. 사용자가 입력한 docker 명령을 받아 이미지, 컨테이너, 네트워크, 볼륨 같은 Docker 객체를 관리합니다. Docker는 클라이언트-서버 구조를 사용하며, Docker 클라이언트가 Docker 데몬에게 요청을 보내고, 데몬이 실제 작업을 수행합니다.4
우리가 터미널에서 입력하는 명령은 Docker 클라이언트가 받아 Docker 데몬으로 전달합니다.
docker run nginx이 명령을 입력하면 실제로는 다음 흐름이 일어납니다.
Docker 클라이언트가 Docker 데몬에 컨테이너 실행을 요청합니다.
Docker 데몬은 로컬에
nginx이미지가 있는지 확인합니다.이미지가 없으면 레지스트리에서 이미지를 내려받습니다.
이미지를 바탕으로 컨테이너를 만듭니다.
컨테이너에 쓰기 가능한 공간과 기본 네트워크를 연결합니다.
컨테이너 안에서 지정된 명령을 실행합니다.
즉, 사용자는 docker run nginx라는 한 줄을 입력하지만, Docker 내부에서는 이미지 확인, 다운로드, 컨테이너 생성, 네트워크 연결, 프로세스 실행이 이어서 처리됩니다.
Docker가 제공하는 가치
Docker의 장점은 단순히 "컨테이너를 실행할 수 있다"가 아닙니다. 개발부터 배포까지 같은 실행 단위를 사용할 수 있다는 점이 핵심입니다.
로컬 개발
개발자는 복잡한 서버 프로그램을 직접 설치하지 않고 컨테이너로 실행할 수 있습니다. 예를 들어 PostgreSQL, Redis, RabbitMQ 같은 도구를 로컬에 직접 설치하면 버전 충돌이나 설정 문제가 생기기 쉽습니다.
Docker를 사용하면 필요한 버전을 명확하게 지정해 실행할 수 있습니다. PostgreSQL 컨테이너를 로컬 개발용으로 실행한다면 다음처럼 버전과 기본 비밀번호를 함께 지정할 수 있습니다.
docker run --name postgres-dev -e POSTGRES_PASSWORD=devpassword -p 5432:5432 -d postgres:16지금은 옵션을 모두 외울 필요가 없습니다. 여기서는 postgres:16처럼 사용할 버전을 명확히 지정할 수 있다는 점만 기억하면 됩니다. 자세한 실행 옵션은 뒤에서 차근차근 다룹니다.
이 방식은 새 팀원이 프로젝트에 합류했을 때 특히 유용합니다. 문서에 적힌 대로 컨테이너를 실행하면 비슷한 개발 환경을 빠르게 만들 수 있습니다.
테스트
테스트 환경은 항상 깨끗해야 합니다. 내 컴퓨터에 우연히 설치된 라이브러리나 남아 있는 데이터 때문에 테스트가 통과하면 안 됩니다.
Docker를 사용하면 테스트용 데이터베이스나 캐시 서버를 매번 컨테이너로 띄우고 지울 수 있습니다. 테스트가 끝나면 컨테이너를 삭제해 상태를 초기화할 수 있습니다.
docker rm -f test-db이렇게 하면 테스트 결과가 특정 개발자의 컴퓨터 상태에 덜 의존합니다.
CI
CI는 Continuous Integration의 줄임말로, 코드를 자주 통합하고 자동으로 검사하는 과정입니다. GitHub Actions, GitLab CI, Jenkins 같은 도구가 여기에 해당합니다.
CI 서버에서 Docker를 사용하면 빌드와 테스트 환경을 코드처럼 관리할 수 있습니다. 개발자가 로컬에서 사용한 이미지와 CI에서 사용하는 이미지를 맞추면 "로컬에서는 통과했는데 CI에서는 실패"하는 문제를 줄일 수 있습니다.
배포
운영 서버에 애플리케이션을 배포할 때도 Docker는 도움이 됩니다. 서버마다 필요한 패키지를 직접 설치하는 대신, 검증된 이미지를 내려받아 컨테이너로 실행할 수 있습니다.
배포 단위가 명확해지면 롤백도 쉬워집니다. 예를 들어 myapp:1.1 버전에 문제가 있으면 이전에 사용하던 myapp:1.0 이미지를 다시 실행할 수 있습니다.
물론 실제 운영에서는 로그, 보안, 네트워크, 볼륨, 모니터링 같은 추가 고려가 필요합니다. Docker를 쓴다고 해서 운영 문제가 자동으로 사라지는 것은 아닙니다. 다만 애플리케이션과 실행 환경을 이미지라는 단위로 다룰 수 있기 때문에, 배포와 복구 과정을 더 명확하게 만들 수 있습니다. 이 책의 뒤쪽 장에서 관련 내용을 하나씩 다룹니다.
이번 장에서 기억할 것
Docker 명령어를 외우기 전에 컨테이너가 해결하려는 문제를 이해해야 합니다.
컨테이너는 애플리케이션을 격리된 실행 환경에서 실행하게 해 줍니다. 가상머신처럼 운영체제 전체를 매번 띄우지 않고, 보통 호스트 커널을 공유하면서 필요한 실행 환경을 분리합니다. 그래서 빠르게 시작하고, 적은 자원으로 여러 서비스를 실행하기 좋습니다.
이미지는 컨테이너를 만들기 위한 템플릿입니다. 컨테이너는 이미지가 실제로 실행된 상태입니다. 레지스트리는 이미지를 저장하고 공유하는 장소입니다. Docker Engine은 이 모든 흐름을 실제로 처리하는 실행 엔진입니다.
앞으로 Docker 명령어를 배울 때는 항상 이 흐름을 떠올리면 됩니다.
이미지 준비 → 컨테이너 생성 → 컨테이너 실행 → 필요하면 레지스트리로 공유다음 장에서는 Docker를 설치하고, 실제로 첫 컨테이너를 실행해 이 흐름을 눈으로 확인합니다.
참고
1What is Docker? | Docker Docs
2What is Docker? | Docker Docs
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.