월. 8월 11th, 2025

Docker Compose 완벽 가이드: 기초부터 프로덕션 애플리케이션까지 Part I: Docker Compose의 기본 원칙 Chapter 1: 다중 컨테이너 오케스트레이션 입문 1.1 Docker Compose 정의: Dockerfile을 넘어서 Docker Compose는 현대적인 소프트웨어 개발에서 핵심적인 도구로, 여러 컨테이너로 구성된 애플리케이션을 정의하고 실행하기 위한 기술입니다. 이를 이해하기 위해서는 먼저 Dockerfile과의 근본적인 차이점을 인식해야 합니다. Dockerfile이 단일 Docker 이미지를 생성하기 위한 설계도라면, docker-compose.yml (또는 compose.yaml) 파일은 웹 서버, 백엔드 API, 데이터베이스 등 여러 서비스로 구성된 전체 애플리케이션 스택을 하나의 응집된 단위로 정의하는 종합 설계도입니다. 과거에는 웹 애플리케이션을 구동하기 위해 데이터베이스 컨테이너를 실행하고, 웹 서버 컨테이너를 별도로 실행하는 등 여러 개의 docker run 명령어를 순차적으로, 그리고 복잡한 옵션과 함께 입력해야 했습니다. 이 방식은 명령어의 길이가 길어지고, 서비스 간의 의존성 및 네트워크 설정을 수동으로 관리해야 하므로 오류가 발생하기 쉽고 비효율적이었습니다. Docker Compose는 이러한 문제점을 해결하기 위해 등장했습니다. YAML 형식의 선언적(declarative) 구성 파일을 사용하여 전체 애플리케이션의 원하는 상태를 기술하면, Compose가 이 상태를 달성하기 위해 필요한 모든 작업을 자동으로 처리합니다. 이는 복잡하고 오류 발생 가능성이 높은 명령형(imperative) 명령어 나열 방식에서 벗어나, 인프라를 코드로 관리하는(Infrastructure as Code, IaC) 패러다임으로의 전환을 의미합니다. 1.2 핵심 가치 제안: 단순성, 일관성, 이식성 Docker Compose가 제공하는 핵심 가치는 세 가지로 요약할 수 있습니다: 단순성, 일관성, 그리고 이식성입니다. 단순성과 효율성: 가장 큰 장점은 복잡한 다중 컨테이너 애플리케이션 관리를 극도로 단순화한다는 점입니다. 개발자는 docker compose up이라는 단일 명령만으로 전체 애플리케이션 스택의 이미지를 빌드하고, 네트워크와 볼륨을 생성하며, 모든 컨테이너를 시작할 수 있습니다. 반대로 docker compose down 명령 하나로 모든 관련 리소스를 깔끔하게 정리할 수 있습니다. 이는 개별 컨테이너를 관리할 때 발생하는 “명령어 피로”를 획기적으로 줄여줍니다. 일관성: compose.yaml 파일은 개발자의 로컬 머신부터 테스트, 스테이징, 프로덕션 서버에 이르기까지 모든 환경에서 애플리케이션이 동일하게 동작하도록 보장합니다. 모든 구성 요소와 그들 간의 관계가 파일 하나에 명시되어 있기 때문에, “제 컴퓨터에서는 됐는데…”와 같은 고질적인 문제를 원천적으로 방지하고, 개발 및 배포 환경 간의 일관성을 유지할 수 있습니다. 이식성과 협업: compose.yaml 파일은 애플리케이션 아키텍처에 대한 살아있는 문서(self-documenting) 역할을 합니다. 이 파일은 Git과 같은 버전 관리 시스템으로 관리할 수 있어 팀원 간에 쉽게 공유할 수 있으며, 모든 팀원이 동일한 구성으로 개발 환경을 구축하도록 보장합니다. 이는 새로운 팀원이 프로젝트에 합류했을 때 온보딩 과정을 크게 단축시키는 효과를 가져옵니다. 1.3 아키텍처 개요: Compose와 Docker 엔진의 상호작용 Docker Compose의 작동 방식을 이해하려면 Compose CLI, compose.yaml 파일, 그리고 Docker 엔진 간의 관계를 파악해야 합니다. Compose CLI는 사용자가 작성한 compose.yaml 파일을 파싱하여 선언된 구성을 Docker 엔진이 이해할 수 있는 일련의 API 호출로 변환하는 클라이언트 역할을 합니다. 즉, Compose 자체가 컨테이너를 직접 실행하는 것이 아니라, 파일의 정의에 따라 Docker 엔진에게 컨테이너, 네트워크, 볼륨 등의 리소스를 생성하고 관리하도록 지시하는 것입니다. Compose는 “프로젝트(project)”라는 개념을 사용하여 여러 애플리케이션 환경을 서로 격리합니다. 기본적으로 프로젝트 이름은 compose.yaml 파일이 위치한 디렉터리의 이름으로 설정되며, 이 이름은 생성되는 모든 리소스(컨테이너, 네트워크, 볼륨 등)의 접두사로 사용됩니다. 예를 들어, myproject라는 디렉터리에서 web과 db 서비스를 실행하면, myproject_web_1, myproject_db_1과 같은 이름의 컨테이너가 생성됩니다. 이 메커니즘 덕분에 동일한 호스트에서 여러 다른 Compose 프로젝트를 실행하더라도 리소스 이름이 충돌하는 것을 방지할 수 있습니다. 이처럼 Docker Compose는 복잡한 컨테이너 관리를 추상화하여 개발자가 애플리케이션 로직 자체에 더 집중할 수 있도록 지원하며, 이는 쿠버네티스와 같은 더 복잡한 오케스트레이션 시스템으로 나아가는 중요한 디딤돌 역할을 합니다. Part II: compose.yaml 파일의 해부 Chapter 2: 핵심 구조와 문법 2.1 Docker Compose를 위한 YAML 필수 사항 Docker Compose 파일은 YAML(YAML Ain’t Markup Language) 형식을 사용합니다. YAML은 사람이 읽고 쓰기 쉬운 데이터 직렬화 언어로, 몇 가지 핵심적인 문법 규칙을 따릅니다. 키-값 쌍: 데이터는 key: value 형태로 구성됩니다. 콜론(:) 뒤에는 반드시 공백이 하나 있어야 합니다. 들여쓰기: YAML은 들여쓰기를 통해 데이터의 계층 구조를 정의합니다. 탭(tab) 문자는 허용되지 않으며, 반드시 공백(space)을 사용해야 합니다. 들여쓰기 칸 수는 일관성만 유지된다면 상관없지만, 일반적으로 2칸을 사용하는 것이 관례입니다. 잘못된 들여쓰기는 Compose 파일에서 가장 흔하게 발생하는 오류의 원인이므로 각별한 주의가 필요합니다. 리스트(배열): 항목 목록은 하이픈(-)으로 시작하며, 하이픈 뒤에도 공백이 필요합니다. 주석: # 기호 뒤에 오는 모든 내용은 주석으로 처리됩니다. 이러한 기본 규칙을 숙지하는 것은 유효한 compose.yaml 파일을 작성하기 위한 첫걸음입니다. 2.2 최상위 요소: 설계도의 기초 compose.yaml 파일은 몇 가지 주요 최상위 키(top-level elements)를 중심으로 구성됩니다. 이들은 애플리케이션 전체의 구조를 정의하는 기초가 됩니다. version: 과거에는 Compose 파일의 형식을 지정하는 중요한 역할을 했지만, 현재는 버전 2.x와 3.x가 통합된 Compose Specification으로 표준화되면서 그 중요성이 줄었습니다. 최신 파일에서는 생략하거나 “3.8”과 같이 명시적으로 버전을 표기할 수 있습니다. services: 가장 핵심적인 요소로, 애플리케이션을 구성하는 개별 컨테이너(서비스)들을 정의하는 공간입니다. 각 서비스의 이미지, 포트, 볼륨 등 구체적인 설정이 여기에 포함됩니다. networks: 서비스 간의 통신을 가능하게 하고 격리하기 위한 커스텀 네트워크를 정의합니다. volumes: 컨테이너의 생명주기를 넘어 데이터를 영속적으로 저장하기 위한 명명된 볼륨(named volumes)을 정의합니다. configs 및 secrets: 구성 파일이나 비밀번호와 같은 민감한 데이터를 관리하기 위한 고급 기능으로, 이들 역시 최상위에서 정의할 수 있습니다. 이러한 최상위 요소들은 다중 컨테이너 애플리케이션의 세 가지 핵심 구성 요소인 컴퓨팅(services), 네트워킹(networks), 스토리지(volumes)를 명확하게 분리하여 관리할 수 있도록 해줍니다. Chapter 3: 서비스 정의 3.1 services 블록: 애플리케이션의 심장 services 블록은 compose.yaml 파일의 심장부로, 애플리케이션을 구성하는 모든 컨테이너를 서비스 단위로 정의합니다. services 아래에 기술되는 각 키(예: web, db, api)는 개발자가 임의로 지정하는 서비스의 이름입니다. 이 이름은 단순히 식별자 역할을 넘어, Compose가 생성하는 내부 네트워크에서 다른 서비스가 해당 서비스를 찾을 때 사용하는 DNS 호스트 이름으로도 기능합니다. 예를 들어, api 서비스는 http://db:5432와 같은 주소로 db 서비스에 접근할 수 있습니다. 각 서비스 정의는 해당 서비스의 컨테이너 인스턴스를 생성하고 실행하는 데 필요한 모든 구성을 포함합니다. 3.2 Image vs. Build: 서비스 이미지 소싱 서비스를 실행할 컨테이너 이미지를 지정하는 방법은 크게 두 가지입니다: image와 build. 이 둘의 선택은 개발 워크플로우와 배포 환경(개발 مقابل 프로덕션)에 따라 달라지며, 이는 Compose 파일 작성의 핵심적인 결정 사항 중 하나입니다. image 사용: Docker Hub와 같은 컨테이너 레지스트리에서 이미 빌드된 이미지를 가져올 때 사용합니다. 이는 데이터베이스(mysql:8.0), 캐시 서버(redis:alpine), 또는 공식 애플리케이션 이미지와 같이 표준화된 소프트웨어를 사용할 때 이상적입니다. 프로덕션 환경에서는 CI/CD 파이프라인을 통해 빌드 및 태깅된 특정 버전의 애플리케이션 이미지를 image 키로 지정하여 배포의 일관성과 안정성을 보장합니다. build 사용: 소스코드로부터 직접 이미지를 빌드해야 할 때 사용합니다. 주로 개발 중인 자체 애플리케이션에 적용됩니다. build 키 아래에는 다음과 같은 하위 속성을 지정할 수 있습니다. context: Dockerfile과 소스코드가 위치한 디렉터리 경로를 지정합니다. dockerfile: Dockerfile 이외의 다른 이름으로 파일명을 지정했을 경우 사용합니다. args: Dockerfile 내에서 사용할 수 있는 빌드 시점의 변수를 전달합니다. 개발 환경에서는 소스 코드 변경 사항을 즉시 반영하여 이미지를 다시 빌드하기 위해 build를 사용하는 것이 일반적입니다. 3.3 네트워크 구성: 포트 노출과 서비스 연결 컨테이너화된 서비스가 외부 또는 다른 서비스와 통신하기 위해서는 네트워크 설정이 필수적입니다. ports: 호스트 머신의 포트와 컨테이너의 포트를 매핑(HOST:CONTAINER)하여 서비스를 외부로 노출시킵니다. 예를 들어, – “8080:80″은 호스트의 8080번 포트로 들어오는 요청을 컨테이너의 80번 포트로 전달합니다. 이는 웹 브라우저 등 외부에서 접근해야 하는 서비스에 필수적입니다. expose: 포트를 호스트에 노출하지 않고, 동일한 Compose 네트워크에 있는 다른 서비스에게만 노출시킵니다. 데이터베이스와 같이 내부 통신만 필요한 서비스에 사용됩니다. networks: 서비스를 특정 최상위 네트워크에 연결하여 통신 범위를 세밀하게 제어할 수 있습니다. 이 키를 명시하지 않으면, Compose는 프로젝트를 위한 기본 브리지(bridge) 네트워크를 생성하고 모든 서비스를 이 네트워크에 자동으로 연결합니다. 이를 통해 서비스들은 서비스 이름을 호스트명으로 사용하여 서로 통신할 수 있습니다. 3.4 환경 및 구성 관리 애플리케이션의 동작을 제어하는 설정값들은 환경 변수를 통해 주입하는 것이 가장 좋은 방법입니다. environment: Compose 파일 내에 직접 환경 변수를 설정합니다. 딕셔너리(맵) 형식이나 리스트 형식 모두 지원합니다. 데이터베이스 연결 정보, API 키, 애플리케이션 모드(development 또는 production) 등을 전달하는 데 유용합니다. env_file: .env와 같은 외부 파일에서 환경 변수를 불러옵니다. 이는 설정과 코드를 분리하는 모범 사례로, 특히 비밀번호와 같은 민감한 정보나 환경별로 달라지는 설정들을 compose.yaml 파일로부터 분리하여 안전하고 유연하게 관리할 수 있게 해줍니다. 3.5 상태 관리: volumes를 이용한 데이터 영속성 컨테이너는 기본적으로 상태가 없고(stateless) 삭제되면 내부 데이터도 함께 사라지는 임시적인(ephemeral) 존재입니다. 따라서 데이터베이스 파일, 사용자 업로드 파일 등 영속적으로 보존해야 할 데이터는 볼륨을 사용하여 컨테이너 외부의 스토리지에 저장해야 합니다. 명명된 볼륨 (Named Volumes): 프로덕션 환경의 데이터를 저장하는 데 가장 권장되는 방식입니다. volumes 최상위 키에 볼륨 이름을 정의하고, 서비스에서는 볼륨이름:/컨테이너/경로 형식으로 마운트합니다. 이 볼륨은 Docker가 직접 관리하며, 컨테이너의 생명주기와 완전히 분리되어 데이터의 안정적인 보존을 보장합니다. 바인드 마운트 (Bind Mounts): 호스트 머신의 파일이나 디렉터리를 컨테이너로 직접 마운트합니다. 로컬/경로:/컨테이너/경로 형식으로 사용하며, 주로 개발 환경에서 소스 코드를 마운트하는 데 사용됩니다. 호스트에서 코드를 수정하면 별도의 이미지 빌드 없이 즉시 컨테이너 내부에 반영되므로, nodemon과 같은 도구를 활용한 실시간 리로딩(live-reloading) 개발이 가능해집니다. 익명 볼륨 (Anonymous Volumes): 컨테이너 경로만 지정했을 때 생성되는 볼륨입니다. 관리 및 추적이 어려워 특별한 경우가 아니면 명명된 볼륨을 사용하는 것이 좋습니다. 이처럼 image와 build, 그리고 명명된 볼륨과 바인드 마운트의 선택은 단순한 구문 차이가 아니라, 개발과 운영이라는 각기 다른 목적에 맞는 워크플로우를 구성하는 전략적 결정입니다. 개발 환경에서는 build와 바인드 마운트를 통해 빠른 피드백과 유연성을 확보하고, 프로덕션 환경에서는 image와 명명된 볼륨을 통해 안정성, 불변성, 데이터 무결성을 보장하는 것이 현대적인 Compose 파일 작성의 핵심 원칙입니다. Chapter 4: 고급 서비스 구성 4.1 시작 및 종료 제어 (depends_on, restart) 애플리케이션의 안정적인 운영을 위해서는 서비스의 시작 순서와 비정상 종료 시의 복구 정책을 제어하는 것이 중요합니다. depends_on: 서비스 간의 의존성을 명시하여 시작 및 종료 순서를 제어합니다. 예를 들어, api 서비스의 depends_on에 db를 지정하면, Compose는 항상 db 컨테이너를 먼저 시작한 후에 api 컨테이너를 시작합니다. 종료 시에는 역순으로 api를 먼저 중지합니다. 하지만 중요한 점은, depends_on은 단지 컨테이너의 시작 순서만을 보장할 뿐, 의존하는 서비스 내부의 애플리케이션이 요청을 처리할 준비가 되었는지(readiness)까지는 기다려주지 않는다는 것입니다. restart: 컨테이너가 중지되었을 때 재시작 정책을 정의합니다. no(기본값), always, on-failure, unless-stopped 등의 값을 사용할 수 있습니다. 프로덕션 환경에서는 예상치 못한 오류로 컨테이너가 종료되더라도 자동으로 복구되도록 restart: always 또는 unless-stopped를 설정하는 것이 일반적입니다. 4.2 healthcheck를 통한 서비스 준비 상태 보장 depends_on의 한계를 극복하고 서비스가 실제로 요청을 처리할 준비가 되었는지 확인하기 위해 healthcheck 기능이 사용됩니다. 이는 분산 시스템에서 발생할 수 있는 경쟁 조건(race condition) 문제를 해결하는 핵심적인 메커니즘입니다. healthcheck 지시어는 컨테이너의 건강 상태를 주기적으로 확인할 명령어를 정의합니다. 예를 들어, PostgreSQL 서비스에는 pg_isready 명령어를, Redis에는 redis-cli ping을 사용하여 서비스가 정상적으로 응답하는지 확인할 수 있습니다. 이 healthcheck는 depends_on의 condition 속성과 결합될 때 강력한 시너지를 발휘합니다. depends_on: db: condition: service_healthy 와 같이 설정하면, api 서비스는 db 컨테이너가 단순히 시작되는 것을 넘어, healthcheck를 통과하여 ‘healthy’ 상태가 될 때까지 기다렸다가 시작됩니다. 이 과정은 다음과 같이 진행됩니다: 사용자가 depends_on만으로 api가 db에 의존하도록 설정합니다. docker compose up 실행 시, db 컨테이너가 시작된 직후 api 컨테이너가 시작됩니다. api 애플리케이션은 데이터베이스 연결을 시도하지만, db 컨테이너 내부의 PostgreSQL 프로세스는 아직 초기화 중일 수 있습니다. 연결은 실패하고 api 컨테이너는 비정상 종료됩니다. 사용자는 db 서비스에 healthcheck를 추가하여 pg_isready를 주기적으로 실행하도록 합니다. 이제 컨테이너는 starting, healthy, unhealthy 상태를 갖게 됩니다. 마지막으로, api 서비스의 depends_on 설정을 condition: service_healthy로 변경합니다. 이제 docker compose up은 db 컨테이너를 시작하고, healthcheck가 성공하여 healthy 상태가 될 때까지 기다린 후 비로소 api 컨테이너를 시작합니다. 이처럼 단순한 시작 순서 제어에서 진정한 ‘준비 상태’ 확인으로의 전환은 안정적이고 자동화된 배포를 위한 필수 요건입니다. 4.3 리소스 관리 및 보안 (deploy, profiles) deploy: 주로 Docker Swarm 모드에서 사용되는 키지만, resources와 같은 일부 하위 키는 Compose에서도 컨테이너의 메모리 및 CPU 사용량을 제한하는 데 사용될 수 있습니다. 이를 통해 특정 서비스가 과도한 리소스를 점유하는 것을 방지할 수 있습니다. profiles: 서비스들을 그룹으로 묶어 선택적으로 활성화하거나 비활성화할 수 있는 기능입니다. 예를 들어, 핵심 서비스(웹, API, DB)와는 별도로 디버깅 도구나 테스트용 서비스들을 debug 프로파일로 정의해두고, 필요할 때만 docker compose –profile debug up 명령으로 실행할 수 있습니다. 이는 Compose 파일을 더 깔끔하고 모듈식으로 관리하는 데 매우 유용합니다. Part III: CLI를 이용한 애플리케이션 생명주기 관리 compose.yaml 파일 작성이 애플리케이션을 ‘설계’하는 과정이라면, Docker Compose CLI(Command Line Interface)는 설계된 애플리케이션을 ‘운영’하는 도구입니다. 최신 docker compose (v2, 공백 사용) 구문을 기준으로 핵심적인 명령어들을 살펴보겠습니다. Chapter 5: 필수 Docker Compose 명령어 Compose CLI 명령어는 크게 애플리케이션 스택 전체를 관리하는 ‘프로젝트 수준’ 명령어와 개별 서비스를 대상으로 하는 ‘서비스 수준’ 명령어로 나눌 수 있습니다. 이러한 구분은 사용자가 상황에 맞는 적절한 도구를 선택하는 데 도움을 줍니다. 5.1 애플리케이션 기동 및 정지: up과 down docker compose up: compose.yaml 파일에 정의된 모든 서비스를 위해 이미지를 빌드(또는 재빌드)하고, 컨테이너를 생성 및 시작하며, 로그를 터미널에 연결하는 가장 기본적인 명령어입니다. -d 또는 –detach: 컨테이너를 백그라운드에서 실행합니다. 프로덕션 환경이나 장기 실행 서비스에 필수적인 옵션입니다. –build: 컨테이너를 시작하기 전에 항상 이미지를 강제로 다시 빌드합니다. –force-recreate: 설정이 변경되지 않았더라도 컨테이너를 강제로 다시 생성합니다. –scale <서비스명>=<개수>: 특정 서비스의 컨테이너 수를 지정된 개수로 확장합니다. docker compose down: up 명령으로 생성된 컨테이너, 네트워크, 기본 볼륨 등 모든 리소스를 중지하고 제거합니다. -v 또는 –volumes: volumes 최상위 키에 정의된 명명된 볼륨까지 함께 삭제합니다. 프로젝트를 완전히 초기화할 때 사용되며, 중요한 데이터가 손실될 수 있으므로 주의해야 합니다. –rmi : 서비스에서 사용된 도커 이미지를 삭제합니다. all은 모든 이미지를, local은 커스텀 태그가 없는 이미지만을 삭제합니다. 5.2 상태 확인: ps, logs, top, events docker compose ps: 현재 프로젝트에 속한 컨테이너들의 목록과 상태(실행 중, 중지 등), 포트 매핑 정보를 보여줍니다. docker compose logs: 서비스 컨테이너에서 발생하는 로그를 출력합니다. 디버깅에 가장 필수적인 명령어 중 하나입니다. -f 또는 –follow: 실시간으로 로그를 계속해서 스트리밍합니다. –tail=”N”: 마지막 N줄의 로그만 표시합니다. 특정 서비스의 로그만 보려면 docker compose logs <서비스명> 형식으로 사용합니다. docker compose top: 각 서비스 컨테이너 내부에서 실행 중인 프로세스 목록을 보여줍니다. docker compose events: 컨테이너 생성, 시작, 중지 등 Docker 데몬에서 발생하는 이벤트를 실시간으로 스트리밍합니다. 5.3 서비스와 상호작용: exec, run, cp docker compose exec <서비스명> <명령어>: 실행 중인 컨테이너 내부에서 지정된 명령어를 실행합니다. docker compose exec api /bin/sh와 같이 대화형 셸에 접속하여 내부 상태를 확인하거나 디버깅하는 데 매우 유용합니다. docker compose run <서비스명> <명령어>: 새로운 컨테이너를 생성하여 일회성 명령을 실행합니다. 데이터베이스 마이그레이션 스크립트 실행이나 초기 데이터 생성과 같은 일회성 작업에 적합합니다. –rm 옵션을 함께 사용하면 명령 실행 후 컨테이너가 자동으로 삭제되어 편리합니다. docker compose cp: 로컬 파일 시스템과 서비스 컨테이너 간에 파일을 복사합니다. 5.4 리소스 관리: build, pull, push, rm docker compose build: compose.yaml 파일의 build 지시어를 기반으로 서비스 이미지를 빌드하거나 재빌드합니다. docker compose pull: 서비스에 필요한 이미지들을 레지스트리에서 미리 다운로드합니다. docker compose push: 빌드된 서비스 이미지들을 레지스트리로 푸시합니다. docker compose rm: 중지된 서비스 컨테이너들을 삭제합니다. 이러한 명령어들을 통해 개발자는 애플리케이션의 전체 생명주기를 효과적으로 관리할 수 있습니다. 예를 들어, 개발자는 먼저 docker compose up -d로 전체 프로젝트를 시작하고, docker compose ps로 상태를 확인합니다. 특정 서비스에서 문제가 발생하면 docker compose logs <서비스명>으로 원인을 파악하고, docker compose exec <서비스명> /bin/sh로 컨테이너에 직접 들어가 문제를 해결하는 논리적인 워크플로우를 따를 수 있습니다. Part IV: 실제 적용 및 사례 연구 이론적 개념들을 실제 프로젝트에 적용하는 방법을 이해하기 위해, 몇 가지 구체적인 사례를 살펴보겠습니다. 더 많은 예제는 공식 awesome-compose 저장소에서 찾아볼 수 있으며, 이는 다양한 기술 스택을 학습하는 데 훌륭한 자료가 됩니다. Chapter 6: 사례 연구: Nginx로 정적 웹사이트 배포 6.1 시나리오: 간단한 HTML, CSS, JavaScript로 구성된 정적 웹사이트를 Nginx 컨테이너를 사용하여 호스팅합니다. 6.2 프로젝트 구조: project/ ├── compose.yaml ├── nginx/ │ └── nginx.conf └── src/ └── index.html 6.3 compose.yaml 분석: version: “3.8” services: nginx: image: nginx:1-alpine container_name: static_website ports: – “8080:80″ volumes: -./src:/usr/share/nginx/html -./nginx/nginx.conf:/etc/nginx/nginx.conf:ro restart: unless-stopped image: nginx:1-alpine: 최소한의 크기를 위해 공식 alpine 버전 Nginx 이미지를 사용합니다. ports: 호스트의 8080 포트를 컨테이너의 80 포트에 연결하여 외부 접근을 허용합니다. volumes: 두 개의 바인드 마운트를 사용합니다. 로컬의 ./src 디렉터리를 컨테이너의 /usr/share/nginx/html (Nginx의 기본 웹 루트)에 마운트하여 정적 파일을 제공합니다. 로컬의 ./nginx/nginx.conf 파일을 컨테이너의 /etc/nginx/nginx.conf에 읽기 전용(ro)으로 마운트하여 기본 Nginx 설정을 덮어씁니다. 6.4 실행 및 확인: 프로젝트 루트 디렉터리에서 docker compose up -d 명령을 실행한 후, 웹 브라우저에서 http://localhost:8080에 접속하여 index.html의 내용이 올바르게 표시되는지 확인합니다. Chapter 7: 사례 연구: Full-Stack Node.js와 PostgreSQL 애플리케이션 7.1 시나리오: Node.js/Express 백엔드 API와 PostgreSQL 데이터베이스로 구성된 일반적인 웹 애플리케이션을 구축합니다. 7.2 프로젝트 구조: project/ ├── compose.yaml ├──.env └── api/ ├── Dockerfile ├── package.json └── server.js 7.3 compose.yaml 분석 : version: “3.8” services: db: image: postgres:15-alpine container_name: postgres_db restart: always env_file:./.env environment: – POSTGRES_USER=${POSTGRES_USER} – POSTGRES_PASSWORD=${POSTGRES_PASSWORD} – POSTGRES_DB=${POSTGRES_DB} ports: – “${POSTGRES_LOCAL_PORT}:5432″ volumes: – db-data:/var/lib/postgresql/data healthcheck: test: interval: 10s timeout: 5s retries: 5 api: build:./api container_name: node_api restart: unless-stopped ports: – “3000:3000″ depends_on: db: condition: service_healthy environment: – DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} volumes: db-data: db 서비스: image: postgres:15-alpine: 공식 PostgreSQL 이미지를 사용합니다. env_file:.env: .env 파일에서 데이터베이스 사용자, 비밀번호 등의 환경 변수를 불러옵니다. volumes: db-data라는 명명된 볼륨을 사용하여 데이터베이스 데이터를 영속적으로 저장합니다. healthcheck: pg_isready를 사용하여 데이터베이스가 연결을 받을 준비가 되었는지 확인합니다. api 서비스: build:./api: api 디렉터리의 Dockerfile을 사용하여 Node.js 애플리케이션 이미지를 빌드합니다. depends_on: db 서비스가 healthy 상태가 될 때까지 기다립니다. 이는 API가 시작되기 전에 데이터베이스가 완전히 준비되도록 보장합니다. environment: 데이터베이스 연결 문자열을 환경 변수로 전달합니다. 여기서 호스트 이름으로 서비스 이름인 db를 사용한 점이 중요합니다. Compose 내부 네트워크에서 서비스 이름은 DNS로 해석 가능합니다. 7.4 실행 및 확인: docker compose up 실행 후, API가 성공적으로 데이터베이스에 연결되고 정상적으로 작동하는지 API 엔드포인트를 통해 확인합니다. Chapter 8: 사례 연구: Python 데이터 서비스와 MySQL 데이터베이스 8.1 시나리오: Python(Flask 프레임워크 기반) 애플리케이션이 MySQL 데이터베이스와 상호작용하는 서비스를 구성합니다. 8.2 프로젝트 구조: Node.js 예제와 유사하게, Python에 특화된 Dockerfile과 requirements.txt를 포함합니다. 8.3 compose.yaml 분석: version: “3.8” services: db: image: mysql:8.0 container_name: mysql_db restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} volumes: – db-data:/var/lib/mysql healthcheck: test: timeout: 20s retries: 10 app: build:./app container_name: python_app restart: unless-stopped ports: – “5000:5000″ depends_on: db: condition: service_healthy environment: – DB_HOST=db – DB_USER=${MYSQL_USER} – DB_PASSWORD=${MYSQL_PASSWORD} – DB_NAME=${MYSQL_DATABASE} volumes: db-data: db 서비스: image: mysql:8.0: 공식 MySQL 이미지를 사용합니다. environment: MYSQL_ROOT_PASSWORD, MYSQL_DATABASE 등 MySQL 초기 설정을 위한 환경 변수를 설정합니다. volumes: 명명된 볼륨을 사용하여 /var/lib/mysql 경로의 데이터를 보존합니다. healthcheck: mysqladmin ping 명령어로 MySQL 서버의 응답 상태를 확인합니다. app 서비스: build:./app: Python 애플리케이션을 빌드합니다. depends_on: db 서비스의 healthcheck가 통과될 때까지 시작을 대기합니다. environment: Python 애플리케이션이 MySQL에 연결하는 데 필요한 호스트, 사용자, 비밀번호 등의 정보를 환경 변수로 전달합니다. Part V: 모범 사례 및 프로덕션 고려 사항 Chapter 9: 프로덕션 수준의 Compose 파일 개발 개발 환경에서 사용하던 Compose 파일을 프로덕션 환경에 그대로 적용하는 것은 바람직하지 않습니다. 프로덕션 환경은 안정성, 보안, 성능에 대한 요구사항이 훨씬 높기 때문입니다. 프로젝트의 성숙도를 나타내는 핵심 지표 중 하나는 개발, 테스트, 프로덕션 등 각 환경의 관심사를 분리하여 관리하는 능력입니다. 9.1 여러 Compose 파일을 이용한 환경 분리 가장 일반적인 모범 사례는 여러 개의 Compose 파일을 사용하여 환경을 분리하는 것입니다. 이 접근법은 다음과 같이 구성됩니다. compose.yaml (기본 파일): 모든 환경(개발, 프로덕션 등)에서 공통으로 사용되는 핵심 서비스 정의, 네트워크, 볼륨 등을 포함합니다. 이 파일은 DRY(Don’t Repeat Yourself) 원칙을 지키며 기본 구성을 깨끗하게 유지합니다. compose.dev.yaml (개발용 오버라이드 파일): 개발 환경에만 특화된 설정을 정의합니다. 예를 들어, 소스 코드 실시간 동기화를 위한 바인드 마운트, nodemon과 같은 개발 서버 실행을 위한 command 오버라이드, 디버깅 포트 노출 등이 포함됩니다. compose.prod.yaml (프로덕션용 오버라이드 파일): 프로덕션 환경에 필요한 설정을 정의합니다. 여기에는 restart: always와 같은 재시작 정책, 소스 코드 바인드 마운트 제거(코드는 이미지에 포함), 프로덕션용 환경 변수(예: 로그 레벨을 INFO로 설정) 등이 포함됩니다. 이렇게 파일을 분리한 후, docker compose -f compose.yaml -f compose.prod.yaml up과 같이 -f 플래그를 사용하여 여러 파일을 병합할 수 있습니다. Compose는 지정된 순서대로 파일을 읽어 설정을 덮어쓰므로, 환경별 차이점을 명확하게 관리하고 검토할 수 있는 강력하고 체계적인 워크플로우를 구축할 수 있습니다. 9.2 민감 데이터 관리 .env 파일은 일반적인 설정을 관리하는 데 유용하지만, API 키, 비밀번호와 같은 매우 민감한 정보를 저장하기에는 최적의 방법이 아닐 수 있습니다. 특히 버전 관리 시스템에 실수로 포함될 위험이 있습니다. 프로덕션 환경에서는 Docker가 제공하는 secrets 기능을 사용하는 것이 더 안전한 대안입니다. secrets는 최상위 키로 정의되며, 컨테이너 내부의 /run/secrets/ 경로에 파일 형태로 마운트됩니다. 애플리케이션은 이 파일을 읽어 민감한 데이터를 메모리에 로드하므로, 환경 변수에 직접 노출되는 것을 방지할 수 있습니다. 9.3 이미지 최적화 및 재현성 버전 고정: latest 태그 대신 node:18.17.1-alpine과 같이 구체적인 버전 태그를 사용하여 이미지를 지정해야 합니다. latest는 시간이 지남에 따라 다른 버전을 가리킬 수 있으므로, 빌드의 재현성을 해치고 예기치 않은 문제를 유발할 수 있습니다. 최소 기본 이미지 사용: 가능한 경우 alpine 기반의 이미지를 사용하면 이미지 크기를 크게 줄일 수 있습니다. 이는 저장 공간을 절약하고 배포 속도를 높이며, 포함된 라이브러리가 적어 보안 취약점(attack surface)을 줄이는 효과도 있습니다. 다단계 빌드 (Multi-Stage Builds): Dockerfile에서 다단계 빌드를 활용하여 프로덕션용 이미지를 최적화할 수 있습니다. 첫 번째 단계(build stage)에서는 컴파일러, 개발 라이브러리 등 빌드에 필요한 모든 도구를 사용하여 애플리케이션을 빌드하고, 두 번째 단계(final stage)에서는 최소한의 기본 이미지 위에 첫 단계에서 생성된 실행 파일이나 결과물만 복사합니다. 이렇게 하면 최종 프로덕션 이미지에는 빌드에만 필요했던 무거운 의존성들이 포함되지 않아 매우 가볍고 안전한 이미지를 만들 수 있습니다. Chapter 10: 고급 패턴 및 향후 방향 10.1 extends와 YAML 앵커를 이용한 코드 재사용 extends: 한 서비스가 다른 서비스의 구성을 상속받을 수 있게 해주는 기능입니다. 동일 파일 또는 다른 파일에 있는 서비스의 설정을 재사용하여 중복을 줄일 수 있습니다. Compose v3 명세에서 공식적으로 제외되었던 이력이 있지만, 여전히 많은 사용 사례에서 유용하게 쓰이고 있습니다. YAML 앵커(&)와 별칭(): extends의 대안으로, 순수 YAML 기능을 사용하여 설정 블록을 재사용할 수 있습니다. 앵커(&)로 재사용할 설정 블록에 이름을 지정하고, 별칭()으로 해당 블록을 참조하는 방식입니다. 이는 단일 파일 내에서 설정을 모듈화하고 중복을 제거하는 데 매우 유연하고 강력한 방법을 제공합니다. 10.2 CI/CD 파이프라인과의 통합 Docker Compose는 CI/CD(지속적 통합/지속적 제공) 파이프라인의 핵심 요소로 활용될 수 있습니다. 일반적인 워크플로우는 다음과 같습니다. 개발자가 Git에 코드를 푸시합니다. CI 서버(예: Jenkins, GitHub Actions)가 변경 사항을 감지하고 코드를 체크아웃합니다. CI 서버는 docker compose -f compose.yaml -f compose.ci.yaml build 명령으로 이미지를 빌드합니다. docker compose -f compose.yaml -f compose.ci.yaml run –rm tests와 같은 명령으로 테스트를 실행합니다. 테스트가 성공하면, docker compose push 명령으로 빌드된 이미지를 컨테이너 레지스트리에 푸시합니다. CD 서버는 프로덕션 서버에 접속하여 최신 이미지를 pull하고, docker compose -f compose.yaml -f compose.prod.yaml up -d –force-recreate 명령으로 애플리케이션을 무중단에 가깝게 업데이트합니다. 10.3 미래: Compose와 쿠버네티스 Docker Compose는 단일 호스트 환경에서의 컨테이너 오케스트레이션에 최적화되어 있지만, 그 역할이 여기서 끝나지는 않습니다. 쿠버네티스가 대규모 분산 시스템의 표준으로 자리 잡으면서, Compose는 로컬 개발 환경과 쿠버네티스 간의 다리 역할을 합니다. Kompose와 같은 도구는 compose.yaml 파일을 쿠버네티스 매니페스트(Deployment, Service 등)로 변환해주는 기능을 제공합니다. 이를 통해 개발자들은 익숙한 Compose 환경에서 애플리케이션을 개발하고 테스트한 후, 비교적 쉽게 쿠버네티스 환경으로 전환할 수 있습니다. 이는 Compose가 복잡한 클라우드 네이티브 생태계로 진입하는 개발자들에게 중요한 첫걸음이 될 수 있음을 시사합니다. 결론 Docker Compose는 단일 Dockerfile의 한계를 넘어, 다중 컨테이너로 구성된 복잡한 애플리케이션을 하나의 단위로 정의하고 관리할 수 있게 해주는 강력하고 필수적인 도구입니다. compose.yaml이라는 선언적 파일을 통해 애플리케이션의 전체 아키텍처를 코드로 관리함으로써, 개발 환경의 일관성을 보장하고, 팀원 간의 협업을 촉진하며, 배포 프로세스를 자동화합니다. 본 보고서에서 살펴본 바와 같이, services, networks, volumes와 같은 핵심 요소를 이해하고, depends_on과 healthcheck를 결합하여 서비스 준비 상태를 보장하며, 환경별로 Compose 파일을 분리하는 모범 사례를 적용함으로써, 개발자는 단순한 로컬 개발 도구를 넘어 프로덕션 수준의 안정적이고 확장 가능한 애플리케이션을 구축할 수 있습니다. CLI 명령어를 통한 능숙한 생명주기 관리는 이러한 과정을 더욱 효율적으로 만듭니다. 결론적으로, Docker Compose는 개발자가 컨테이너 기술의 이점을 최대한 활용하여 더 빠르고, 더 안정적으로 소프트웨어를 개발하고 배포할 수 있도록 지원하는 핵심 기술입니다. 그 원칙과 패턴을 깊이 이해하는 것은 모든 현대 개발자에게 요구되는 중요한 역량이라 할 수 있습니다. 참고 자료

  1. sseozytank.tistory.com, https://sseozytank.tistory.com/86#:~:text=%EB%8F%84%EC%BB%A4%20%EC%BB%B4%ED%8F%AC%EC%A6%88%EB%9E%80%3F,%ED%95%B4%EC%95%BC%ED%95%98%EB%8A%94%20%EB%B2%88%EA%B1%B0%EB%A1%9C%EC%9B%80%EC%9D%B4%20%EC%9E%88%EB%8B%A4. 2. [Docker] Docker Compose 란? 특징 및 필요성 – 프론티어쿼리 – 티스토리, https://fronquarry.tistory.com/40 3. 도커 컴포즈(Docker Compose) 뜻, 작성법 및 주의사항 – SInce 20180106 – 티스토리, https://blockchainstudy.tistory.com/62 4. 4 : Docker Compose 이해하고 구성하기 – Contributor9 – 티스토리, https://adjh54.tistory.com/503 5. TIL112. Docker : Docker-compose를 쓰는 이유 – velog, https://velog.io/@jewon119/TIL112.-Docker-Docker-compose%EB%A5%BC-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0 6. 도커 컴포즈(Docker Compose) – velog, https://velog.io/@dong5854/%EB%8F%84%EC%BB%A4-%EC%BB%B4%ED%8F%AC%EC%A6%88Docker-Compose 7. [Docker] Docker Compose란? + 명령어 – 홍카나의 공부방 – 티스토리, https://hongcana.tistory.com/141 8. Docker Compose와 버전별 특징, https://meetup.nhncloud.com/posts/277/ 9. [Docker] Docker Compose를 이용한 손 쉬운 배포 환경 구성하기(Ubuntu, Flask, Nginx, uWSGI, MySQL), https://eveningdev.tistory.com/169 10. [Docker] 도커 컴포즈와 분산 애플리케이션 – 강해질거임 – 티스토리, https://joyerim.tistory.com/108 11. Docker compose란? – 개발일기 – 티스토리, https://phsun102.tistory.com/36 12. Compose file reference | Docker Docs, https://docs.docker.com/reference/compose-file/ 13. Docker Compose 파일 구성 요소 – Stack O Flow, https://sangjaeoh.github.io/docker/docker-compose-%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%84%B1-%EC%9A%94%EC%86%8C/ 14. Services – Docker Docs, https://docs.docker.com/reference/compose-file/services/ 15. [Docker-compose] yaml 파일 구조 설명 – 월클데싸 – 티스토리, https://big-data-analyst.tistory.com/22 16. Interpolation | Docker Docs, https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/ 17. docker-compose.yml 사용하여 다중 컨테이너 애플리케이션 정의 …, https://learn.microsoft.com/ko-kr/dotnet/architecture/microservices/multi-container-microservice-net-applications/multi-container-applications-docker-compose 18. Volumes – Docker Docs, https://docs.docker.com/engine/storage/volumes/ 19. Use Volumes to Manage Persistent Data With Docker Compose – Kinsta, https://kinsta.com/blog/docker-compose-volumes/ 20. [INFRA] Docker와 Dockerfile, Docker-compose 구성하기 – olrlobt – 티스토리, https://olrlobt.tistory.com/81 21. How to install Nginx in Docker-Compose – Zomro, https://zomro.com/blog/faq/317-kak-ustanovit-nginx-v-docker-compose 22. Can’t connect python server and mysql with Docker compose – Stack Overflow, https://stackoverflow.com/questions/78072241/cant-connect-python-server-and-mysql-with-docker-compose 23. Collection of Docker Compose healthcheck examples – GitHub, https://github.com/rodrigobdz/docker-compose-healthchecks 24. docker compose | Docker Docs, https://docs.docker.com/reference/cli/docker/compose/ 25. Docker Compose Commands – GitHub Gist, https://gist.github.com/mkfares/41c9609fcde8d9f665210034e99d4bd9 26. Awesome Docker Compose samples – GitHub, https://github.com/docker/awesome-compose 27. Awesome-Compose: What It is and Why You Should Really Care About It, https://ajeetraina.medium.com/awesome-compose-what-it-is-and-why-you-should-really-care-about-it-69770ac82780 28. How to use Nginx with Docker Compose effectively with examples, https://geshan.com.np/blog/2024/03/nginx-docker-compose/ 29. nginx – Official Image – Docker Hub, https://hub.docker.com/_/nginx 30. Docker Compose Nodejs and Postgres example – BezKoder, https://www.bezkoder.com/docker-compose-nodejs-postgres/ 31. Create a postgres database within a docker-compose.yml file – Stack Overflow, https://stackoverflow.com/questions/75246059/create-a-postgres-database-within-a-docker-compose-yml-file 32. awesome-compose/nginx-flask-mysql/README.md at master – GitHub, https://github.com/docker/awesome-compose/blob/master/nginx-flask-mysql/README.md 33. Use Compose in production – Docker Docs, https://docs.docker.com/compose/how-tos/production/ 34. Dockerizing an Express.js API with PostgreSQL Database for Testing and Production, https://dev.to/mspilari/dockerizing-an-expressjs-api-with-postgresql-database-for-testing-and-production-1pjj 35. Best practices | Docker Docs, https://docs.docker.com/compose/how-tos/environment-variables/best-practices/ 36. Building best practices – Docker Docs, https://docs.docker.com/build/building/best-practices/ 37. docker-compose/docs/extends.md at master – GitHub, https://github.com/Yelp/docker-compose/blob/master/docs/extends.md 38. Extend | Docker Docs, https://docs.docker.com/compose/how-tos/multiple-compose-files/extends/ 39. Docker-compose: Replacement for the extends keyword – Stack Overflow, https://stackoverflow.com/questions/58673249/docker-compose-replacement-for-the-extends-keyword 40. Extensions | Docker Docs, https://docs.docker.com/reference/compose-file/extension/

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다