# [Docker Compose 실습] Compose 파일 1차 정리하기

이번 실습의 목표는 현재 Compose 파일을 "돌아가는 파일"에서 "읽고 관리할 수 있는 파일"로 바꾸는 것이다. 여기서는 앱 코드나 Dockerfile은 아직 고치지 않고, Compose 파일의 책임만 먼저 정리한다.

정리 기준은 단순하다. 각 서비스에는 그 서비스가 필요한 값만 넣는다.

1차 정리 방향

정리 항목처리
version: '3.8'제거
프로젝트 이름name: discodeit 추가
container_name제거 권장
db.env_file제거하고 POSTGRES_*만 명시
app.env_file제거하고 앱에 필요한 변수만 명시
DB 포트로컬 DB 클라이언트가 필요하면 유지
named volume유지
healthcheck유지

container_name은 꼭 필요한 경우가 아니면 제거하는 편이 낫다. Compose는 기본적으로 프로젝트명-서비스명-번호 형태로 컨테이너 이름을 만든다. 이 기본 네이밍을 쓰면 같은 Compose 파일을 다른 프로젝트 이름으로 여러 벌 실행하기 쉽다.

정리된 Compose 예시

아래 예시는 현재 프로젝트 기준의 1차 정리안이다. 실제 값은 .env에 두고, Compose 파일에서는 필요한 변수만 골라 각 서비스에 전달한다.

name: discodeit

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "${APP_PORT:-8081}:80"
    environment:
      SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/${POSTGRES_DB}
      SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER}
      SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
      SPRING_JPA_HIBERNATE_DDL_AUTO: ${SPRING_JPA_HIBERNATE_DDL_AUTO:-update}
      SPRING_JPA_SHOW_SQL: ${SPRING_JPA_SHOW_SQL:-false}
      AWS_S3_ACCESS_KEY: ${AWS_S3_ACCESS_KEY}
      AWS_S3_SECRET_KEY: ${AWS_S3_SECRET_KEY}
      AWS_S3_REGION: ${AWS_S3_REGION}
      AWS_S3_BUCKET: ${AWS_S3_BUCKET}
      AWS_S3_PRESIGNED_URL_EXPIRATION: ${AWS_S3_PRESIGNED_URL_EXPIRATION:-600}
      ADMIN_EMAIL: ${ADMIN_EMAIL}
      ADMIN_USERNAME: ${ADMIN_USERNAME}
      ADMIN_PASSWORD: ${ADMIN_PASSWORD}
      JWT_SECRET: ${JWT_SECRET}
      JVM_OPTS: ${JVM_OPTS:-}
    volumes:
      - app_storage:/app/storage
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    ports:
      - "${POSTGRES_HOST_PORT:-5432}:5432"
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro
    restart: unless-stopped

volumes:
  postgres_data:
  app_storage:

healthcheck.test에서 $${POSTGRES_USER}처럼 달러를 두 번 쓴 점이 중요하다. Compose가 파일을 읽는 시점에 값을 먼저 치환하지 않고, 컨테이너 안에서 실행될 때 환경 변수를 읽게 하려는 의도다. 단순히 ${POSTGRES_USER}를 써도 현재는 동작할 수 있지만, healthcheck 명령을 컨테이너 환경 기준으로 읽히게 하려면 이 방식이 더 명확하다.

왜 env_file을 빼는가

env_file은 편하지만 경계를 흐린다.

env_file:
  - .env

이 한 줄은 파일 안의 모든 변수를 해당 컨테이너에 넣는다. 작은 프로젝트에서는 편해 보이지만, 시간이 지나면 어떤 컨테이너가 어떤 비밀값을 알고 있는지 추적하기 어려워진다.

정리된 예시에서는 .env가 여전히 필요하다. 다만 .env는 Compose 파일의 ${...} 값을 채우는 저장소로 쓰고, 컨테이너에 들어가는 값은 environment에서 서비스별로 고른다.

APP_PORT와 POSTGRES_HOST_PORT를 추가하는 이유

현재 파일은 포트가 고정되어 있다.

ports:
  - "8081:80"
  - "5432:5432"

로컬에서 이미 5432를 쓰고 있으면 DB 컨테이너가 뜨지 않는다. 포트를 .env로 뺄 수 있게 하면 충돌을 피하기 쉽다.

APP_PORT=8081
POSTGRES_HOST_PORT=5433

이렇게 하면 앱은 localhost:8081, DB는 호스트에서 localhost:5433으로 접근한다. 컨테이너 내부의 DB 포트는 여전히 5432다. 앱의 JDBC URL은 db:5432 그대로 유지된다.

정리 후 확인

파일을 수정한 뒤에는 바로 실행하지 말고 먼저 구문과 해석 결과를 본다.

docker compose config

확인할 포인트는 다음과 같다.

  • version 경고가 사라졌는가.
  • db.environmentPOSTGRES_*만 있는가.
  • app.environment에 앱이 필요한 값만 있는가.
  • ports가 원하는 호스트 포트로 풀렸는가.
  • healthcheck가 유지되었는가.
config 출력은 계속 민감하다

env_file을 제거해도 environment에 secret을 넘기면 docker compose config에는 실제 값이 출력된다. 출력 공유 금지 원칙은 그대로 유지한다.

여기까지의 결과

이 단계가 끝나면 Compose 파일은 다음 상태가 된다.

  • 최신 Compose에서 불필요한 version이 없다.
  • 프로젝트 이름이 명시되어 리소스 이름이 예측 가능하다.
  • DB 컨테이너가 앱 secret을 받지 않는다.
  • 앱 컨테이너가 필요한 Spring, AWS, JWT, Admin 값만 받는다.
  • DB healthcheck와 named volume은 유지된다.

다음 실습에서는 앱 이미지와 Spring Boot 설정을 맞춘다. 특히 Dockerfile의 JAR 파일명 문제를 정리해야 한다.