Docker Compose를 활용하여 PostgreSQL, Redis, Kafka가 연동된 로컬 개발 환경을 효율적으로 구축하는 방법을 상세히 안내합니다. 각 서비스의 장단점과 설정 팁을 비교 분석합니다.
개발 프로젝트를 시작할 때 가장 먼저 마주하는 난관 중 하나는 바로 로컬 개발 환경 구축입니다. 특히 복잡한 마이크로서비스 아키텍처나 다양한 데이터 스토어, 메시지 큐 시스템이 필요한 프로젝트의 경우, 개발 환경 설정에만 상당한 시간을 소요하게 됩니다. PostgreSQL로 데이터를 관리하고, Redis로 캐싱 및 세션 관리를 하며, Kafka로 비동기 메시징을 처리해야 하는 상황을 상상해 보세요. 각각의 서비스를 개별적으로 설치하고 설정하는 것은 비효율적이며, 잠재적인 충돌 문제를 야기할 수 있습니다.
이러한 문제에 대한 강력한 해결책이 바로 Docker Compose입니다. Docker Compose는 여러 개의 Docker 컨테이너를 하나의 서비스 스택으로 정의하고 실행할 수 있도록 돕는 도구입니다. 이 글에서는 Docker Compose를 활용하여 PostgreSQL, Redis, Kafka를 효율적으로 연동하는 로컬 개발 환경을 구축하는 방법을 상세히 살펴보고자 합니다. 각 서비스의 역할과 설정의 핵심을 비교 분석하며, 안정적이고 일관된 개발 환경을 만드는 데 필요한 모든 정보를 제공하겠습니다.
📑 목차
- Docker Compose와 로컬 개발 환경의 중요성
- 전통적인 환경 구축 방식의 한계
- Docker Compose를 통한 혁신
- Docker Compose 기본 설정 이해하기
- docker-compose.yml 파일의 핵심 구조
- 기본 docker-compose.yml 예시
- PostgreSQL 연동 상세 가이드
- PostgreSQL 서비스 정의
- Redis 연동 상세 가이드
- Redis 서비스 정의
- Kafka 연동 상세 가이드 (with Zookeeper)
- Zookeeper 및 Kafka 서비스 정의
- 모든 서비스를 통합한 docker-compose.yml 완성하기
- 서비스 실행 및 관리
- 로컬 개발 환경 효율성 극대화 팁
- 1. 볼륨 마운트를 통한 코드 실시간 반영
- 2. 네트워크 별칭 활용
- 3. 환경 변수 관리
- 4. 컨테이너 리소스 제한
- 5. Docker Desktop 자원 설정
- 결론: 안정적이고 효율적인 개발 환경의 시작
Image by Olga_Fil on Pixabay
Docker Compose와 로컬 개발 환경의 중요성
개발 환경 구축은 프로젝트 성공의 기반입니다. 전통적인 방식과 Docker Compose를 활용하는 방식은 개발 워크플로우에 큰 차이를 가져옵니다. 각각의 장단점을 살펴보면, Docker Compose가 왜 현대 개발 환경에서 필수적인 도구로 자리매김했는지 명확하게 이해할 수 있습니다.
전통적인 환경 구축 방식의 한계
과거에는 개발에 필요한 데이터베이스, 캐시 서버, 메시지 브로커 등을 로컬 운영체제에 직접 설치하는 것이 일반적이었습니다. 이 방식은 다음과 같은 문제점을 안고 있었습니다:
- 의존성 충돌: 여러 프로젝트가 다른 버전의 동일한 서비스를 필요로 할 때 충돌이 발생하기 쉽습니다. 예를 들어, 한 프로젝트는 PostgreSQL 12를, 다른 프로젝트는 PostgreSQL 14를 필요로 하는 경우, 로컬 환경에서 동시에 관리하기가 어렵습니다.
- 환경 불일치: 개발자마다, 또는 개발 환경과 운영 환경 간에 설정이 달라져 '내 컴퓨터에서는 되는데...'와 같은 문제가 빈번하게 발생합니다.
- 초기 설정 시간: 새로운 개발자가 프로젝트에 합류하거나 새로운 환경을 설정할 때, 각 서비스를 수동으로 설치하고 구성하는 데 많은 시간이 소요됩니다.
- 자원 관리의 어려움: 사용하지 않는 서비스라도 백그라운드에서 계속 실행되어 시스템 자원을 점유할 수 있습니다.
Docker Compose를 통한 혁신
Docker Compose는 이러한 문제들을 해결하며, 개발 환경을 컨테이너 기반으로 표준화합니다. 이는 다음과 같은 이점을 제공합니다:
- 일관성: `docker-compose.yml` 파일 하나로 모든 팀원이 동일한 개발 환경을 구축할 수 있습니다. 운영 환경과의 일관성도 유지하기 용이합니다.
- 격리: 각 서비스는 독립적인 컨테이너에서 실행되므로, 서로의 의존성에 영향을 주지 않습니다. 버전 충돌 문제를 효과적으로 해결합니다.
- 간편한 설정 및 배포: 단일 명령(`docker compose up`)으로 모든 서비스를 시작하고 중지할 수 있습니다. 새로운 개발자의 온보딩 시간을 크게 단축시킵니다.
- 이식성: `docker-compose.yml` 파일만 있다면 어떤 Docker 환경에서든 동일하게 작동합니다.
전통적인 방식과 Docker Compose 방식의 주요 차이점을 다음 표에서 비교해 볼 수 있습니다.
| 특징 | 전통적인 로컬 설치 | Docker Compose 활용 |
|---|---|---|
| 설정 복잡성 | 각 서비스 개별 설치 및 설정 필요, OS 의존적 | docker-compose.yml 파일 하나로 정의, OS 독립적 |
| 환경 일관성 | 개발자 및 환경별 차이 발생 가능성 높음 | docker-compose.yml 기반으로 높은 일관성 유지 |
| 의존성 관리 | 버전 충돌 위험, 복잡한 관리 | 컨테이너 기반 격리로 충돌 없이 여러 버전 동시 사용 가능 |
| 자원 활용 | 백그라운드 서비스 상시 실행으로 자원 소모 | 필요할 때만 서비스 실행, 유연한 자원 관리 |
| 초기 설정 시간 | 상당한 시간 소요 | 매우 짧음 (docker compose up 한 번으로 끝) |
Docker Compose 기본 설정 이해하기
Docker Compose는 docker-compose.yml이라는 YAML 파일을 사용하여 다중 컨테이너 애플리케이션의 서비스, 네트워크, 볼륨 등을 정의합니다. 이 파일은 Docker Compose가 애플리케이션을 어떻게 빌드하고 실행할지를 지시하는 청사진 역할을 합니다.
docker-compose.yml 파일의 핵심 구조
docker-compose.yml 파일은 크게 세 가지 최상위 키를 가집니다: version, services, networks, volumes. 이 중 services가 가장 중요하며, 우리가 구축하려는 PostgreSQL, Redis, Kafka 각각이 하나의 서비스로 정의됩니다.
version: Compose 파일의 버전을 지정합니다. 최신 기능과 문법을 사용하려면 최신 버전을 사용하는 것이 좋습니다. (예: '3.8')services: 애플리케이션을 구성하는 각 컨테이너 서비스를 정의합니다. 각 서비스는 사용할 Docker 이미지, 포트 매핑, 환경 변수, 볼륨 등을 포함합니다.networks: 서비스 간 통신을 위한 네트워크를 정의합니다. 명시적으로 정의하지 않으면 기본 네트워크가 생성되지만, 명시적으로 정의하여 네트워크 격리 및 관리를 용이하게 할 수 있습니다.volumes: 컨테이너의 데이터를 영구적으로 저장하기 위한 볼륨을 정의합니다. 컨테이너가 삭제되어도 데이터가 유지될 수 있도록 합니다.
기본 docker-compose.yml 예시
가장 기본적인 형태의 docker-compose.yml 파일은 다음과 같습니다.
version: '3.8'
services:
# 여기에 각 서비스 (PostgreSQL, Redis, Kafka 등) 정의
my_app:
image: my_app_image:latest
ports:
- "8000:8000"
environment:
MY_ENV_VAR: "value"
depends_on:
- db
networks:
- app_network
db:
image: postgres:14-alpine
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data
networks:
- app_network
networks:
app_network:
driver: bridge
volumes:
db_data:
위 예시에서 볼 수 있듯이, 각 서비스는 독립적으로 정의되며, depends_on을 통해 서비스 간의 의존성을 명시할 수 있습니다. networks와 volumes는 서비스 전반에 걸쳐 사용될 공통 리소스를 정의하는 데 활용됩니다.
PostgreSQL 연동 상세 가이드
PostgreSQL은 강력하고 안정적인 오픈 소스 관계형 데이터베이스 시스템입니다. 데이터 무결성, 확장성, 다양한 데이터 타입 지원 등 여러 장점으로 인해 많은 엔터프라이즈 애플리케이션의 핵심 데이터 스토어로 활용됩니다. 로컬 개발 환경에서 PostgreSQL을 컨테이너로 실행하면, 호스트 시스템에 직접 설치할 필요 없이 격리된 환경에서 데이터베이스를 관리할 수 있습니다.
PostgreSQL 서비스 정의
docker-compose.yml 파일에 PostgreSQL 서비스를 추가하는 방법은 다음과 같습니다. 우리는 공식 Docker 이미지인 postgres를 활용할 것입니다.
services:
# ... (다른 서비스들)
postgresql:
image: postgres:14-alpine # 가벼운 alpine 버전을 권장합니다.
ports:
- "5432:5432" # 로컬 5432 포트를 컨테이너 5432 포트에 매핑
environment:
POSTGRES_DB: mydatabase # 데이터베이스 이름
POSTGRES_USER: user # 데이터베이스 사용자 이름
POSTGRES_PASSWORD: password # 데이터베이스 사용자 비밀번호
volumes:
- postgres_data:/var/lib/postgresql/data # 데이터 영속성을 위한 볼륨 마운트
networks:
- app_network
restart: always # 컨테이너 종료 시 항상 재시작
volumes:
postgres_data: # PostgreSQL 데이터 저장을 위한 볼륨 정의
각 설정의 의미는 다음과 같습니다:
image: postgres:14-alpine: PostgreSQL 14 버전의 Alpine Linux 기반 이미지를 사용합니다. Alpine 버전은 더 가볍고 보안성이 좋습니다.ports: "5432:5432": 호스트 머신의 5432 포트를 컨테이너의 5432 포트(PostgreSQL 기본 포트)에 연결합니다. 이를 통해 로컬 애플리케이션이나 SQL 클라이언트에서localhost:5432로 PostgreSQL에 접속할 수 있습니다.environment: PostgreSQL 컨테이너의 동작을 설정하는 환경 변수들입니다.POSTGRES_DB: 초기화 시 생성될 데이터베이스의 이름을 지정합니다.POSTGRES_USER: 데이터베이스 관리자 계정의 사용자 이름을 지정합니다.POSTGRES_PASSWORD: 데이터베이스 관리자 계정의 비밀번호를 지정합니다.
volumes: - postgres_data:/var/lib/postgresql/data: 데이터 영속성을 보장하기 위해postgres_data라는 Docker 볼륨을 컨테이너 내부의 PostgreSQL 데이터 디렉토리(/var/lib/postgresql/data)에 마운트합니다. 컨테이너가 삭제되더라도 데이터는 볼륨에 남아있으므로, 다음에 컨테이너를 다시 시작할 때 기존 데이터를 그대로 사용할 수 있습니다.networks: - app_network: 서비스 간 통신을 위한 사용자 정의 네트워크에 연결합니다.restart: always: 컨테이너가 어떤 이유로든 종료되면 항상 다시 시작하도록 설정합니다. 개발 중 컨테이너가 예기치 않게 종료되는 상황에 대비할 수 있습니다.
이 설정을 통해 PostgreSQL 데이터베이스는 격리된 컨테이너에서 안정적으로 실행되며, 개발자는 복잡한 설치 과정 없이 즉시 데이터베이스를 활용할 수 있습니다.
Redis 연동 상세 가이드
Redis는 인메모리 데이터 구조 스토어로, 주로 캐싱, 세션 관리, 메시지 브로커, 리더보드 등 다양한 용도로 사용됩니다. 매우 빠른 응답 속도를 자랑하며, 복잡한 데이터 구조(스트링, 해시, 리스트, 셋, 정렬된 셋)를 지원하여 개발 생산성을 높여줍니다. 로컬 개발 환경에서 Redis를 Docker 컨테이너로 실행하면, 애플리케이션의 성능을 테스트하고 실시간 데이터 처리를 시뮬레이션하는 데 매우 유용합니다.
Redis 서비스 정의
docker-compose.yml 파일에 Redis 서비스를 추가하는 방법은 다음과 같습니다. 우리는 공식 Docker 이미지인 redis를 사용할 것입니다.
services:
# ... (다른 서비스들)
redis:
image: redis:7-alpine # 가벼운 alpine 버전을 권장합니다.
ports:
- "6379:6379" # 로컬 6379 포트를 컨테이너 6379 포트에 매핑
command: redis-server --appendonly yes # AOF(Append Only File) 영속성 활성화
volumes:
- redis_data:/data # 데이터 영속성을 위한 볼륨 마운트 (AOF, RDB 파일 저장)
networks:
- app_network
restart: always # 컨테이너 종료 시 항상 재시작
volumes:
redis_data: # Redis 데이터 저장을 위한 볼륨 정의
각 설정의 의미는 다음과 같습니다:
image: redis:7-alpine: Redis 7 버전의 Alpine Linux 기반 이미지를 사용합니다. 가볍고 효율적입니다.ports: "6379:6379": 호스트 머신의 6379 포트를 컨테이너의 6379 포트(Redis 기본 포트)에 연결합니다. 로컬 애플리케이션에서localhost:6379로 Redis에 접속할 수 있습니다.command: redis-server --appendonly yes: Redis 서버를 시작할 때--appendonly yes옵션을 추가하여 AOF(Append Only File) 영속성을 활성화합니다. 이는 Redis가 모든 쓰기 작업을 파일에 기록하여 데이터 손실을 최소화하는 방법입니다. 개발 환경에서는 데이터 손실이 치명적이지 않을 수 있지만, 실제 운영 환경과 유사하게 구성하여 테스트하는 것이 좋습니다.volumes: - redis_data:/data: Redis 데이터를 영구적으로 저장하기 위해redis_data볼륨을 컨테이너 내부의 데이터 디렉토리(/data)에 마운트합니다. AOF 파일이나 RDB(Redis Database) 스냅샷 파일이 이곳에 저장됩니다.networks: - app_network: 서비스 간 통신을 위한 사용자 정의 네트워크에 연결합니다.restart: always: 컨테이너 종료 시 항상 재시작합니다.
Redis 컨테이너를 통해 개발 환경에서 캐싱, 메시지 큐 등 다양한 Redis 기능을 손쉽게 활용하고 테스트할 수 있습니다.
Image by Boskampi on Pixabay
Kafka 연동 상세 가이드 (with Zookeeper)
Apache Kafka는 고성능의 분산 스트리밍 플랫폼으로, 대용량 실시간 데이터 스트림을 처리하는 데 특화되어 있습니다. 로그 수집, 실시간 분석, 이벤트 소싱, 메시지 큐 등 다양한 애플리케이션에서 핵심적인 역할을 수행합니다. Kafka는 독립적으로 작동하지 않고, Zookeeper와 함께 동작하여 클러스터 상태 관리, 브로커 및 토픽 정보 저장 등을 수행합니다. 로컬 개발 환경에서 Kafka를 구축하는 것은 실시간 데이터 처리 파이프라인을 개발하고 테스트하는 데 필수적입니다.
Zookeeper 및 Kafka 서비스 정의
Kafka를 사용하려면 먼저 Zookeeper가 필요합니다. docker-compose.yml 파일에 Zookeeper와 Kafka 서비스를 함께 추가하는 방법은 다음과 같습니다. 우리는 wurstmeister/zookeeper와 wurstmeister/kafka 이미지를 사용할 것입니다. 이 이미지는 개발 환경에서 쉽게 Kafka를 구축할 수 있도록 잘 구성되어 있습니다.
services:
# ... (다른 서비스들)
zookeeper:
image: wurstmeister/zookeeper
ports:
- "2181:2181" # 로컬 2181 포트를 컨테이너 2181 포트에 매핑
networks:
- app_network
restart: always
kafka:
image: wurstmeister/kafka
ports:
- "9092:9092" # 로컬 9092 포트를 컨테이너 9092 포트에 매핑
environment:
KAFKA_ADVERTISED_HOST_NAME: kafka # Kafka 브로커의 접근 가능한 호스트 이름 (컨테이너 내부 네트워크 이름)
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 # Zookeeper 연결 정보 (서비스 이름:포트)
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092 # Kafka가 모든 네트워크 인터페이스에서 9092 포트로 수신
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 # 외부에서 접근할 때 사용할 주소
KAFKA_CREATE_TOPICS: "my_topic:1:1" # 초기 토픽 생성 (토픽명:파티션수:복제수)
volumes:
- kafka_data:/var/lib/kafka/data # 데이터 영속성을 위한 볼륨 마운트
depends_on:
- zookeeper # Kafka는 Zookeeper에 의존
networks:
- app_network
restart: always
volumes:
kafka_data: # Kafka 로그 및 데이터 저장을 위한 볼륨 정의
각 설정의 의미는 다음과 같습니다:
Zookeeper 서비스 설정
image: wurstmeister/zookeeper: Zookeeper 컨테이너 이미지를 사용합니다.ports: "2181:2181": 호스트 머신의 2181 포트를 컨테이너의 2181 포트(Zookeeper 기본 포트)에 매핑합니다.networks: - app_network: 사용자 정의 네트워크에 연결합니다.
Kafka 서비스 설정
image: wurstmeister/kafka: Kafka 컨테이너 이미지를 사용합니다.ports: "9092:9092": 호스트 머신의 9092 포트를 컨테이너의 9092 포트(Kafka 기본 포트)에 매핑합니다.environment: Kafka 컨테이너의 핵심 설정입니다.KAFKA_ADVERTISED_HOST_NAME: kafka: Kafka 브로커가 자신을 다른 서비스에 알릴 때 사용하는 호스트 이름입니다.app_network내에서 Kafka 서비스는kafka라는 이름으로 접근 가능합니다.KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181: Kafka가 연결할 Zookeeper 클러스터의 주소입니다. Docker Compose 내부에서는 서비스 이름으로 다른 컨테이너에 접근할 수 있으므로,zookeeper:2181로 지정합니다.KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092: Kafka가 수신 대기할 리스너를 설정합니다.0.0.0.0은 모든 네트워크 인터페이스를 의미합니다.KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092: 매우 중요한 설정으로, 외부(호스트 머신 또는 다른 로컬 애플리케이션)에서 Kafka에 접근할 때 사용되는 주소입니다.localhost:9092로 설정해야 호스트 머신의 애플리케이션이 Kafka 브로커에 정상적으로 연결할 수 있습니다.KAFKA_CREATE_TOPICS: "my_topic:1:1": 컨테이너 시작 시 자동으로my_topic이라는 토픽을 생성합니다. (파티션 수 1, 복제 수 1).
volumes: - kafka_data:/var/lib/kafka/data: Kafka 로그 및 데이터를 영구적으로 저장하기 위해kafka_data볼륨을 마운트합니다.depends_on: - zookeeper: Kafka 서비스는 Zookeeper 서비스가 먼저 시작되어야 하므로 의존성을 명시합니다.networks: - app_network: 사용자 정의 네트워크에 연결합니다.
이 설정을 통해 분산 메시지 큐 시스템인 Kafka를 로컬 개발 환경에서 효율적으로 실행하고, 실시간 데이터 스트리밍 애플리케이션을 개발할 수 있습니다.
모든 서비스를 통합한 docker-compose.yml 완성하기
이제 위에서 설명한 PostgreSQL, Redis, Kafka(와 Zookeeper) 서비스를 모두 통합한 완전한 docker-compose.yml 파일을 살펴보겠습니다. 이 파일은 여러분의 로컬 개발 환경의 핵심 구성 요소가 될 것입니다.
version: '3.8'
services:
# PostgreSQL 데이터베이스 서비스
postgresql:
image: postgres:14-alpine
ports:
- "5432:5432"
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app_network
restart: always
# Redis 캐시 및 메시지 브로커 서비스
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- app_network
restart: always
# Zookeeper 서비스 (Kafka의 핵심 의존성)
zookeeper:
image: wurstmeister/zookeeper
ports:
- "2181:2181"
networks:
- app_network
restart: always
# Kafka 메시지 스트리밍 서비스
kafka:
image: wurstmeister/kafka
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: kafka
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_CREATE_TOPICS: "my_topic:1:1"
volumes:
- kafka_data:/var/lib/kafka/data
depends_on:
- zookeeper
networks:
- app_network
restart: always
# (선택 사항) Kafka UI 또는 관리 도구 (예: Kafdrop)
kafdrop:
image: obsidiandynamics/kafdrop
ports:
- "9000:9000" # 로컬 9000 포트를 Kafdrop의 9000 포트에 매핑
environment:
KAFKA_BROKERCONNECT: kafka:9092 # Kafdrop가 연결할 Kafka 브로커 주소 (서비스 이름:포트)
depends_on:
- kafka # Kafdrop는 Kafka에 의존
networks:
- app_network
restart: always
# 사용자 정의 네트워크 정의
networks:
app_network:
driver: bridge # 기본 bridge 네트워크 사용
# 데이터 영속성을 위한 볼륨 정의
volumes:
postgres_data:
redis_data:
kafka_data:
위 docker-compose.yml 파일은 PostgreSQL, Redis, Zookeeper, Kafka 그리고 Kafdrop(선택 사항인 Kafka UI 도구)까지 총 5개의 서비스를 정의하고 있습니다. 각 서비스는 app_network라는 사용자 정의 네트워크를 통해 서로 통신합니다. 데이터 영속성을 위해 각각의 볼륨도 명확하게 정의되어 있습니다.
서비스 실행 및 관리
이 docker-compose.yml 파일이 있는 디렉토리에서 터미널을 열고 다음 명령어를 실행하면 모든 서비스가 백그라운드에서 시작됩니다:
docker compose up -d
up: 서비스를 시작합니다.-d(detached mode): 컨테이너를 백그라운드에서 실행하여 터미널을 계속 사용할 수 있도록 합니다.
서비스가 정상적으로 실행되는지 확인하려면 다음 명령어를 사용합니다:
docker compose ps
모든 서비스가 Up 상태로 표시되면 성공적으로 실행된 것입니다.
로그를 확인하려면 다음 명령어를 사용합니다:
docker compose logs -f [서비스_이름]
예: docker compose logs -f kafka
모든 서비스를 중지하고 컨테이너를 삭제하려면 다음 명령어를 사용합니다:
docker compose down
-v 옵션을 추가하면 볼륨도 함께 삭제됩니다. (주의: 데이터가 영구적으로 삭제됩니다!)
docker compose down -v
Image by congerdesign on Pixabay
로컬 개발 환경 효율성 극대화 팁
Docker Compose를 활용한 로컬 개발 환경은 기본적인 설정만으로도 많은 이점을 제공하지만, 몇 가지 추가적인 팁을 통해 효율성을 더욱 극대화할 수 있습니다.
1. 볼륨 마운트를 통한 코드 실시간 반영
애플리케이션 개발 시 코드 변경 사항이 컨테이너에 즉시 반영되도록 하려면, 호스트 머신의 코드 디렉토리를 컨테이너 내부의 작업 디렉토리에 볼륨으로 마운트하는 것이 필수적입니다. 예를 들어, Node.js 애플리케이션을 개발한다면:
services:
my_app:
build: . # Dockerfile이 있는 현재 디렉토리를 기반으로 빌드
ports:
- "8000:8000"
volumes:
- .:/app # 현재 디렉토리를 컨테이너의 /app 디렉토리에 마운트
- /app/node_modules # node_modules는 호스트에서 마운트하지 않고 컨테이너 내부에서 관리
networks:
- app_network
depends_on:
- postgresql
- redis
- kafka
- .:/app 설정은 호스트의 현재 작업 디렉토리(.)를 컨테이너의 /app 디렉토리에 연결하여, 호스트에서 코드를 변경하면 컨테이너 내부에서도 즉시 변경 사항을 확인할 수 있도록 합니다. - /app/node_modules는 컨테이너 내부의 node_modules 디렉토리가 호스트 디렉토리에 의해 덮어씌워지는 것을 방지하여, 컨테이너에 설치된 의존성을 유지할 수 있도록 합니다.
2. 네트워크 별칭 활용
app_network와 같은 사용자 정의 네트워크를 사용하면, 각 서비스가 서로를 서비스 이름으로 참조할 수 있습니다. 예를 들어, 애플리케이션 컨테이너에서 PostgreSQL에 연결할 때는 localhost:5432 대신 postgresql:5432로 연결할 수 있습니다. 이는 컨테이너 환경의 유연성을 높이고, 실제 운영 환경과 유사한 네트워크 구성을 갖추는 데 도움을 줍니다.
# 애플리케이션 코드 내에서 데이터베이스 연결 시
// 기존: 'postgres://user:password@localhost:5432/mydatabase'
// 변경: 'postgres://user:password@postgresql:5432/mydatabase'
# Redis 연결 시
// 기존: 'redis://localhost:6379'
// 변경: 'redis://redis:6379'
# Kafka 연결 시
// 기존: 'localhost:9092'
// 변경: 'kafka:9092'
이러한 방식은 개발 환경과 운영 환경 간의 코드 일관성을 유지하는 데 유리하며, Docker Compose의 강력한 기능 중 하나입니다.
3. 환경 변수 관리
민감한 정보(비밀번호, API 키 등)나 환경별로 달라지는 설정 값(데이터베이스 호스트, 포트 등)은 .env 파일을 활용하여 관리하는 것이 좋습니다. .env 파일을 docker-compose.yml과 같은 디렉토리에 두고 환경 변수를 정의하면, Docker Compose가 자동으로 해당 변수들을 로드하여 docker-compose.yml 파일 내에서 사용할 수 있습니다.
.env 파일 예시:
POSTGRES_DB=mydatabase
POSTGRES_USER=user
POSTGRES_PASSWORD=password
KAFKA_BROKER_HOST=kafka
docker-compose.yml에서 .env 변수 사용 예시:
services:
postgresql:
image: postgres:14-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# ...
kafka:
image: wurstmeister/kafka
environment:
KAFKA_ADVERTISED_HOST_NAME: ${KAFKA_BROKER_HOST}
# ...
이를 통해 설정 변경이 용이하고 보안을 강화할 수 있습니다. .env 파일은 버전 관리 시스템(Git 등)에 포함하지 않도록 .gitignore에 추가하는 것이 일반적입니다.
4. 컨테이너 리소스 제한
로컬 개발 환경에서 너무 많은 컨테이너를 동시에 실행하면 시스템 리소스가 부족해질 수 있습니다. 각 서비스에 필요한 CPU 및 메모리 리소스를 제한하여 안정적인 개발 환경을 유지할 수 있습니다.
services:
postgresql:
# ...
deploy:
resources:
limits:
cpus: '0.5' # 0.5 코어 사용 제한
memory: 512M # 512MB 메모리 사용 제한
redis:
# ...
deploy:
resources:
limits:
cpus: '0.25'
memory: 256M
이 설정은 Docker Compose의 deploy 섹션에서 사용할 수 있으며, 특히 자원이 제한적인 환경에서 유용합니다.
5. Docker Desktop 자원 설정
Mac 또는 Windows에서 Docker Desktop을 사용하는 경우, Docker Desktop 애플리케이션의 설정에서 할당된 CPU 및 메모리 자원을 조절할 수 있습니다. 로컬 개발 환경의 규모와 시스템 사양에 맞춰 적절한 자원을 할당하는 것이 중요합니다. 너무 적은 자원은 성능 저하를, 너무 많은 자원은 호스트 시스템의 불안정을 초래할 수 있습니다.
이러한 팁들을 활용하면 Docker Compose 기반의 로컬 개발 환경을 더욱 효율적이고 안정적으로 운영할 수 있습니다. 개발자는 인프라 설정에 대한 고민을 줄이고, 오롯이 애플리케이션 개발에만 집중할 수 있게 됩니다.
결론: 안정적이고 효율적인 개발 환경의 시작
지금까지 Docker Compose를 활용하여 PostgreSQL, Redis, Kafka가 연동된 로컬 개발 환경을 구축하는 방법에 대해 상세히 살펴보았습니다. 전통적인 개발 환경 구축 방식과 비교 분석하며 Docker Compose가 제공하는 일관성, 격리성, 효율성이라는 강력한 장점들을 명확히 이해할 수 있었습니다.
우리는 각 서비스의 docker-compose.yml 설정을 자세히 들여다보며, 포트 매핑, 환경 변수, 데이터 영속성을 위한 볼륨 마운트, 서비스 간 네트워크 통신 구성 등의 핵심 요소를 학습했습니다. 특히 Kafka의 경우 Zookeeper와의 연동 방식과 KAFKA_ADVERTISED_LISTENERS와 같은 중요한 환경 변수 설정의 의미를 짚어보며, 외부 애플리케이션이 Kafka 브로커에 정상적으로 접근할 수 있도록 하는 방법을 파악했습니다.
최종적으로 모든 서비스를 통합한 docker-compose.yml 파일을 완성하고, docker compose up -d와 같은 간단한 명령어로 복잡한 다중 서비스 환경을 손쉽게 시작하고 관리하는 방법을 익혔습니다. 또한, 볼륨 마운트를 통한 코드 실시간 반영, 네트워크 별칭 활용, 환경 변수 관리, 리소스 제한 등의 효율성 극대화 팁을 통해 더욱 생산적인 개발 환경을 구축하는 방안까지 제시했습니다.
이 가이드가 여러분의 로컬 개발 환경 구축에 큰 도움이 되기를 바랍니다. Docker Compose는 단순히 서비스를 실행하는 도구를 넘어, 개발 팀 전체의 생산성을 향상시키고, 개발 환경과 운영 환경 간의 격차를 줄여주는 핵심적인 기술입니다. 안정적이고 일관된 개발 환경을 통해 더욱 빠르고 효율적으로 멋진 애플리케이션을 만들어 나가시길 응원합니다.
이 글에 대한 궁금한 점이나 여러분만의 Docker Compose 활용 팁이 있다면 댓글로 공유해 주세요. 함께 더 나은 개발 문화를 만들어갈 수 있습니다!
📌 함께 읽으면 좋은 글
- [튜토리얼] Docker Compose 로컬 개발 환경 구축 및 관리 가이드: 효율적인 컨테이너 활용 전략
- [튜토리얼] Go 언어와 Fiber 프레임워크로 빠르고 견고한 RESTful API 서버 구축하기
- [튜토리얼] Docker와 Spring Boot, 컨테이너로 빌드하고 배포까지: 실전 가이드
이 글이 도움이 되셨다면 공감(♥)과 댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.
'튜토리얼' 카테고리의 다른 글
| WebSocket 실시간 채팅: Spring Boot & React 연동 풀스택 개발 가이드 (0) | 2026.06.01 |
|---|---|
| OpenAPI와 Swagger를 활용한 REST API 문서 자동화 및 효율적인 테스트 전략 (0) | 2026.05.31 |
| Docker와 Spring Boot, 컨테이너로 빌드하고 배포까지: 실전 가이드 (0) | 2026.05.30 |
| Kafka를 활용한 분산 메시지 큐 시스템 구축 실전 가이드 (0) | 2026.05.30 |
| Go 언어와 Fiber 프레임워크로 빠르고 견고한 RESTful API 서버 구축하기 (0) | 2026.05.28 |