docker-compose - build context 활용하기

StrayCat·2026년 3월 14일

강의에서는 각 서비스 폴더에서 직접 docker build를 실행하고, 이후 docker compose up으로 컨테이너를 띄우는 방식을 사용했다. 그런데 실제로 써보니 불편한 부분이 있었다.

서비스가 두 개뿐인데도 아래처럼 명령어를 여러 번 실행해야 했다.

# service-b 폴더에서
docker build -t img-service-b .

# service-a 폴더에서
docker build -t img-service-a .

# 상위 폴더에서
docker compose up -d

"이미지 빌드와 컨테이너 실행을 한 번에 할 수 없을까?" 라는 생각에서 찾아보니, docker-compose.ymlbuild 옵션을 추가하면 가능했다.


핵심 아이디어: build.context

기존 docker-compose.yml은 이미 만들어진 이미지 이름만 참조했다.

# 기존 방식: 미리 빌드된 이미지를 그냥 가져다 씀
services:
  service-a:
    image: img-service-a

여기에 build.context를 추가하면 이미지가 없을 때(또는 --build 옵션 사용 시) 자동으로 빌드까지 처리해준다.

services:
  service-a:
    build:
      context: ./com.service.a  # 이 경로의 Dockerfile을 사용해서 이미지를 빌드한다
    image: img-service-a        # 빌드 후 이 이름으로 태깅

context는 Dockerfile이 있는 디렉터리의 경로다. docker-compose.yml 파일 위치를 기준으로 상대 경로로 지정한다.


전체 docker-compose.yml

프로젝트 구조는 아래와 같다고 가정한다.

my-project/                 ← docker-compose.yml 위치
├── com.service.a/
│   ├── Dockerfile
│   ├── build/libs/*.jar
│   └── ...
└── com.service.b/
    ├── Dockerfile
    ├── build/libs/*.jar
    └── ...
# version 필드는 최신 Docker Compose에서 사용하지 않아도 된다 (아래 참고)
version: '3.8'

services:
  service-a:
    build:
      # docker-compose.yml 기준으로 com.service.a 폴더의 Dockerfile을 사용해 빌드
      context: ./com.service.a
    # 빌드된 이미지에 붙일 이름 (docker images 명령어로 확인 가능)
    image: img-service-a
    ports:
      - "18080:8080"
    environment:
      # 컨테이너 이름(service-b)으로 서비스 간 통신
      - SERVICE_B_URL=http://service-b:8080
    depends_on:
      # service-b 컨테이너가 먼저 시작되도록 순서 지정
      - service-b

  service-b:
    build:
      context: ./com.service.b
    image: img-service-b
    ports:
      - "18081:8080"

networks:
  default:
    # Docker Compose가 자동으로 브리지 네트워크를 생성
    # 같은 네트워크 안에서 서비스 이름으로 서로 호출 가능
    driver: bridge

Dockerfile

build.context가 참조하는 각 서비스 폴더 안에는 아래와 같은 Dockerfile이 있다.

# 베이스 이미지: Eclipse Temurin 17 JDK (Ubuntu Jammy 기반)
# openjdk:17-jdk-slim은 2022년 7월 이후 공식 업데이트가 중단된 deprecated 이미지다
# 현재(2026년 기준) Java 컨테이너 베이스 이미지 표준은 Eclipse Temurin이다
FROM eclipse-temurin:17-jdk-jammy

# Spring Boot가 내부적으로 사용하는 임시 파일 경로를 볼륨으로 지정
# 컨테이너가 재시작되어도 /tmp 데이터가 호스트에 유지될 수 있게 한다
# 필수는 아니지만 Spring Boot 공식 가이드에서 권장하는 패턴이다
VOLUME /tmp

# 빌드 시점에 주입되는 인수(ARG): JAR 파일 경로를 변수로 관리
# *.jar 와일드카드를 사용하므로 버전명이 바뀌어도 Dockerfile 수정 없이 동작한다
ARG JAR_FILE=build/libs/*.jar

# 호스트의 JAR 파일을 컨테이너 내부에 app.jar라는 이름으로 복사
# ${JAR_FILE}은 위에서 선언한 ARG 값을 참조한다
COPY ${JAR_FILE} app.jar

# 컨테이너가 시작될 때 실행할 명령어
# CMD와 달리 ENTRYPOINT는 docker run 시 인수를 추가해도 이 명령어가 덮어써지지 않는다
# exec 형식(JSON 배열)을 사용하면 shell을 거치지 않아 시그널(SIGTERM 등) 처리가 안정적이다
ENTRYPOINT ["java", "-jar", "/app.jar"]

베이스 이미지: openjdk → eclipse-temurin으로

openjdk:17-jdk-slim을 그대로 쓰면 실행은 되지만, Docker Hub에서 아래와 같은 안내를 확인할 수 있다.

"Deprecated: Users should use eclipse-temurin, amazoncorretto, or other maintained images."

Eclipse Temurin은 Adoptium 프로젝트(구 AdoptOpenJDK)에서 관리하는 OpenJDK 공식 빌드다. jammy는 Ubuntu 22.04 LTS(코드명 Jammy Jellyfish) 기반이라는 의미로, 안정성과 보안 패치 측면에서 신뢰할 수 있는 선택이다.

이미지상태비고
openjdk:17-jdk-slimDeprecated2022년 7월 이후 업데이트 없음
eclipse-temurin:17-jdk-jammy유지보수 중현재 권장
eclipse-temurin:17-jre-jammy유지보수 중실행만 필요하다면 JRE가 이미지 크기 면에서 유리

ENTRYPOINT vs CMD

둘 다 컨테이너 시작 시 실행할 명령어를 지정한다는 점은 같다. 차이는 다음과 같다.

ENTRYPOINTCMD
docker run 시 인수 추가기존 명령어 유지, 인수만 추가됨전체 명령어가 덮어써짐
주 용도항상 실행되어야 하는 고정 명령어기본값 제공 (재정의 가능)

Spring Boot JAR 실행처럼 "무조건 이 명령어로 시작해야 한다"는 경우엔 ENTRYPOINT가 적합하다.


실행 방법

JAR 빌드는 여전히 수동으로 해야 한다. Gradle 빌드까지 Compose에 포함시키려면 멀티스테이지 Dockerfile이 필요한데, 지금 단계에서는 아래 방법으로 충분하다.

# 1. 각 서비스 JAR 빌드 (순서 무관)
cd com.service.a && ./gradlew clean bootJar
cd ../com.service.b && ./gradlew clean bootJar

# 2. 상위 폴더로 이동 후 이미지 빌드 + 컨테이너 실행 한 번에
cd ..
docker compose up --build -d

--build 옵션이 핵심이다. 이 옵션을 붙이면 기존 이미지가 있더라도 매번 새로 빌드한 뒤 컨테이너를 실행한다. 코드를 수정하고 재배포할 때 유용하다.

--build 없이 docker compose up -d만 실행하면, 이미 빌드된 이미지가 있을 경우 그것을 그대로 사용한다. 코드를 바꿨는데 반영이 안 된다면 이 옵션을 빠뜨렸을 가능성이 높다.



실제로 실행했을 때 로그 해석

직접 실행해보니 아래와 같은 로그가 출력되었다. 각 단계가 무엇을 의미하는지 살펴보면 이렇다.

1단계 — Dockerfile 로드

[service-a] load build definition from Dockerfile
[service-b] load build definition from Dockerfile

context 경로에서 Dockerfile을 읽어들인다.

2단계 — 베이스 이미지 준비

[service-b] load metadata for eclipse-temurin:17-jdk-jammy
#8 CACHED

CACHED라고 표시된 것은 이미 로컬에 해당 이미지가 있어서 Docker Hub에서 다시 받지 않은 것이다. 처음 실행할 때는 다운로드가 일어난다.

3단계 — JAR 파일 복사

[service-a 2/2] COPY build/libs/*.jar app.jar
[service-b 2/2] COPY build/libs/*.jar app.jar

각 서비스의 build/libs/ 디렉터리에서 JAR 파일을 이미지 내부로 복사한다. Dockerfile의 COPY ${JAR_FILE} app.jar 구문이 실행되는 단계다.

4단계 — 이미지 생성 및 태깅

naming to docker.io/library/img-service-a:latest
naming to docker.io/library/img-service-b:latest

image: img-service-a에 지정한 이름으로 빌드된 이미지가 태깅된다.

5단계 — 네트워크 및 컨테이너 생성

Network week-_default       Created
Container week--service-b-1 Created
Container week--service-a-1 Created

Docker Compose가 자동으로 브리지 네트워크를 생성한다. 네트워크 이름은 docker-compose.yml이 위치한 디렉터리 이름 + _default 형식으로 자동 결정된다. 여기서는 폴더 이름이 week-프로젝트관리심화였기 때문에 week-_default가 된 것이다.

컨테이너도 depends_on 설정에 따라 service-b가 먼저, service-a가 나중에 생성된다.

실행 확인

CONTAINER ID   IMAGE           PORTS
e4e0cc598afc   img-service-a   0.0.0.0:18080->8080/tcp
9c4a9f004eeb   img-service-b   0.0.0.0:18081->8080/tcp

두 컨테이너가 모두 Up 상태로 올라와 있으면 성공이다.


기존 방식과 비교

기존 방식build context 방식
이미지 빌드각 서비스 폴더에서 docker build 개별 실행docker compose up --build로 한 번에 처리
관리 위치폴더마다 분산docker-compose.yml 하나로 통합
코드 변경 후 재배포빌드 → 태깅 → compose up 순서 직접 관리--build 옵션 하나로 처리

서비스가 늘어날수록 이 차이가 더 크게 느껴진다.


정리

build.context를 추가하는 것은 작은 변경이지만, 워크플로우(workflow, 작업 흐름)를 상당히 단순하게 만들어준다. 코드를 수정하고 나서 매번 여러 폴더를 돌아다니며 docker build를 실행하는 것보다, 상위 폴더에서 docker compose up --build -d 한 번으로 끝나는 쪽이 훨씬 효율적이다.

강의에서는 Docker의 각 단계를 분리해서 이해시키는 목적으로 순서대로 진행했지만, 실제로 개발하면서는 이처럼 더 편한 방법을 찾아 적용해보는 과정 자체가 좋은 학습이 된다.

profile
알면 좋은 것보단 잊어버리기 싫은 것들을 기록합니다.

0개의 댓글