# Docker & Docker Compose를 활용한 애플리케이션 배포 가이드 ## 1. Docker 기본 개념 ### 1.1 Docker란 무엇인가? Docker는 애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 컨테이너 기반의 오픈소스 가상화 플랫폼입니다. 애플리케이션을 해당 환경과 함께 "컨테이너"라는 격리된 공간에 패키징하여, 개발 환경과 운영 환경의 차이로 인해 발생하는 문제를 해결합니다. ### 1.2 이미지(Image)와 컨테이너(Container) - **이미지(Image)**: 애플리케이션을 실행하는 데 필요한 모든 것(코드, 런타임, 시스템 도구, 라이브러리 등)을 포함하는 읽기 전용 템플릿입니다. 이미지는 계층(Layer)으로 구성되어 있어 효율적인 빌드와 배포가 가능합니다. - **컨테이너(Container)**: 이미지의 실행 가능한 인스턴스입니다. 하나의 이미지로 여러 개의 독립적인 컨테이너를 생성할 수 있으며, 각 컨테이너는 격리된 환경에서 실행됩니다. ### 1.3 Dockerfile 작성법 `Dockerfile`은 Docker 이미지를 생성하기 위한 지침이 담긴 텍스트 파일입니다. Docker는 이 파일을 읽어 순서대로 명령을 실행하여 이미지를 빌드합니다. 주요 `Dockerfile` 명령어는 다음과 같습니다. ([공식 문서 참고](https://docs.docker.com/reference/dockerfile/)) - `FROM`: 생성할 이미지의 기반이 될 베이스 이미지를 지정합니다. (예: `FROM python:3.9-slim`) - `WORKDIR`: `RUN`, `CMD`, `ENTRYPOINT`, `COPY`, `ADD` 명령이 실행될 작업 디렉토리를 설정합니다. - `COPY` 또는 `ADD`: 호스트 머신의 파일이나 디렉토리를 컨테이너 안으로 복사합니다. - `RUN`: 이미지 빌드 과정에서 필요한 스크립트나 명령을 실행합니다. 주로 애플리케이션 의존성 설치 등에 사용됩니다. - `CMD` 또는 `ENTRYPOINT`: 컨테이너가 시작될 때 실행될 기본 명령을 설정합니다. - `EXPOSE`: 컨테이너가 외부에 노출할 포트를 지정합니다. **Dockerfile 예시 (`Dockerfile` - Spring Boot)** ```dockerfile # 1단계: Maven을 사용하여 애플리케이션 빌드 (빌드 환경) FROM maven:3.8-eclipse-temurin-17 AS build WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # 2단계: 빌드된 애플리케이션을 실행 환경으로 복사 FROM eclipse-temurin:17-jre-focal WORKDIR /app COPY --from=build /app/target/*.jar app.jar # 컨테이너가 8080번 포트를 외부에 노출 EXPOSE 8080 # 컨테이너 시작 시 JAR 파일 실행 ENTRYPOINT ["java", "-jar", "app.jar"] ``` ## 2. Docker Compose 활용 ### 2.1 Docker Compose란 무엇인가? Docker Compose는 여러 개의 컨테이너로 구성된 애플리케이션을 정의하고 실행하기 위한 도구입니다. `docker-compose.yml`이라는 YAML 파일을 사용하여 애플리케이션의 서비스, 네트워크, 볼륨 등을 설정할 수 있습니다. 이를 통해 복잡한 애플리케이션도 단일 명령어로 쉽게 관리할 수 있습니다. ### 2.2 `docker-compose.yml` 작성법 - `version`: 사용할 Docker Compose 파일 형식을 지정합니다. (현재는 `services` 등 최상위 요소만으로 구성 가능) - `services`: 애플리케이션을 구성하는 각 컨테이너(서비스)를 정의합니다. - `image`: 서비스의 기반이 될 Docker 이미지를 지정합니다. - `build`: Docker 이미지를 직접 빌드할 때 `Dockerfile`이 있는 경로를 지정합니다. - `ports`: `HOST:CONTAINER` 형식으로 호스트와 컨테이너 간의 포트를 매핑합니다. - `volumes`: `HOST:CONTAINER` 형식으로 호스트와 컨테이너 간의 디렉토리나 파일을 공유(마운트)합니다. - `environment`: 컨테이너 내에서 사용할 환경 변수를 설정합니다. - `networks`: 서비스들이 사용할 네트워크를 정의합니다. - `volumes`: 데이터를 영속적으로 저장하기 위한 볼륨을 정의합니다. **docker-compose.yml 예시 (`docker-compose.yml` - Spring Boot)** ```yaml services: web: build: . container_name: my_java_app ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=development ``` 위 예시는 `web` 서비스를 정의합니다. - `web` 서비스는 현재 디렉토리의 `Dockerfile`을 사용하여 이미지를 빌드하고, 호스트의 8080번 포트를 컨테이너의 8080번 포트와 연결합니다. - `container_name`으로 컨테이너의 이름을 지정하고, `environment`를 통해 스프링 부트의 `development` 프로파일을 활성화합니다. ## 3. Shell Script를 이용한 배포 자동화 ### 3.1 왜 Shell Script를 사용하는가? 애플리케이션을 배포하는 과정은 일반적으로 다음과 같은 반복적인 작업들을 포함합니다. 1. 최신 소스코드 가져오기 2. Docker 이미지 빌드 3. 기존 컨테이너 중지 및 삭제 4. 새로운 컨테이너 실행 5. 불필요한 리소스 정리 이러한 과정들을 Shell Script로 작성해두면, 단일 명령어 실행만으로 전체 배포 과정을 자동화할 수 있어 실수를 줄이고 효율성을 높일 수 있습니다. ### 3.2 배포 스크립트 예시 (`deploy.sh`) ```bash #!/bin/bash # 스크립트 실행 중 오류 발생 시 즉시 중단 set -e echo "배포 스크립트를 시작합니다..." # 1. 최신 버전의 소스 코드 받기 (main 브랜치 기준) echo "최신 소스코드를 가져옵니다..." git pull origin main # 2. Docker 이미지 빌드 (캐시 사용 안함) echo "Docker 이미지를 빌드합니다..." docker-compose build --no-cache # 3. 기존 컨테이너 중지 및 삭제 echo "기존 컨테이너를 중지하고 삭제합니다..." docker-compose down # 4. 새로운 컨테이너 실행 (백그라운드에서 실행) echo "새로운 컨테이너를 실행합니다..." docker-compose up -d # 5. 불필요한 Docker 이미지 정리 echo "불필요한 Docker 이미지를 정리합니다..." docker image prune -f echo "배포가 성공적으로 완료되었습니다!" ``` **스크립트 실행 권한 부여:** 스크립트를 실행하기 전에 실행 권한을 부여해야 합니다. ```bash chmod +x deploy.sh ``` **스크립트 실행:** ```bash ./deploy.sh ``` ## 4. 실전 예제: 간단한 Java Spring Boot 웹 애플리케이션 배포 아래는 Java 17과 내장 Tomcat을 사용하는 Spring Boot 웹 애플리케이션을 Docker, Docker Compose, 그리고 배포 스크립트로 배포하는 전체 예제입니다. **프로젝트 구조:** ``` -/my-java-project -| |-- pom.xml -| |-- src -| | |-- main -| | |-- java -| | |-- com/example/demo -| | |-- DemoApplication.java -| | |-- HelloController.java |-- Dockerfile |-- docker-compose.yml |-- deploy.sh ``` **파일 내용:** 1. **`pom.xml`** (Maven 빌드 설정 파일) ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 3.1.5 com.example demo 0.0.1-SNAPSHOT demo Demo project for Spring Boot 17 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin ``` 2. **`DemoApplication.java`** (Spring Boot 메인 애플리케이션) ```java package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` 3. **`HelloController.java`** (웹 요청을 처리하는 컨트롤러) ```java package com.example.demo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/") public String hello() { return "Hello, Docker with Java 17 & Spring Boot!"; } } ``` 4. **`Dockerfile`** (Java 애플리케이션을 위한 Multi-stage 빌드) ```dockerfile # 1단계: Maven을 사용하여 애플리케이션 빌드 (빌드 환경) FROM maven:3.8-eclipse-temurin-17 AS build WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # 2단계: 빌드된 애플리케이션을 실행 환경으로 복사 FROM eclipse-temurin:17-jre-focal WORKDIR /app COPY --from=build /app/target/*.jar app.jar # 컨테이너가 8080번 포트를 외부에 노출 EXPOSE 8080 # 컨테이너 시작 시 JAR 파일 실행 ENTRYPOINT ["java", "-jar", "app.jar"] ``` 5. **`docker-compose.yml`** ```yaml services: web: build: . container_name: my_java_app ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=development ``` 6. **`deploy.sh`** (스크립트 내용은 이전과 거의 동일) ```bash #!/bin/bash set -e echo "배포 스크립트를 시작합니다..." # git pull origin main # 실제 운영 시에는 주석 해제 docker-compose build --no-cache docker-compose down docker-compose up -d docker image prune -f echo "배포가 성공적으로 완료되었습니다!" ``` **실행 방법:** 1. 위 파일들을 하나의 디렉토리에 생성합니다. 2. 터미널에서 `deploy.sh` 파일에 실행 권한을 부여합니다 (`chmod +x deploy.sh`). 3. `./deploy.sh`를 실행하여 배포를 진행합니다. 4. 배포가 완료되면 웹 브라우저에서 `http://localhost:8080`에 접속하여 "Hello, Docker with Java 17 & Spring Boot!" 메시지를 확인합니다.