내일배움캠프

[내일배움캠프] Docker와 Docker Compose

munsik22 2026. 5. 4. 13:01

📚 목차

    🧩 Docker

    Docker란?

    • Docker: 애플리케이션을 쉽게 만들고, 테스트하고, 배포할 수 있게 도와주는 소프트웨어 플랫폼
      • 애플리케이션을 컨테이너라는 가볍고 이식성 있는 패키지로 실행할 수 있음
      • Docker 이미지: 애플리케이션을 실행하는 데 필요한 모든 것을 포함 (코드, 런타임, 시스템 도구, 시스템 라이브러리 등)
    • 주요 특징
      • 컨테이너화: 애플리케이션과 필요한 모든 것을 하나의 패키지로 묶어 어디서든 실행 가능
      • 경량: Docker는 운영 체제의 커널을 공유하므로 가상 머신보다 훨씬 가볍고 빠르게 실행됨
      • 이식성: Docker 컨테이너는 어디서든 동일하게 실행됨
      • 확장성: Docker를 사용하면 여러 개의 컨테이너를 효율적으로 관리하고 쉽게 확장 가능함
    • 주요 키워드
      • 이미지: 애플리케이션과 모든 실행에 필요한 파일을 포함한 읽기 전용 템플릿
      • 컨테이너: 이미지를 실행하여 동작하는 애플리케이션 인스턴스
        • Docker 이미지를 실행한 상태
        • 격리된 공간에서 애플리케이션을 실행하며, 필요한 모든 의존성을 포함함
        • 하나의 시스템에서 여러 개의 컨테이너를 독립적으로 실행 가능
      • Dockerfile: 이미지를 생성하기 위한 명령어가 담긴 스크립트 파일
      • Docker Hub: 이미지를 저장하고 공유하는 중앙 저장소
      • 볼륨: 컨테이너 데이터를 지속적으로 저장하는 메커니즘
        • 컨테이너가 삭제되어도 볼륨에 저장된 데이터는 유지됨
        • 데이터를 컨테이너와 독립적으로 관리 가능
      • 네트워크: 컨테이너 간의 통신을 관리하는 방식
    • Docker 네트워크 종류
      • Bridge Network
        • 기본적으로 Docker가 컨테이너를 실행할 때 사용하는 네트워크
        • 동일한 브리지 네트워크에 연결된 컨테이너들은 서로 통신 가능함
        • 외부 네트워크와는 NAT을 통해 통신함 (NAT: 내부 네트워크의 여러 장치가 하나의 공용 IP 주소를 통해 외부 네트워크와 통신할 수 있도록 IP 주소를 변환하는 기술)
        • 일반적으로 단일 호스트에서 여러 컨테이너를 연결할 때 사용됨
          docker network create my-bridge-network
          docker run -d --name container1 --network my-bridge-network nginx
          docker run -d --name container2 --network my-bridge-network nginx
      • Host Network
        • 컨테이너가 호스트의 네트워크 스택을 직접 사용함
        • 네트워크 격리가 없기 때문에 성능상 이점이 있지만 보안 및 네트워크 충돌 위험이 있음
        • 일반적으로 성능이 중요한 애플리케이션에 사용됨
          docker run -d --network host nginx
      • Overlay Network
        • 여러 Docker 호스트에 걸쳐 있는 컨테이너를 연결할 때 사용됨
        • Swarm 모드나 Kubernetes 같은 오케스트레이션 도구와 함께 사용됨 (Swarm: Docker 컨테이너의 오케스트레이션과 클러스터링을 지원하여 여러 호스트에서 컨테이너를 관리하고 배포할 수 있는 기능)
        • 데이터 센터 또는 클라우드 환경에서 분산 시스템을 구축할 때 유용함

    Docker vs VM

    가상 머신(VM): 하이퍼바이저를 통해 물리적 하드웨어 위에 가상화된 운영 체제를 실행하는 기술
    하이퍼바이저: 여러 운영 체제를 동시에 실행할 수 있도록 물리적 하드웨어를 가상화하는 소프트웨어

    • Docker의 장점
      • 빠른 시작과 낮은 오버헤드
      • 높은 이식성과 확장성
    • Docker의 단점
      • 보안 격리가 VM보다 약함
      • 운영 체제 종속성 존재 (리눅스 친화적)
    • VM의 장점
      • 격리된 환경 제공
      • 다양한 운영 체제 실행 가능
    • VM의 단점
      • 오버헤드가 크고 느린 부팅 시간
      • 높은 리소스 소비

    Docker는 언제 사용하면 좋을까?

    • 일관된 개발 환경이 필요할 때: 모든 환경에서 동일한 컨테이너를 실행 가능함
    • 애플리케이션을 빠르게 배포하고 싶을 때: Docker 이미지를 빌드하고 컨테이너로 실행하면 필요한 모든 구성 요소가 포함되어 있어 별도의 설치 과정 없이 바로 실행 가능함
    • MSA를 도입할 때: 각 서비스가 독립적으로 배포되고 실행될 수 있어 여러 개의 컨테이너를 통해 다양한 서비스를 쉽게 관리 가능함
    • CI/CD 파이프라인을 구축할 때: 코드를 변경할 때마다 자동으로 빌드, 테스트, 배포할 수 있도록 설정 가능함
    • 리소스 효율성을 높이고 싶을 때: 운영 체제의 커널을 공유하므로 더 많은 애플리케이션을 동일한 하드웨어에서 실행 가능함
    • 애플리케이션 격리가 필요할 때: 각 컨테이너가 서로 격리되어 실행됨
    • 쉽게 스케일링하고 싶을 때: 필요한 만큼 컨테이너를 추가하여 수평 확장이 가능하며, 오케스트레이션 도구와 결합하여 자동 확장도 가능함
    • Kubernetes와 함께 사용하고자 할 때: 쿠버네티스는 다수의 Docker 컨테이너를 관리하고 자동 배포, 확장, 운영을 지원함

    Docker 명령어

    이미지 관련 명령어

    • 이미지 빌드: 현재 디렉토리의 Dockerfile을 기반으로 myapp이라는 이름의 이미지를 생성함
    docker build -t myapp:latest .
    • 이미지 가져오기
    docker pull postgres
    • 이미지 목록 보기
    docker images
    • 이미지 삭제
    docker rmi myapp:latest

     

    컨테이너 관련 명령어

    • 컨테이너 실행: -d → 백그라운드 실행 / -p 8080:80 → 호스트의 8080 포트를 컨테이너의 80 포트에 매핑
    docker run -d -p 8080:80 myapp:latest

    호스트번호는 중복 할당이 불가능하지만, 컨테이너 내부 포트는 중복 할당이 가능하다.

     

    • 컨테이너 내부 접속: -i → 컨테이너의 표준 입력(STDIN)을 열어 사용자 입력을 받을 수 있음 / -t → 가상 터미널 할당
    docker exec -it container_id /bin/shell
    • 실행 중인 컨테이너 목록 보기
    docker ps
    • 모든 컨테이너 목록 보기
    # 중지된 컨테이너 포함 모든 컨테이너 목록 표시
    docker ps -a
    
    # 마지막으로 실행된 컨테이너를 먼저 나열
    docker ps -al
    • 컨테이너 중지
    docker stop container_id
    • 컨테이너 시작
    docker start container_id
    • 컨테이너 삭제
    docker rm container_id

     

    Docker 네트워크 및 볼륨 관련

    • 네트워크 생성
    docker network create mynetwork
    • 네트워크 목록 보기
    docker network ls
    • 네트워크 삭제
    docker nework rm mynetwork
    • 볼륨 생성
    docker volume create myvolume
    • 볼륨 목록 보기
    docker volume ls
    • 볼륨 삭제
    docker volume rm myvolume

    실습: PostgreSQL 컨테이너 2개 실행하기

    • 이미지 받기
    docker pull postgres
    • 컨테이너 실행하기 (리눅스)
    docker run -d --name postgres-sample \
      -p 5433:5432 \
        -e POSTGRES_USER=admin1 \
        -e POSTGRES_PASSWORD=admin2 \
        -e PGDATA=/var/lib/postgresql/data/pgdata \
        -v ${로컬_바인딩_폴더}:/var/lib/postgresql/data:z \
        postgres
    • 컨테이너 실행하기 (윈도우)
    docker run -d --name postgres-sample `
      -p 5433:5432 `
      -e POSTGRES_USER=admin1 `
      -e POSTGRES_PASSWORD=admin2 `
      -e PGDATA=/var/lib/postgresql/data/pgdata `
      -v "${로컬_바인딩_폴더}:/var/lib/postgresql/data:z" `
      postgres

    🧩 Docker Compose

    Docker Compose란?

    • Docker Compose: 다중 컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구
    • docker-compose.yaml 파일 하나로 애플리케이션의 서비스, 네트워크, 볼륨 등을 정의할 수 있음
    • 20.10 버전부터 기본적으로 설치되며, docker compose로 실행 가능함

    docker-compose.yaml 파일 구조

    
    version: '3'
    services:
      web:
        image: nginx
        ports:
          - "8080:80"
      app:
        build: .
        ports:
          - "8081:8080"
        depends_on:
          - db
      db:
        image: postgres
        environment:
          POSTGRES_PASSWORD: example
    
    • version: Docker Compose 파일 버전
    • services: 애플리케이션의 각 서비스를 정의
      • web, app, db: 각 서비스의 이름
      • image: 서비스를 실행할 Docker 이미지
      • build: Dockerfile이 있는 디렉토리 경로를 지정해 이미지를 빌드함
      • ports: 호스트와 컨테이너 간의 포트를 매핑함
      • depends_on: 다른 서비스가 먼저 실행되어야 하는 순서를 지정함
      • environment: 컨테이너의 환경 변수를 설정함

    Docker Compose 명령어

    • docker compose up
      • YAML 파일에 정의된 서비스를 빌드하고 시작함
      • 백그러운드에서 실행하려면 -d 옵션을 추가함
    docker compose up -d
    docker compose -f /path/to/your/project/docker-compose.yml up
    • docker compose down: 실행중인 모든 서비스를 중지하고 컨테이너, 네트워크, 볼륨 등을 정리함

    Dockerfile 작성 예시

    FROM amazoncorretto:17
    COPY target/myapp.jar /app/myapp.jar
    WORKDIR /app
    ENTRYPOINT ["java", "-jar", "myapp.jar"]

    🧩 실습하기

    애플리케이션 생성

    • [B] BController
    @RestController
    public class BController {
        @GetMapping("hello")
        public String hello(){
            return "hello";
        }
    }
    • [A] AController
    @RestController
    @RequiredArgsConstructor
    public class AController {
        private final BServiceClient bServiceClient;
    
        @GetMapping("hi")
        public String hi(){
            String hello = bServiceClient.getHello();
            return "A: hi B: " + hello;
        }
    }
    • [A] BServiceClient
    @FeignClient(name = "service-b", url = "${service.b.url}")
    public interface BServiceClient {
        @GetMapping("/hello")
        public String getHello();
    }

    실행 결과

    Docker 사용 실습

    • application.properties 수정
      • 기존에는 service-a가 18080, service-b가 18081로 서로 다른 포트 번호를 사용했지만, Docker를 사용하면 서로 독립적인 환경이 되기 때문에 포트를 8080로 통일해도 상관 없다.
      • 같은 Docker 네트워크 내에서는 Docker 컨테이너의 이름으로 호출할 수 있다.
    spring.application.name=service-a
    
    server.port=8080
    
    service.b.url=http://service-b:8080
    spring.application.name=service-b
    
    server.port=8080
    • Dockerfile 생성
    FROM amazoncorretto:17
    
    VOLUME /tmp
    
    ARG JAR_FILE=build/libs/*.jar
    
    COPY ${JAR_FILE} app.jar
    
    ENTRYPOINT ["java","-jar","/app.jar"]
    • Docker 네트워크 생성: 도커끼리 컨테이너 이름으로 호출하기 위해선 기본 브리지 네트워크가 아닌 사용자 정의 네트워크에서 진행해야 한다.
    docker network create my-network
    • 프로젝트 빌드 및 도커 이미지 생성
    ./gradlew clean bootJar
     docker build -t img-service-a .
    ./gradlew clean bootJar
     docker build -t img-service-a .
    • 컨테이너 생성
    docker run -d --name service-a `
     --network my-network `
     -p 18080:8080 `
     -e SERVICE_B_URL=http://service-b:8080 `
    img-service-a
    docker run -d --name service-b `
     --network my-network `
     -p 18081:8080 `
     img-service-b

    실행 결과

     

    Docker Compose 사용 실습

    • 컨테이너 삭제
      • 일반적으로는 아래처럼 강제 삭제가 아니라 컨테이너를 정지시킨 후에 삭제를 진행해야 한다.
      • 필자는 Docker Desktop 상에서 삭제를 진행했다. (위 이미지에서 빨간색 휴지통 아이콘)
    docker rm -f 서비스_A_컨테이너_아이디
    docker rm -f 서비스_B_컨테이너_아이디
    • docker-compose.yml 파일 생성
    version: '3.8'
    
    services:
      service-a:
        image: img-service-a
        ports:
          - "18080:8080"
        environment:
          - SERVICE_B_URL=http://service-b:8080
        depends_on:
          - service-b
    
      service-b:
        image: img-service-b
        ports:
          - "18081:8080"
    
    networks:
      default:
        driver: bridge

    service-a에만 compose 파일을 생성했지만, 서비스 A, B 둘다 실행된다.
    실행 결과

    • Docker Compose를 사용하여 서비스를 실행하면 Docker Compose는 기본적으로 새 브리지 네트워크를 생성하여 각 서비스 컨테이너를 그 네트워크에 연결한다.