Docker는 애플리케이션을 컨테이너라는 표준화된 단위로 패키징하고 실행하기 위한 오픈 소스 플랫폼이다. 이 기술은 애플리케이션 코드, 런타임, 시스템 도구, 시스템 라이브러리 등 모든 필요한 요소를 하나의 이미지로 묶어, 어떤 환경에서도 일관되게 실행될 수 있도록 보장한다. Docker의 등장은 데브옵스 문화의 확산과 함께 소프트웨어 개발, 배포, 운영 방식을 혁신적으로 변화시켰다.
Docker의 핵심 가치는 환경의 일관성과 격리성에 있다. 기존에는 개발, 테스트, 운영 환경 간의 차이로 인해 "내 컴퓨터에서는 되는데"라는 문제가 빈번했다. Docker 컨테이너는 이러한 환경 차이를 애플리케이션 레벨에서 추상화하여 해결한다. 이를 통해 개발자는 로컬에서 구축한 환경을 그대로 프로덕션 서버에 배포할 수 있으며, 시스템 관리자는 서로 다른 애플리케이션이 동일한 호스트 머신에서 충돌 없이 독립적으로 실행되도록 관리할 수 있다.
Docker 생태계는 크게 세 가지 핵심 구성 요소로 이루어져 있다. Docker 이미지는 애플리케이션을 실행하는 데 필요한 모든 것을 담은 읽기 전용 템플릿이다. 이 이미지를 실행한 인스턴스가 바로 Docker 컨테이너이며, 컨테이너는 이미지에 정의된 환경에서 프로세스가 격리되어 동작하는 상태를 말한다. 생성된 이미지는 Docker 레지스트리에 저장되고 공유될 수 있으며, Docker Hub는 가장 대표적인 공개 레지스트리 서비스이다.
이 기술은 마이크로서비스 아키텐처, CI/CD 파이프라인, 클라우드 네이티브 컴퓨팅의 기반이 되었다. 각 서비스를 독립된 컨테이너로 구성함으로써 확장성과 유지보수성을 높이고, 자동화된 빌드 및 배포 프로세스에 쉽게 통합될 수 있다. 결과적으로 Docker는 애플리케이션의 이식성을 극대화하고, 인프라의 활용도를 높이며, 개발부터 배포까지의 전체 라이프사이클을 가속화하는 데 기여한다.
컨테이너는 애플리케이션과 그 실행에 필요한 모든 요소(라이브러리, 시스템 도구, 코드, 런타임)를 하나의 패키지로 묶은 표준화된 소프트웨어 단위이다. 이는 애플리케이션이 컨테이너화되어 있으면, 호스트 시스템의 환경과 독립적으로 일관되게 실행될 수 있음을 의미한다.
가상 머신은 하이퍼바이저를 통해 물리적 하드웨어를 가상화하여 각각의 완전한 게스트 운영 체제를 포함한다. 반면, Docker 컨테이너는 호스트 운영 체제의 커널을 공유하며, 애플리케이션 계층만 격리한다. 이 차이로 인해 컨테이너는 일반적으로 더 가볍고, 시작 속도가 빠르며, 리소스 효율성이 높다.
특성 | Docker 컨테이너 | |
|---|---|---|
격리 수준 | 하드웨어 수준 | 운영 체제 수준 |
시작 시간 | 느림 (분 단위) | 빠름 (초 단위) |
성능 오버헤드 | 상대적으로 높음 | 상대적으로 낮음 |
이미지 크기 | 대형 (GB 단위) | 소형 (MB 단위) |
호스트 커널 | 공유하지 않음 | 공유함 |
Docker의 핵심 구성 요소는 이미지, 컨테이너, 레지스트리이다. 이미지는 컨테이너를 생성하기 위한 읽기 전용 템플릿으로, 애플리케이션과 환경을 정의한다. 이 이미지를 실행한 인스턴스가 바로 컨테이너이다. 레지스트리는 Docker 이미지를 저장하고 배포하는 서비스이며, Docker Hub가 공식 공개 레지스트리이다. 사용자는 레지스트리에서 이미지를 풀(pull) 받아 사용하거나, 자신이 생성한 이미지를 푸시(push)하여 공유할 수 있다.
컨테이너와 가상 머신은 모두 애플리케이션을 격리된 환경에서 실행하기 위한 기술이지만, 그 접근 방식과 구조에서 근본적인 차이를 보인다.
가상 머신은 하이퍼바이저를 통해 물리적 서버(호스트 머신) 위에 여러 개의 완전한 가상 머신을 생성한다. 각 가상 머신은 자체적인 게스트 운영체제, 라이브러리, 애플리케이션을 포함하며, 호스트의 하드웨어를 가상화한다. 이로 인해 가상 머신은 크기가 수 GB에 달할 수 있으며, 완전한 운영체제 부팅 과정을 거쳐야 하므로 상대적으로 시작 속도가 느리다. 반면, Docker 컨테이너는 호스트 운영체제의 커널을 공유한다. 컨테이너는 애플리케이션과 그 실행에 필요한 라이브러리, 바이너리, 설정 파일만을 패키징한 경량의 실행 환경이다. 이는 호스트 OS 위에서 격리된 프로세스로 실행되므로, 부팅이 필요 없어 시작 속도가 매우 빠르며, 이미지 크기도 보통 수백 MB 이하로 작다.
아래 표는 두 기술의 주요 차이점을 요약한다.
특성 | 가상 머신 (VM) | Docker 컨테이너 |
|---|---|---|
격리 수준 | 하드웨어 수준의 강력한 격리 | 운영체제(프로세스/파일 시스템) 수준의 격리 |
게스트 OS | 각 VM마다 완전한 게스트 OS 필요 | 호스트 OS 커널 공유, 게스트 OS 불필요 |
시작 시간 | 느림 (OS 부팅 시간 소요) | 매우 빠름 (초 단위) |
성능 오버헤드 | 상대적으로 높음 (하드웨어 가상화) | 매우 낮음 (네이티브에 가까움) |
이미지 크기 | 대형 (GB 단위) | 소형 (MB 단위) |
이식성 | 호환되는 하이퍼바이저 환경 필요 | 호스트 OS 커널만 같다면 높은 이식성 |
결과적으로, 가상 머신은 서로 다른 운영체제를 필요로 하는 워크로드나 완전한 하드웨어 격리가 필요한 경우에 적합하다. 반면, 컨테이너는 동일한 운영체제 커널 위에서 수많은 마이크로서비스 애플리케이션을 빠르게 배포하고 확장해야 하는 현대적인 클라우드 네이티브 개발 및 데브옵스 환경에서 선호된다.
Docker의 핵심 구성 요소는 Docker 이미지, Docker 컨테이너, 그리고 Docker 레지스트리이다. 이 세 가지 개념은 서로 긴밀하게 연결되어 Docker 생태계의 기본을 이룬다.
Docker 이미지는 애플리케이션과 그 실행에 필요한 모든 것을 포함하는 읽기 전용 템플릿이다. 이는 운영 체제, 런타임, 라이브러리, 환경 변수, 구성 파일, 그리고 애플리케이션 코드 자체를 계층적으로 패키징한 것이다. 이미지는 Dockerfile이라는 텍스트 파일에 정의된 일련의 명령어를 통해 빌드된다. 각 명령어는 새로운 레이어를 생성하며, 이러한 레이어 구조는 이미지의 효율적인 저장, 공유 및 재사용을 가능하게 한다. 예를 들어, ubuntu:22.04나 nginx:alpine과 같은 형식으로 이름과 태그가 지정된다.
이미지를 실행 가능한 인스턴스로 만든 것이 Docker 컨테이너이다. docker run 명령어를 통해 이미지로부터 컨테이너를 생성하고 시작한다. 컨테이너는 이미지의 최상위에 쓰기 가능한 레이어를 추가하여 실행된다. 이 레이어에서 발생하는 모든 변경사항은 컨테이너의 생명주기 동안만 유지된다. 컨테이너는 격리된 프로세스로 호스트 머신에서 실행되며, 필요한 네트워크 인터페이스와 저장소가 할당된다. 하나의 이미지로부터 여러 개의 독립적인 컨테이너를 동시에 실행할 수 있다.
개념 | 설명 | 특징 |
|---|---|---|
Docker 이미지 | 애플리케이션 실행을 위한 모든 종속성을 포함한 읽기 전용 템플릿 | 계층적 구조, 불변성, Dockerfile로 정의 |
Docker 컨테이너 | 이미지를 실행한 격리된 인스턴스 | 실행 가능, 쓰기 가능 레이어 추가, 일시적 또는 지속적 상태 보유 |
Docker 레지스트리 | Docker 이미지를 저장하고 배포하는 서비스 | 공개(Docker Hub) 또는 사설 레지스트리 운영 가능 |
Docker 레지스트리는 Docker 이미지를 저장하고 배포하는 서비스이다. 가장 대표적인 공개 레지스트리는 Docker Hub이다. 개발자는 자신이 빌드한 이미지를 레지스트리에 푸시(push)하여 팀이나 공개 커뮤니티와 공유할 수 있으며, 필요한 이미지를 풀(pull) 받아 사용한다. 기업은 보안과 성능을 위해 JFrog Artifactory, Harbor, Amazon ECR과 같은 사설 레지스트리를 구축하여 내부에서 이미지를 관리한다. 레지스트리는 이미지의 버전을 태그로 관리하며, 접근 제어와 취약점 스캔 같은 기능을 제공하기도 한다.
Docker 엔진은 리눅스 커널의 컨테이너 기능을 기반으로 하므로, 설치 방법은 호스트 운영체제에 따라 다릅니다. 주요 OS별 설치 경로는 다음과 같습니다.
운영체제 | 권장 설치 방법 | 비고 |
|---|---|---|
공식 APT 저장소 |
| |
공식 YUM/DNF 저장소 | ||
Docker Desktop for Mac | GUI 애플리케이션으로 제공됨 | |
Windows 10/11 Pro/Enterprise | Docker Desktop for Windows | |
Windows 10/11 Home | Docker Desktop for Windows (WSL 2 백엔드) |
대부분의 리눅스 배포판에서는 공식 스크립트를 이용한 설치도 가능합니다. 설치 후에는 docker --version 명령어로 정상 설치를 확인하고, sudo usermod -aG docker $USER 명령으로 현재 사용자를 docker 그룹에 추가하여 sudo 권한 없이 Docker CLI를 사용할 수 있게 설정합니다.
설치가 완료되면 Docker 데몬(dockerd)이 백그라운드 서비스로 실행됩니다. 이 데몬은 이미지 관리, 컨테이너 실행, 네트워크 및 스토리지 자원을 제어하는 핵심 엔진입니다. 사용자는 주로 Docker CLI(명령행 인터페이스)를 통해 데몬과 상호작용합니다. 기본 설정 파일(/etc/docker/daemon.json)을 수정하여 레지스트리 미러 설정, 로그 드라이버 변경, 디버깅 옵션 조정 등의 환경 구성을 할 수 있습니다.
Docker는 리눅스 커널의 컨테이너 기술을 기반으로 하므로, 리눅스 환경에서 네이티브로 실행된다. 다른 운영체제에서는 가상 머신을 통해 리눅스 환경을 구성한 뒤 Docker를 설치하는 방식을 취한다.
주요 운영체제별 설치 방법은 다음과 같다.
운영체제 | 권장 설치 방법 | 주요 특징 |
|---|---|---|
공식 저장소( |
| |
공식 저장소( |
| |
Docker Desktop 애플리케이션 | 통합 GUI 제공, 자동 업데이트, 내장 Kubernetes 지원 | |
Windows 10/11 Pro/Enterprise | Docker Desktop 애플리케이션 | |
Windows 10/11 Home | Docker Desktop 애플리케이션 | WSL 2 백엔드 필수 설치 및 사용 |
리눅스 배포판에서는 패키지 관리자를 사용해 공식 저장소에서 Docker Engine을 설치하는 것이 일반적이다. 설치 후 docker 그룹에 사용자를 추가하여 sudo 권한 없이 명령어를 실행할 수 있도록 구성한다.
macOS와 Windows에서는 Docker Desktop을 공식 웹사이트에서 다운로드하여 설치한다. 이 애플리케이션은 Docker 데몬, CLI, Docker Compose, 가상화 환경을 번들로 제공하여 설치와 관리를 단순화한다. 특히 Windows 환경에서는 WSL 2 통합 모드를 사용하면 리눅스와 유사한 성능과 호환성을 얻을 수 있다. 모든 설치 후에는 터미널 또는 명령 프롬프트에서 docker --version 명령어로 설치를 확인한다.
Docker 데몬은 호스트 시스템에서 Docker 엔진의 핵심 구성 요소로, 컨테이너의 생성, 실행, 관리와 이미지 관리를 담당하는 백그라운드 서비스이다. 이 데몬은 dockerd 프로세스로 실행되며, Docker API 요청을 수신하고 처리한다. 사용자는 직접 데몬과 상호작용하기보다는 Docker CLI를 통해 명령을 전달하는 방식을 주로 사용한다.
Docker CLI는 사용자가 터미널에서 입력하는 docker 명령어 도구이다. docker run, docker build 등의 명령은 CLI가 수신하여 Docker 데몬에 전달하고, 그 결과를 다시 사용자에게 표시한다. 기본적으로 CLI는 유닉스 도메인 소켓(/var/run/docker.sock)을 통해 로컬 데몬과 통신하도록 구성되어 있다. 원격 데몬과 통신하려면 DOCKER_HOST 환경 변수를 설정하여 TCP 소켓 주소를 지정할 수 있다.
CLI의 구성 파일은 사용자별로 ~/.docker/config.json 경로에 위치하며, 여기서 기본 레지스트리, 인증 정보, 출력 형식 등을 관리할 수 있다. 예를 들어, 명령어 출력을 기본적으로 JSON 형식으로 설정하거나, 사용할 컨테이너 레지스트리를 지정하는 것이 가능하다. 데몬의 구성은 운영체제별로 다른 경로의 daemon.json 파일에서 조정한다. 이 파일을 통해 다음과 같은 설정을 변경할 수 있다.
설정 카테고리 | 주요 구성 옵션 예시 |
|---|---|
레지스트리 관련 |
|
네트워크 관련 |
|
로깅 관련 |
|
런타임 관련 |
|
설정 변경 후에는 대부분의 경우 데몬을 재시작해야 적용된다. 시스템의 서비스 관리자(예: systemctl for systemd)를 사용하여 데몬을 제어한다. 데몬과 CLI의 구성은 보안과 성능에 직접적인 영향을 미치므로, 특히 프로덕션 환경에서는 신중하게 검토하고 적용해야 한다.
Dockerfile은 Docker 이미지를 생성하기 위한 텍스트 기반의 빌드 지시어 집합이다. 이 파일에는 베이스 이미지 지정, 파일 복사, 패키지 설치, 환경 변수 설정, 실행할 명령어 정의 등이 포함된다. 각 지시어는 새로운 이미지 레이어를 생성하며, 이러한 레이어의 캐싱 메커니즘은 동일한 빌드 과정을 반복할 때 효율성을 높인다. 일반적인 지시어로는 FROM, RUN, COPY, ADD, CMD, ENTRYPOINT, ENV, WORKDIR 등이 있다.
이미지는 docker build 명령어를 사용하여 Dockerfile로부터 빌드된다. 빌드 시 -t 옵션을 통해 저장소명/이미지명:태그 형식으로 태깅할 수 있으며, 태그를 생략하면 기본값으로 latest가 적용된다. 빌드된 이미지는 로컬 Docker 데몬에 저장되며, docker images 명령으로 목록을 확인할 수 있다. 이미지를 Docker 레지스트리(예: Docker Hub, Amazon ECR, Google Container Registry)에 공유하려면 docker push 명령을 사용한다. 반대로 레지스트리에서 이미지를 가져오려면 docker pull 명령을 실행한다.
작업 | 주요 명령어 | 설명 |
|---|---|---|
이미지 빌드 |
| 현재 디렉토리의 Dockerfile로 이미지를 빌드하고 태그를 지정한다. |
이미지 목록 조회 |
| 로컬에 저장된 이미지 목록을 보여준다. |
이미지 레지스트리에 푸시 |
| 지정된 레지스트리에 이미지를 업로드한다. |
이미지 레지스트리에서 풀 |
| 지정된 레지스트리에서 이미지를 다운로드한다. |
이미지 삭제 |
| 로컬에서 지정된 이미지를 삭제한다. |
이미지 최적화는 보안과 효율성 측면에서 중요하다. 불필요한 파일이나 패키지를 포함하지 않도록 하고, .dockerignore 파일을 사용하여 빌드 컨텍스트에서 제외할 파일을 명시하는 것이 좋다. 또한, 명령어를 적절히 결합하여 불필요한 레이어 수를 줄이고, 가능한 한 작은 공식 베이스 이미지(예: alpine)를 사용하는 것이 이미지 크기를 최소화하는 데 도움이 된다. 다단계 빌드(Multi-stage build)를 활용하면 빌드 도구와 같은 최종 이미지에 불필요한 구성 요소를 제외하고, 런타임에 필요한 파일만 포함된 최종 이미지를 생성할 수 있다.
Dockerfile은 Docker 이미지를 생성하기 위한 텍스트 파일이다. 이 파일에는 베이스 이미지 지정, 파일 복사, 패키지 설치, 환경 변수 설정, 실행 명령어 등 이미지를 조립하는 데 필요한 모든 명령어가 순서대로 기록된다. Docker는 이 파일의 지시사항을 읽어 순차적으로 실행하며, 각 단계의 결과를 이미지 레이어로 저장하여 최종 이미지를 완성한다.
Dockerfile의 핵심 명령어는 다음과 같다.
명령어 | 용도 | 예시 |
|---|---|---|
| 베이스 이미지를 지정한다. 모든 Dockerfile은 이 명령어로 시작한다. |
|
| 이미지 내에서 쉘 명령어를 실행한다. 주로 패키지를 설치하거나 파일을 생성할 때 사용한다. |
|
| 호스트 머신의 파일이나 디렉토리를 이미지 내부로 복사한다. |
|
| 이후의 |
|
| 환경 변수를 설정한다. |
|
| 컨테이너가 런타임에 특정 네트워크 포트를 사용할 것임을 선언한다. 실제 포트를 열지는 않는다. |
|
| 컨테이너가 시작될 때 실행할 기본 명령어나 인자를 정의한다. Dockerfile에서 한 번만 사용할 수 있다. |
|
| 컨테이너를 실행 파일처럼 구성할 때 사용하며, |
|
효율적인 Dockerfile 작성에는 몇 가지 모범 사례가 있다. 먼저, .dockerignore 파일을 사용하여 이미지 빌드 컨텍스트에서 불필요한 파일을 제외하면 빌드 속도와 보안이 향상된다. 또한, 명령어를 논리적으로 그룹화하고 캐시를 활용하기 위해 자주 변경되지 않는 명령어를 Dockerfile 상단에, 애플리케이션 소스 코드 복사와 같이 자주 변경되는 작업은 하단에 배치한다. 하나의 RUN 명령어에서 여러 작업을 &&로 연결하고 불필요한 파일을 정리하면 최종 이미지 크기를 줄일 수 있다. 마지막으로, 보안을 위해 루트 사용자가 아닌 특정 사용자로 애플리케이션을 실행하도록 USER 명령어를 사용하는 것이 권장된다.
docker build 명령어를 사용하여 Dockerfile로부터 Docker 이미지를 생성하는 과정을 이미지 빌드라고 한다. 기본적인 빌드 명령어는 docker build -t <이미지명> <경로> 형식이다. 여기서 <경로>는 Dockerfile이 위치한 디렉토리(보통 현재 디렉토리를 의미하는 .)를 지정한다. -t 옵션은 태그를 부여하는 데 사용되며, 빌드 과정에서 캐시 레이어를 활용하여 효율성을 높인다.
태그는 이미지의 특정 버전이나 변종을 식별하는 이름표 역할을 한다. 일반적으로 이름:태그 형식으로 구성되며, 태그를 생략하면 자동으로 latest 태그가 적용된다[3]. 버전 관리와 배포를 위해 의미 있는 태그(예: v1.2, prod, commit-hash)를 사용하는 것이 권장된다. docker tag 명령어를 사용하면 기존 이미지에 새로운 태그를 추가할 수 있다. 예를 들어, docker tag myapp:latest myregistry.com/myapp:v1.0은 로컬의 myapp:latest 이미지를 새로운 이름과 태그로 복제한다.
빌드된 이미지는 Docker 레지스트리에 업로드(푸시)하여 공유하거나 배포할 수 있다. 주요 공개 레지스트리로는 Docker Hub가 있으며, 사설 레지스트리로는 Harbor나 Amazon ECR 등을 사용할 수 있다. 이미지를 푸시하기 전에는 반드시 docker login으로 레지스트리에 인증해야 한다. 푸시는 docker push <이미지명> 명령어로 수행한다. 이미지명은 레지스트리 주소를 포함한 전체 경로(예: myregistry.com/team/myapp:v1.0)여야 한다. 일반적인 워크플로우는 로컬에서 이미지를 빌드하고 태그를 지정한 후, 원격 레지스트리에 푸시하는 순서로 이루어진다.
Docker 이미지는 여러 개의 읽기 전용 레이어로 구성됩니다. 각 레이어는 Dockerfile의 명령어 하나에 해당하며, 파일 시스템의 변경 사항을 나타냅니다. 예를 들어, RUN apt-get update, COPY . /app, RUN pip install과 같은 각 명령은 새로운 레이어를 생성합니다. 이러한 레이어 구조는 캐싱과 이미지 공유를 효율적으로 만듭니다. 빌드 과정에서 Docker는 기존에 빌드된 레이어 캐시를 재사용하여 빌드 시간을 단축합니다. 또한, 여러 이미지가 같은 베이스 레이어를 공유하면 디스크 공간을 절약할 수 있습니다.
이미지 최적화는 크기와 보안, 빌드 효율성을 향상시키는 것을 목표로 합니다. 주요 전략은 다음과 같습니다. 첫째, 가능한 한 작은 베이스 이미지를 선택하는 것입니다. 예를 들어, 전체 Ubuntu 이미지 대신 alpine과 같은 경량 리눅스 배포판 기반 이미지를 사용합니다. 둘째, 불필요한 파일을 레이어에 포함시키지 않도록 주의합니다. .dockerignore 파일을 사용하여 컨텍스트에서 불필요한 파일을 제외시키고, 한 레이어에서 생성된 임시 파일은 같은 레이어에서 삭제해야 합니다. 셋째, 레이어 캐시를 최대한 활용하기 위해 Dockerfile 명령어의 순서를 신중하게 배열합니다. 자주 변경되지 않는 명령어(예: 의존성 설치)를 앞쪽에, 자주 변경되는 명령어(예: 애플리케이션 소스 코드 복사)를 뒤쪽에 배치합니다.
최적화 기법 | 설명 | 예시 |
|---|---|---|
다단계 빌드 사용 | 빌드 도구와 런타임 환경을 분리하여 최종 이미지 크기를 줄입니다. | 첫 번째 |
레이어 통합 | 여러 |
|
의존성 캐싱 활용 | 패키지 매니저의 캐시를 정리하여 이미지 크기를 줄입니다. |
|
적절한 베이스 이미지 | 애플리케이션에 꼭 필요한 구성요소만 포함된 베이스를 선택합니다. |
|
이러한 최적화를 통해 생성된 이미지는 더 빠르게 배포되고, 보안 취약점 노출 영역이 줄어들며, 저장 및 전송 비용이 절감됩니다. 특히 다단계 빌드는 빌드에 필요한 컴파일러와 개발 도구를 최종 이미지에 포함시키지 않도록 함으로써 크기와 보안 측면에서 큰 이점을 제공합니다.
컨테이너는 docker run 명령어로 실행됩니다. 이 명령어는 Docker 이미지로부터 새로운 컨테이너 인스턴스를 생성하고 시작합니다. 주요 실행 옵션으로는 백그라운드 실행을 위한 -d, 포트 매핑을 위한 -p, 볼륨 마운트를 위한 -v, 환경 변수 설정을 위한 -e 등이 있습니다. 컨테이너의 생명주기는 생성(Created), 실행(Running), 일시정지(Paused), 중지(Stopped), 삭제(Deleted) 상태로 관리됩니다. docker ps 명령어로 실행 중인 컨테이너 목록을 확인하고, docker stop, docker start, docker rm 등의 명령어로 상태를 제어합니다.
네트워크 구성은 컨테이너 간 통신과 외부 연결을 위해 중요합니다. Docker는 기본적으로 bridge, host, none 등의 네트워크 드라이버를 제공합니다. 기본 bridge 네트워크는 컨테이너가 내부 IP를 할당받아 서로 통신할 수 있게 하지만, 호스트 머신 외부에서는 직접 접근이 불가능합니다. 외부에서 접근하려면 -p 8080:80과 같이 호스트의 포트와 컨테이너의 포트를 명시적으로 매핑해야 합니다. 데이터의 영속성을 위해 Docker 볼륨이나 바인드 마운트를 사용합니다. 볼륨은 Docker가 관리하는 저장 공간으로, 컨테이너 삭제 후에도 데이터를 보존합니다.
컨테이너 운영 중 로그 수집은 필수적입니다. docker logs 명령어를 통해 컨테이너의 표준 출력(stdout)과 표준 에러(stderr) 로그를 확인할 수 있습니다. --follow 옵션을 추가하면 실시간 로그를 스트리밍합니다. 효율적인 모니터링을 위해 로그 드라이버를 syslog, journald 또는 Fluentd 같은 외부 로깅 시스템으로 변경할 수도 있습니다. 컨테이너의 자원 사용량(CPU, 메모리, 네트워크, 디스크 I/O)은 docker stats 명령어로 실시간 모니터링이 가능합니다.
컨테이너의 생명주기는 docker run 명령어로 시작하여 docker rm 명령어로 완전히 제거될 때까지의 일련의 상태 변화를 의미한다. 주요 상태는 생성됨(Created), 실행 중(Running), 일시 중지됨(Paused), 중지됨(Stopped), 삭제됨(Deleted)으로 구분된다.
주요 Docker CLI 명령어를 통해 이 생명주기를 관리할 수 있다. docker create는 이미지로부터 컨테이너를 생성하지만 실행하지는 않는다. docker start는 중지된 컨테이너를 다시 실행한다. docker run은 생성과 실행을 한 번에 수행하는 가장 일반적인 명령어다. 실행 중인 컨테이너를 중지하려면 docker stop(정상 종료) 또는 docker kill(강제 종료)을 사용한다. docker pause와 docker unpause는 컨테이너의 모든 프로세스를 일시 정지하거나 재개한다. 컨테이너를 완전히 삭제하려면 먼저 중지한 후 docker rm 명령을 실행해야 한다.
명령어 | 주요 기능 | 비고 |
|---|---|---|
| 이미지로부터 새 컨테이너를 생성하고 실행 |
|
| 중지된 컨테이너를 시작 | 컨테이너 ID 또는 이름 지정 |
| 실행 중인 컨테이너를 정상적으로 중지 | SIGTERM 후 SIGKILL 신호 전송 |
| 실행 중인 컨테이너를 즉시 강제 중지 | SIGKILL 신호 직접 전송 |
| 컨테이너 내 모든 프로세스를 일시 정지 | cgroups의 freezer 기능 사용 |
| 중지된 컨테이너를 삭제 |
|
생명주기 관리 시 자원 정리를 위해 중지된 컨테이너를 정기적으로 삭제하는 것이 좋다. docker container prune 명령은 중지된 모든 컨테이너를 한꺼번에 제거한다. 또한 docker run 시 --rm 옵션을 사용하면 컨테이너가 종료될 때 자동으로 삭제되도록 설정할 수 있어 일회성 작업용 컨테이너 운영에 유용하다. 컨테이너의 상태는 docker ps(실행 중인 컨테이너)와 docker ps -a(모든 컨테이너) 명령으로 확인한다.
컨테이너의 네트워크 설정은 격리된 환경 간 또는 외부와의 통신을 가능하게 합니다. Docker는 기본적으로 여러 네트워크 드라이버를 제공하며, docker network 명령어로 관리할 수 있습니다. 기본 bridge 네트워크는 컨테이너가 호스트 내부에서 서로 통신할 수 있게 하지만, 외부에서는 접근이 불가능합니다. 외부 접근이 필요할 경우 컨테이너의 특정 포트를 호스트 포트에 명시적으로 -p 옵션으로 포트 포워딩하여 공개합니다. 사용자 정의 브리지 네트워크를 생성하면 DNS 기반의 자동 서비스 디스커버리를 통해 컨테이너 이름으로 서로를 찾을 수 있습니다. host 네트워크 모드는 컨테이너가 호스트의 네트워크 스택을 직접 사용하게 하며, none 모드는 네트워크를 완전히 비활성화합니다.
볼륨과 바인드 마운트는 컨테이너의 데이터를 지속적으로 관리하는 두 가지 주요 방법입니다. 컨테이너의 파일 시스템은 기본적으로 일시적이므로, 컨테이너가 삭제되면 내부 데이터도 함께 사라집니다. 이를 방지하기 위해 Docker가 완전히 관리하는 볼륨을 생성하여 데이터를 저장합니다. 볼륨은 호스트 파일 시스템의 특정 경로(보통 /var/lib/docker/volumes/)에 저장되며, docker volume 명령어로 생성 및 관리할 수 있습니다. 반면, 바인드 마운트는 호스트 파일 시스템의 임의의 경로나 파일을 컨테이너 내부에 직접 연결합니다. 이는 개발 시 소스 코드를 컨테이너에 실시간으로 반영하거나 호스트의 설정 파일을 사용할 때 유용합니다.
네트워크와 볼륨 설정은 주로 docker run 명령어의 옵션 또는 Docker Compose 파일을 통해 정의합니다. 아래 표는 주요 옵션을 비교한 것입니다.
설정 유형 | Docker CLI 옵션 예시 | 주요 목적 |
|---|---|---|
포트 포워딩 |
| 호스트의 8080 포트를 컨테이너의 80 포트에 연결 |
사용자 정의 네트워크 연결 |
| 컨테이너를 특정 사용자 정의 네트워크에 연결 |
볼륨 마운트 |
| 'my-volume'이라는 볼륨을 컨테이너의 |
바인드 마운트 |
| 호스트의 |
이러한 설정을 통해 컨테이너는 필요한 네트워크 서비스를 제공하면서도 상태 없는(stateless) 애플리케이션의 원칙을 유지하며 데이터를 안정적으로 보관할 수 있습니다.
Docker 컨테이너의 로그는 기본적으로 표준 출력(stdout)과 표준 오류(stderr) 스트림으로 수집된다. docker logs 명령어를 사용하면 실행 중이거나 중지된 컨테이너의 로그 스트림을 확인할 수 있다. 로그 출력을 지속적으로 모니터링하려면 --follow 옵션을, 특정 시간대의 로그를 보려면 --since 또는 --until 옵션을 활용한다. 로그는 JSON 파일 형식으로 호스트 머신에 저장되어 컨테이너 삭제 후에도 유지될 수 있다[4].
보다 체계적인 중앙 집중식 로깅을 위해선 로깅 드라이버를 구성할 수 있다. Docker는 syslog, Journald, Fluentd, 데이터그램 프로토콜과 같은 다양한 로깅 드라이버를 지원한다. 예를 들어, --log-driver 옵션을 사용해 컨테이너 로그를 호스트의 syslog 데몬으로 직접 전송하거나, Fluentd를 통해 Elasticsearch와 Kibana 스택으로 보내 시각화할 수 있다.
로깅 방법 | 명령어 예시 또는 설명 | 주요 용도 |
|---|---|---|
기본 로그 확인 |
| 단일 컨테이너의 로그 빠르게 조회 |
실시간 로그 스트림 |
| 애플리케이션 디버깅 및 실시간 모니터링 |
로깅 드라이버 사용 |
| 호스트 시스템의 중앙 로깅 시스템과 통합 |
로그 드라이버 옵션 |
| 로그 파일의 최대 크기나 개수 제한 설정 |
컨테이너 모니터링에는 docker stats 명령어가 유용하다. 이 명령어는 모든 실행 중인 컨테이너의 실시간 CPU 사용률, 메모리 사용량, 네트워크 입출력, 블록 입출력 등의 성능 메트릭을 보여준다. 더 포괄적인 모니터링을 위해서는 Prometheus와 같은 전문 모니터링 도구를 도입한다. Docker 데몬은 Prometheus 형식의 메트릭을 노출할 수 있으며, cAdvisor 같은 도구는 컨테이너별 리소스 사용량과 성능 데이터를 수집하여 제공한다. 이러한 데이터는 그라파나 대시보드에 연결해 시각화하는 것이 일반적이다.
Docker Compose는 다중 컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구이다. 단일 docker run 명령어로는 관리하기 복잡한 여러 컨테이너(예: 웹 서버, 데이터베이스, 캐시 서버)의 설정, 네트워크, 볼륨을 하나의 YAML 파일(docker-compose.yml)로 선언적으로 관리할 수 있게 한다. 이를 통해 개발 환경을 쉽게 구성하고 재현할 수 있으며, 테스트와 배포 과정을 단순화한다.
docker-compose.yml 파일의 주요 구조는 version, services, networks, volumes 섹션으로 구성된다. services 섹션 아래에 각 컨테이너 서비스를 정의하며, 각 서비스는 사용할 Docker 이미지, 포트 매핑, 환경 변수, 볼륨 마운트, 컨테이너 간 네트워크 연결 등을 설정한다. 예를 들어, 웹 애플리케이션과 데이터베이스 서비스를 다음과 같이 정의할 수 있다.
```yaml
version: '3.8'
services:
web:
build: .
ports:
"8000:8000"
depends_on:
db
environment:
DATABASE_URL=postgresql://user:pass@db/mydb
db:
image: postgres:15
volumes:
postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD=pass
volumes:
postgres_data:
```
의존성 관리는 depends_on 지시어를 통해 이루어진다. 위 예시에서 web 서비스는 db 서비스가 먼저 시작된 후에 실행된다. 그러나 이는 컨테이너 실행 순서만 보장할 뿐, 데이터베이스가 실제로 요청을 받을 준비가 되었는지(헬스 체크)는 보장하지 않는다. 더 정교한 준비 상태 확인을 위해서는 스크립트나 healthcheck 설정을 추가로 활용해야 한다.
Docker Compose 명령어를 사용하면 정의된 전체 애플리케이션 스택을 쉽게 제어할 수 있다. docker-compose up 명령은 모든 서비스를 백그라운드에서 시작하며, docker-compose down 명령은 실행 중인 모든 컨테이너, 네트워크, 볼륨을 정리한다. 또한 docker-compose logs로 통합 로그를 확인하거나 docker-compose exec로 특정 실행 중인 컨테이너에 명령을 실행할 수 있다. 이로 인해 로컬 개발, 통합 테스트, 데모 환경 구성 등에서 매우 효율적인 워크플로우를 제공한다.
docker-compose.yml 파일은 YAML 형식으로 작성되며, 프로젝트에 필요한 서비스, 네트워크, 볼륨 등을 선언적으로 정의합니다. 파일의 최상위에는 주로 version, services, networks, volumes 등의 키가 사용됩니다. version 키는 사용할 Docker Compose 파일 형식의 버전을 지정하며, 버전에 따라 사용 가능한 구성 옵션이 달라집니다. services 섹션은 애플리케이션을 구성하는 각 컨테이너(서비스)를 정의하는 핵심 부분입니다.
각 서비스는 image나 build와 같은 키로 정의됩니다. image는 사용할 Docker 이미지를 지정하고, build는 빌드 컨텍스트 경로와 Dockerfile 경로를 지정하여 이미지를 직접 빌드하도록 합니다. 서비스의 동작을 제어하는 다른 주요 옵션들은 다음과 같습니다.
옵션 | 설명 |
|---|---|
| 생성될 컨테이너의 이름을 지정합니다. |
| 호스트 포트와 컨테이너 포트를 |
| 호스트의 디렉토리나 도커 볼륨을 컨테이너에 마운트합니다. |
| 컨테이너 내에 설정할 환경 변수를 키-값 쌍으로 정의합니다. |
| 환경 변수를 정의한 파일의 경로를 지정합니다. |
| 다른 서비스에 대한 시작 의존성을 선언합니다. |
| 서비스가 연결될 사용자 정의 네트워크를 지정합니다. |
| 컨테이너가 시작될 때 실행할 기본 명령어를 재정의합니다. |
networks와 volumes 섹션은 프로젝트 전반에서 사용될 사용자 정의 네트워크와 영구 저장소를 선언합니다. 이를 통해 서비스들은 동일한 사용자 정의 네트워크에 연결되어 서로 통신할 수 있으며, 데이터를 지속적으로 보관할 수 있습니다. docker-compose up 명령어는 이 파일을 읽어 모든 서비스, 네트워크, 볼륨을 한 번에 생성하고 시작합니다.
docker-compose.yml 파일에서 각 애플리케이션 구성 요소는 services 하위에 독립적인 서비스로 정의됩니다. 각 서비스는 실행할 Docker 이미지, 노출할 포트, 마운트할 볼륨, 환경 변수 등을 설정합니다. 예를 들어, 웹 애플리케이션, 데이터베이스, 캐시 서버를 각각 하나의 서비스로 정의하여 단일 파일에서 관리할 수 있습니다.
서비스 간 의존성은 depends_on, links, networks 등의 지시어를 통해 관리됩니다. depends_on은 서비스의 시작 순서를 제어하여, 예를 들어 웹 애플리케이션 서비스가 시작되기 전에 데이터베이스 서비스가 먼저 실행되도록 보장합니다[5]. 네트워크는 기본적으로 또는 사용자 정의 네트워크를 통해 서비스들이 서로 통신할 수 있도록 합니다.
의존성 관리와 관련된 주요 설정은 다음과 같습니다.
지시어 | 설명 | 예시 |
|---|---|---|
| 서비스 시작 순서 의존성 정의 |
|
| 다른 서비스에 대한 컨테이너 내 네트워크 별칭 생성(레거시) |
|
| 서비스가 연결될 네트워크 지정 |
|
이러한 구성을 통해, 복잡한 다중 컨테이너 애플리케이션의 구조와 상호작용을 선언적으로 정의하고, docker-compose up 하나의 명령으로 전체 스택을 일관되게 시작할 수 있습니다.
보안 취약점 스캔은 Docker 이미지를 구성하는 소프트웨어 패키지와 의존성에 존재하는 알려진 취약점을 식별하는 과정이다. 일반적으로 정적 분석 도구를 사용하여 이미지를 스캔하며, CVE 데이터베이스와 취약점 정보를 비교한다. 이를 통해 이미지에 포함된 운영체제 패키지, 언어별 라이브러리, 애플리케이션 코드의 보안 문제를 조기에 발견할 수 있다. 스캔은 CI/CD 파이프라인에 통합하여 새로운 이미지가 빌드될 때마다 자동으로 실행하는 것이 모범 사례이다. 발견된 취약점은 심각도에 따라 분류되고, 패치 가능한 경우 기본 이미지나 의존성 버전을 업데이트하여 해결한다.
최소 권한 원칙 적용은 컨테이너가 필요한 최소한의 권한만으로 실행되도록 보장하는 것이다. 주요 실천 방법은 다음과 같다.
실천 방법 | 설명 |
|---|---|
루트 권한 없이 실행 | Dockerfile의 |
필요한 능력만 부여 |
|
읽기 전용 루트 파일 시스템 사용 | 컨테이너 실행 시 |
이미지 빌드 시 실행 파일의 특수 권한 비트를 찾아 제거하여 권한 상승 위험을 줄인다. |
기본 이미지는 가능한 한 경량이고 공식적으로 유지 관리되는 것을 선택한다. 예를 들어, 전체 운영체제 대신 alpine 또는 distroless와 같은 최소화된 베이스 이미지를 사용하면 공격 표면을 크게 줄일 수 있다. 또한, Dockerfile에는 애플리케이션 실행에 꼭 필요한 패키지만 설치하고, 빌드 의존성은 최종 이미지에 포함되지 않도록 멀티-스테이지 빌드를 활용한다. 이미지에 비밀 정보를 하드코딩하지 않고, Docker 시크릿이나 환경 변수를 통해 런타임에 주입하는 것도 필수적이다.
Docker 이미지의 보안 취약점 스캔은 이미지 내 포함된 소프트웨어 패키지, 라이브러리, 운영 체제 구성 요소에서 알려진 취약점을 식별하고 평가하는 과정이다. 이는 컨테이너 보안의 핵심적인 실천 사례로, 악의적인 공격이나 데이터 유출을 방지하기 위해 필수적이다. 스캔 도구는 일반적으로 CVE(Common Vulnerabilities and Exposures) 데이터베이스와 같은 공개 취약점 데이터베이스를 참조하여 이미지를 분석한다.
주요 스캔 도구로는 Trivy, Clair, Anchore Engine, Snyk, Docker Desktop에 내장된 Docker Scout 등이 널리 사용된다. 이러한 도구들은 CI/CD 파이프라인에 통합되어 개발 단계나 레지스트리에 푸시되기 전에 자동으로 스캔을 수행할 수 있다. 스캔 결과는 취약점의 심각도(위험, 높음, 중간, 낮음), 영향을 받는 패키지 이름 및 버전, 해결 방안(예: 특정 버전으로 업데이트)을 상세히 보고한다.
효과적인 취약점 관리를 위해 다음 절차를 따르는 것이 좋다.
1. 기본 이미지 선택: 가능한 한 최소한의 공격 표면을 가진 공식적이고 잘 관리되는 기본 이미지(예: alpine, distroless)를 선택한다.
2. 정기적 스캔: 새로운 취약점이 지속적으로 발견되므로, 이미지 빌드 시와 주기적으로 기존 이미지에 대해 스캔을 반복 실행한다.
3. 심각도 기반 대응: 모든 취약점을 즉시 수정하는 것은 현실적이지 않을 수 있다. 위험(Risk) 또는 높음(High) 등급의 취약점을 우선적으로 해결하는 위험 기반 접근법을 적용한다.
4. 자동화된 정책 적용: 스캔 도구와 레지스트리를 연동하여 특정 심각도 이상의 취약점이 발견된 이미지의 배포를 자동으로 차단하는 정책을 설정할 수 있다.
스캔은 이미지의 모든 레이어를 분석하므로, 애플리케이션 종속성뿐만 아니라 기본 운영 체제의 패키지 관리자(apt, apk, yum 등)를 통해 설치된 모든 구성 요소도 검사 대상이 된다. 따라서 Dockerfile에서 불필요한 패키지 설치를 최소화하고, 패키지 캐시를 정리하는 등의 이미지 최적화 작업도 간접적으로 보안성을 향상시킨다[6].
최소 권한 원칙은 Docker 보안의 핵심 원칙 중 하나로, 컨테이너와 그 내부의 애플리케이션이 정상적으로 작동하는 데 필요한 최소한의 권한만을 부여하는 것을 의미한다. 이는 잠재적인 공격 표면을 줄이고, 보안 위협 발생 시 피해를 국한시키는 데 목적이 있다.
이 원칙을 적용하는 주요 방법은 다음과 같다.
적용 영역 | 주요 방법 | 설명 |
|---|---|---|
사용자 권한 | 비루트 사용자 실행 | Dockerfile에서 |
파일 시스템 | 읽기 전용 마운트 | 컨테이너의 루트 파일 시스템을 읽기 전용( |
커널 기능 | 불필요한 Capabilities 제거 |
|
네트워크 | 사용하지 않는 포트 노출 금지 |
|
구체적으로, Dockerfile 작성 시 기본 이미지에서 제공하는 비루트 사용자를 활용하거나 새 사용자를 생성하여 애플리케이션을 실행해야 한다. 또한 컨테이너 실행 시 docker run --read-only --user 1000:1000 --cap-drop=ALL과 같은 옵션 조합을 사용하면 강력한 최소 권한 환경을 구성할 수 있다. 이렇게 하면 컨테이너가 침해당하더라도 호스트 시스템이나 다른 컨테이너로의 영향이 제한된다.
이 원칙은 이미지 보안 및 모범 사례의 일환으로, CI/CD 파이프라인과의 통합 과정에서 자동화된 보안 검사 도구를 통해 정기적으로 점검되어야 한다. 궁극적으로는 애플리케이션의 요구 사항을 정확히 분석하여 필요한 리소스와 권한만을 명시적으로 정의하는 것이 핵심이다.
CI/CD 파이프라인과 Docker를 통합하면 애플리케이션 빌드, 테스트, 배포 과정을 자동화하고 표준화할 수 있다. 이를 통해 개발부터 운영까지의 흐름을 가속화하고 환경 불일치 문제를 해결한다. 파이프라인은 일반적으로 소스 코드 관리 시스템의 변경 사항을 트리거로 시작되어, Dockerfile을 사용한 이미지 빌드, 보안 및 기능 테스트, 그리고 레지스트리에 이미지를 푸시하는 단계를 거친다.
자동화된 이미지 빌드는 파이프라인의 핵심 단계이다. Jenkins, GitLab CI/CD, GitHub Actions, CircleCI와 같은 도구를 사용하여 코드 커밋이나 병합 시 자동으로 새로운 Docker 이미지를 생성하도록 구성한다. 빌드 과정에서는 특정 버전을 식별하기 위해 Git 커밋 해시나 빌드 번호를 이미지 태그로 활용하는 것이 일반적이다. 이는 어떤 소스 코드에서 이미지가 생성되었는지 추적성을 보장한다.
CI/CD 단계 | 주요 Docker 작업 | 목적 |
|---|---|---|
빌드 (Build) |
| 소스 코드로부터 애플리케이션 이미지 생성 및 기본 검증 |
테스트 (Test) | 생성된 이미지로 컨테이너 실행, 통합/보안 테스트 수행 | 기능적 정확성과 보안 취약점 점검 |
푸시 (Push) |
| 테스트된 이미지를 배포를 위한 저장소에 저장 |
배포 (Deploy) | 레지스트리에서 새 이미지를 풀(Pull)하여 환경에 배포 | 스테이징 또는 프로덕션 환경에 애플리케이션 릴리스 |
레지스트리와의 연동은 파이프라인의 후반부를 책임진다. Docker Hub, Amazon ECR, Google Container Registry, Azure Container Registry 등의 레지스트리에 빌드 및 테스트가 완료된 이미지를 푸시한다. 이후 배포 단계(예: Kubernetes 클러스터나 클라우드 서비스)에서는 이 레지스트리로부터 최신 이미지를 풀받아 서비스를 업데이트한다. 민감한 이미지는 프라이빗 레지스트리에 저장하고 접근 제어를 설정하여 보안을 강화한다. 이러한 통합은 재현 가능하고 신뢰할 수 있는 배포 패키지를 지속적으로 제공하는 데 기여한다.
CI/CD 파이프라인에서 Docker 이미지 빌드를 자동화하는 것은 애플리케이션의 지속적인 통합과 배포를 효율적으로 만드는 핵심 단계이다. 일반적으로 Jenkins, GitLab CI/CD, GitHub Actions, CircleCI와 같은 도구를 사용하여 소스 코드 저장소에 변경 사항이 푸시되거나 특정 이벤트가 발생할 때마다 자동으로 Dockerfile을 기반으로 새로운 이미지를 빌드하도록 구성한다. 이 과정은 빌드 일관성을 보장하고 수동 개입을 최소화하여 개발부터 프로덕션 환경까지의 흐름을 가속화한다.
자동화된 빌드 파이프라인은 일반적으로 몇 가지 주요 단계로 구성된다. 먼저, 파이프라인이 코드 저장소의 특정 브랜치(예: main 또는 develop)에 변경이 발생하면 트리거된다. 이후 파이프라인은 최신 소스 코드를 체크아웃하고, 미리 정의된 Dockerfile을 사용하여 docker build 명령을 실행한다. 빌드 시점에 애플리케이션 버전, Git 커밋 해시, 빌드 번호 등을 Docker 이미지 태그로 자동 생성하여 이미지에 부여하는 것이 일반적이다[7]. 이렇게 생성된 이미지는 자동으로 보안 취약점 스캔을 거친 후 Docker 레지스트리(예: Docker Hub, Amazon ECR, Google Container Registry, 사설 레지스트리)에 푸시된다.
효율적인 자동화를 위해 다음과 같은 모범 사례를 적용할 수 있다.
관심사 | 모범 사례 | 목적 |
|---|---|---|
태깅 전략 | 의미 있는 버전 태그( | 특정 빌드를 정확히 추적하고 롤백 가능 |
캐시 활용 | 파이프라인 실행 환경에서 Docker 빌드 캐시를 유지하거나 | 빌드 시간 단축 및 네트워크 대역폭 절약 |
다단계 빌드 |
| 보안 강화 및 배포 속도 향상 |
레지스트리 정리 | 파이프라인에 오래된 또는 불필요한 이미지 레이어를 정리하는 단계 포함 | 레지스트리 저장 공간 관리 및 비용 절감 |
이러한 자동화는 개발 팀이 새로운 기능을 빠르게 제공하고, 표준화된 방식으로 애플리케이션을 패키징하며, 개발, 스테이징, 프로덕션 환경 간의 격차를 줄이는 데 기여한다. 결과적으로 소프트웨어 제공의 신뢰성과 속도가 크게 향상된다.
CI/CD 파이프라인에서 자동으로 빌드된 Docker 이미지를 저장하고 배포하기 위해서는 레지스트리와의 연동이 필수적이다. 일반적으로 파이프라인은 Docker Hub, Amazon ECR, Google Container Registry, Azure Container Registry 또는 자체 호스팅 레지스트리에 이미지를 푸시한다. 연동 과정은 주로 레지스트리에 인증한 후, 빌드된 이미지에 적절한 태그를 지정하고 docker push 명령을 실행하는 방식으로 이루어진다.
파이프라인 도구(Jenkins, GitLab CI, GitHub Actions 등)에서는 보안 자격 증명을 안전하게 관리해야 한다. 이를 위해 시크릿 관리 도구를 사용하거나 해당 도구의 내장 시크릿 기능을 활용하여 레지스트리의 사용자 이름과 비밀번호 또는 액세스 토큰을 저장한다. 인증 후, 파이프라인 스크립트는 일반적으로 다음과 같은 단계를 실행한다.
1. docker build 명령으로 이미지를 빌드한다.
2. docker tag 명령을 사용해 이미지를 레지스트리주소/이미지명:태그 형식으로 태깅한다. 태그는 Git 커밋 해시, 빌드 번호, 또는 버전 태그를 사용하는 것이 일반적이다.
3. docker push 명령으로 태깅된 이미지를 레지스트리에 업로드한다.
효율적인 연동을 위한 모범 사례도 존재한다. 먼저, 파이프라인이 항상 최신 베이스 이미지를 사용하도록 하여 보안 패치를 자동으로 적용해야 한다. 또한, 이미지 레이어 캐싱을 최대한 활용하기 위해 Dockerfile의 레이어 순서를 전략적으로 구성한다. 마지막으로, 이미지 푸시 후 다음 단계(예: Kubernetes 배포 또는 스테이징 환경 테스트)를 자동으로 트리거하도록 파이프라인을 설계한다. 이를 통해 개발부터 배포까지의 흐름이 완전히 자동화된다.
Kubernetes는 컨테이너 오케스트레이션의 사실상 표준으로, Docker 컨테이너의 배포, 확장, 관리를 자동화하는 데 널리 사용된다. Docker는 컨테이너를 생성하고 실행하는 데 특화된 반면, Kubernetes는 수백, 수천 개의 컨테이너를 클러스터 단위로 관리하는 플랫폼이다. 일반적인 통합 방식은 Docker로 애플리케이션 이미지를 빌드한 후, 이를 컨테이너 레지스트리에 푸시하고, Kubernetes의 매니페스트 파일에서 해당 이미지를 참조하여 파드를 생성하는 것이다. 최근 Docker Desktop에는 단일 노드 Kubernetes 클러스터를 내장하여 로컬 개발 및 테스트 환경을 제공하기도 한다.
다중 아키텍처 이미지는 x86-64, ARM 등 서로 다른 CPU 아키텍처에서 동일한 애플리케이션을 실행할 수 있도록 한다. 이를 구현하는 핵심 기술은 docker buildx 도구와 매니페스트 리스트[8]이다. 개발자는 하나의 Dockerfile 또는 아키텍처별 Dockerfile을 사용하여 buildx로 여러 플랫폼을 대상으로 동시에 빌드할 수 있다. 생성된 각 아키텍처별 이미지는 하나의 통합된 태그(예: myapp:latest)로 푸시되며, 도커 엔진은 호스트 시스템의 아키텍처에 맞는 올바른 이미지 레이어를 자동으로 풀(pull)하여 실행한다.
주제 | 주요 내용 | 관련 도구/기술 |
|---|---|---|
Kubernetes 연동 | 대규모 컨테이너 오케스트레이션, 서비스 디스커버리, 로드 밸런싱, 자동 복구 |
|
다중 아키텍처 이미지 | 다양한 CPU 플랫폼(ARM, x86 등) 호환성 보장, 단일 태그 관리 |
|
이러한 고급 주제의 발전은 클라우드 네이티브 애플리케이션과 엣지 컴퓨팅 환경에서 Docker 생태계의 적응력을 보여준다. 표준화된 컨테이너 형식은 이기종 인프라에서의 이식성을 보장하며, 지속적인 통합 및 배포(CI/CD) 워크플로우의 핵심 요소로 자리 잡았다.
Docker는 애플리케이션을 컨테이너로 패키징하고 실행하는 데 초점을 맞춘 반면, Kubernetes는 이러한 컨테이너화된 애플리케이션의 배포, 스케일링, 운영을 자동화하는 컨테이너 오케스트레이션 플랫폼이다. 따라서 Docker로 생성된 컨테이너 이미지는 Kubernetes 클러스터에서 실행되는 워크로드의 기본 단위가 된다. Kubernetes는 Docker 컨테이너 런타임을 지원하는 여러 컨테이너 런타임 인터페이스 중 하나로 사용할 수 있으며, Docker 데스크톱 환경에는 단일 노드 Kubernetes 클러스터를 내장하여 로컬 개발과 테스트를 용이하게 한다.
Kubernetes는 Docker 이미지를 활용하기 위해 여러 핵심 객체를 정의한다. 가장 기본적인 단위는 Pod로, 하나 이상의 컨테이너를 포함하는 배포 단위이다. Pod의 명세서인 매니페스트 파일에서는 spec.containers[].image 필드를 사용하여 Docker 레지스트리에서 가져올 이미지의 이름과 태그를 명시한다. 예를 들어, nginx:1.21-alpine이나 myregistry.com/app:v1.2와 같은 형식으로 이미지를 지정한다. 이미지를 효율적으로 관리하고 롤링 업데이트를 수행하기 위해 Deployment 객체를 주로 사용하며, 서비스와 인그레스 객체를 통해 네트워크 트래픽을 Pod에 안정적으로 전달한다.
실제 운영 환경에서의 통합은 다음과 같은 워크플로우를 따른다.
1. 개발자는 Dockerfile을 작성하고 docker build 명령어로 이미지를 생성한다.
2. 생성된 이미지는 Docker Hub, Amazon ECR, Google Container Registry, Azure Container Registry 또는 사설 하버 레지스트리 등에 docker push로 푸시된다.
3. Kubernetes의 매니페스트 파일(YAML)에서 해당 이미지 주소를 참조한다.
4. kubectl apply 명령어로 매니페스트를 클러스터에 적용하면, Kubernetes는 지정된 레지스트리에서 이미지를 풀(Pull)하여 노드에 Pod를 생성하고 실행한다.
이를 위한 주요 명령어와 설정 예시는 다음과 같다.
작업 | Docker 명령어 | Kubernetes 명령어/설정 |
|---|---|---|
이미지 빌드 |
| - |
레지스트리 푸시 |
| - |
컨테이너 실행 |
| 매니페스트의 |
실행 중인 인스턴스 확인 |
|
|
최근에는 Kubernetes가 Docker 외에도 containerd나 CRI-O와 같은 표준 CRI 호환 런타임을 직접 사용하는 추세이다. 이는 더 가벼운 아키텍처와 보안을 강화하기 위함이다. 그러나 Docker로 빌드된 이미지는 OCI 표준 형식을 따르기 때문에, 이러한 모든 CRI 호환 런타임에서 문제없이 실행될 수 있다. 즉, Docker는 여전히 이미지 빌드와 개발의 핵심 도구로 자리 잡은 반면, Kubernetes는 프로덕션 환경에서 수많은 컨테이너의 생명주기를 관리하는 오케스트레이터 역할을 분명히 구분하여 상호 보완적으로 동작한다.
다중 아키텍처 이미지는 서로 다른 CPU 아키텍처(예: x86_64, ARM64, ppc64le, s390x)에서 동일한 이미지 태그로 Docker 이미지를 실행할 수 있게 해주는 기능이다. 이는 개발자가 특정 하드웨어 플랫폼을 고려하지 않고도 애플리케이션을 배포할 수 있도록 지원한다. 예를 들어, 라즈베리 파이(ARM)와 AWS EC2(x86_64) 인스턴스 모두에서 myapp:latest라는 같은 명령어로 컨테이너를 실행할 수 있게 한다.
이 기능의 핵심은 매니페스트 리스트(Manifest List) 또는 다중 아키텍처 매니페스트(Multi-arch manifest)라는 개념이다. 이는 단일 이미지 태그 아래에 여러 아키텍처별 이미지 매니페스트를 그룹화하는 인덱스 역할을 한다. 사용자가 docker pull 또는 docker run 명령을 실행하면 Docker 데몬은 호스트 머신의 아키텍처를 자동으로 감지하고, 매니페스트 리스트에서 해당 플랫폼에 맞는 실제 이미지를 가져온다.
다중 아키텍처 이미지를 생성하는 일반적인 방법은 docker buildx 도구를 사용하는 것이다. buildx는 Moby BuildKit 빌드 엔진을 기반으로 하며, 한 번의 빌드 명령으로 여러 플랫폼을 대상으로 이미지를 빌드하고, 이를 하나의 매니페스트 리스트로 묶어 레지스트리에 푸시할 수 있다. 주요 빌드 및 푸시 절차는 다음 표와 같다.
단계 | 명령어 예시 (CLI) | 설명 |
|---|---|---|
빌더 인스턴스 생성 |
| 다중 플랫폼 빌드를 지원하는 빌더 인스턴스를 생성하고 사용한다. |
다중 아키텍처 빌드 및 푸시 |
| 현재 디렉토리의 Dockerfile을 사용해 x86_64와 ARM64 이미지를 동시에 빌드하고, 결과를 매니페스트 리스트와 함께 지정된 태그로 레지스트리에 푸시한다. |
이 접근 방식은 CI/CD 파이프라인에 통합되어, 코드 변경 시 여러 플랫폼용 이미지를 자동으로 빌드하고 배포하는 데 유용하다. 또한, Docker Hub나 Amazon ECR, Google Container Registry 같은 현대적인 컨테이너 레지스트리는 매니페스트 리스트를 지원하여 이 기능을 원활하게 사용할 수 있게 한다. 이를 통해 개발 및 운영 팀은 애플리케이션의 배포 범위를 클라우드, 엣지 컴퓨팅, IoT 디바이스에 이르기까지 효율적으로 확장할 수 있다.
Docker는 개발과 배포의 방식을 혁신했지만, 그 여정에는 기술적 성과 외에도 흥미로운 일화와 문화적 영향이 존재한다. "도커(Docker)"라는 이름은 독일의 물류 회사인 Hapag-Lloyd가 사용하는 표준화된 화물 컨테이너인 "도커 컨테이너"에서 유래했다. 이는 소프트웨어를 표준화된 단위로 포장하여 어디서나 실행할 수 있게 한다는 철학을 반영한다.
초기 Docker 프로젝트의 코드명은 "dotCloud"였다. 이는 Docker Inc.의 전신인 플랫폼 서비스 회사 이름이었다. 2013년 PyCon에서 처음 공개된 이후, 그 단순함과 강력함으로 인해 폭발적인 인기를 얻으며 컨테이너 생태계의 사실상 표준이 되었다. 이 성공은 때로 "컨테이너 전쟁"이라 불리는 경쟁을 촉발시켰다.
Docker의 로고인 고래는 컨테이너를 등에 실고 있는 모습으로, 대규모 물류(여기서는 애플리케이션)를 효율적으로 운반한다는 개념을 시각화한 것이다. 커뮤니티 내에서는 컨테이너의 경량성과 격리성을 강조하는 유머로, "고래 위에서 실행되는 리눅스"라는 표현이 종종 사용되기도 했다.
연도 | 주요 사건 |
|---|---|
2013 | Solomon Hykes가 PyCon에서 Docker를 처음 공개함 |
2014 | Docker 1.0 정식 버전 출시 |
2017 | Docker 컨테이너 런타임이 Kubernetes에서 기본 옵션에서 제외됨[9] |
2019 | Docker Inc.가 엔터프라이즈 비즈니스를 Mirantis에 매각 |
Docker의 광범위한 채택은 "DevOps" 문화의 확산에 큰 기여를 했다. "내 환경에서는 되는데"라는 고전적인 문제를 크게 줄여, 개발자와 운영팀 간의 협업 방식을 재정의했다. 또한, 수많은 공식 및 커뮤니티 이미지가 생성되면서, 소프트웨어 설치와 실행의 접근성을 근본적으로 변화시켰다.