본문으로 건너뛰기

Docker 기반 서버 연동 아키텍처

Docker는 애플리케이션과 그 실행 환경을 컨테이너라는 격리된 단위로 패키징하는 기술입니다. 전통적인 서버 환경에서는 Nginx, Tomcat, 데이터베이스가 하나의 물리 서버 또는 VM에 설치되어 서로 직접 통신했지만, Docker 환경에서는 각 서비스가 독립적인 컨테이너로 분리되어 더 명확한 역할 구분과 유연한 배포가 가능합니다.


전통적 서버 환경 vs Docker 환경 비교

항목전통적 서버 환경Docker 환경
배포 단위서버(물리/VM)컨테이너
환경 구성수동 패키지 설치Dockerfile로 코드화
격리 수준프로세스 수준네임스페이스 + cgroup
확장성VM 추가 (분 단위)컨테이너 추가 (초 단위)
환경 일관성서버마다 다를 수 있음이미지 기반으로 동일 보장
자원 사용OS 전체 부팅 필요호스트 커널 공유, 경량
롤백스냅샷 또는 수동 복구이전 이미지 태그로 즉시 복구
설정 관리서버 직접 접속 후 편집볼륨 마운트 또는 이미지 내 포함
네트워크 설계iptables 직접 관리Docker 네트워크 드라이버 활용
로그 관리파일 시스템 직접 접근docker logs 또는 중앙 집계

컨테이너 역할 분리

Docker 기반 서버 연동에서 핵심 원칙은 하나의 컨테이너에 하나의 역할입니다. 이를 통해 각 서비스의 독립적인 업데이트, 스케일링, 장애 격리가 가능합니다.

Nginx 리버스 프록시 컨테이너

  • 외부 트래픽을 최초로 수신하는 진입점(Entry Point)
  • SSL/TLS 종료(Termination): HTTPS → HTTP 변환 후 내부 전달
  • 정적 파일 직접 서빙 (HTML, CSS, JS, 이미지)
  • 로드 밸런싱: 다수의 WAS 컨테이너로 트래픽 분산
  • 요청 버퍼링 및 캐싱으로 WAS 부하 경감
  • 접근 로그, 보안 헤더 설정
공식 이미지: nginx:alpine (약 7MB, 경량)
포트: 80 (HTTP), 443 (HTTPS) → 호스트에 노출

Tomcat / Spring Boot WAS 컨테이너

  • 비즈니스 로직 처리, REST API 제공
  • Nginx로부터 프록시된 요청만 수신 (외부 직접 접근 차단)
  • 세션 관리, 인증/인가 처리
  • 데이터베이스 연결 풀 관리 (HikariCP 등)
  • 수평 확장 가능: 동일 이미지로 다수 인스턴스 실행
공식 이미지: tomcat:10-jdk17-temurin-alpine 또는 eclipse-temurin:17-jre
포트: 8080 → 외부 미노출, 컨테이너 네트워크 내부 통신만

DB 컨테이너

  • 데이터 영구 저장 (PostgreSQL, MySQL, MariaDB 등)
  • 가장 내부 레이어에 위치, 외부 접근 완전 차단
  • 볼륨을 통한 데이터 영속성 보장 (컨테이너 재시작 시에도 데이터 유지)
  • 프로덕션에서는 관리형 DB 서비스(RDS 등) 권장, 개발/스테이징은 컨테이너 활용
공식 이미지: postgres:16-alpine
포트: 5432 → 외부 미노출, 백엔드 네트워크 내부에서만 접근

컨테이너 네트워크 설계 원칙

보안과 격리를 위해 네트워크를 프론트엔드백엔드로 분리하는 것이 표준 패턴입니다.

프론트엔드 네트워크 (frontend network)

  • Nginx 컨테이너와 WAS 컨테이너가 속한 네트워크
  • Nginx → WAS 방향의 프록시 통신 담당
  • 외부(호스트)와 연결되는 유일한 네트워크

백엔드 네트워크 (backend network)

  • WAS 컨테이너와 DB 컨테이너가 속한 네트워크
  • WAS → DB 방향의 데이터베이스 쿼리 통신 담당
  • 외부에서 완전히 격리

네트워크 분리의 핵심 원칙

  • Nginx 컨테이너: frontend + backend 네트워크 모두 참여 (게이트웨이 역할)
  • WAS 컨테이너: frontend + backend 네트워크 모두 참여
  • DB 컨테이너: backend 네트워크만 참여 (외부 접근 원천 차단)

트래픽 흐름

Client (브라우저/앱)

│ HTTPS:443 / HTTP:80

┌─────────────────┐ frontend network
│ Nginx 컨테이너 │ ─────────────────────────────┐
│ (리버스 프록시) │ │
└─────────────────┘ │
│ HTTP:8080 (proxy_pass) │
│ frontend network │
▼ │
┌─────────────────┐ backend network │
│ WAS 컨테이너 │ ──────────────────┐ │
│ (Spring Boot / │ │ │
│ Tomcat) │ │ │
└─────────────────┘ │ │
│ JDBC:5432 │ │
│ backend network │ │
▼ │ │
┌─────────────────┐ │ │
│ DB 컨테이너 │ ◄────────────────┘ │
│ (PostgreSQL) │ backend network │
└─────────────────┘ │

frontend network ───┘

Architecture


Docker Compose 기본 뼈대 예시

아래는 Nginx + Spring Boot + PostgreSQL을 구성하는 기본 뼈대입니다. 각 서비스의 역할과 네트워크 연결 방식을 확인하세요.

# docker-compose.yml
version: '3.9'

services:
# ── 1. Nginx 리버스 프록시 ──────────────────────────
nginx:
image: nginx:alpine
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certbot/conf:/etc/letsencrypt:ro
- nginx-logs:/var/log/nginx
- static-files:/usr/share/nginx/html/static:ro
depends_on:
app:
condition: service_healthy
networks:
- frontend
restart: unless-stopped

# ── 2. Spring Boot WAS ──────────────────────────────
app:
build:
context: .
dockerfile: Dockerfile
container_name: spring-app
expose:
- "8080" # 외부 노출 없이 컨테이너 네트워크 내부 통신만
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_HOST=db # 서비스 이름이 DNS 역할
- DB_PORT=5432
- DB_NAME=${POSTGRES_DB}
- DB_USER=${POSTGRES_USER}
- DB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- app-logs:/app/logs
- static-files:/app/static
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- frontend
- backend
restart: unless-stopped

# ── 3. PostgreSQL DB ────────────────────────────────
db:
image: postgres:16-alpine
container_name: postgres-db
expose:
- "5432" # 외부 노출 없음
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
restart: unless-stopped

# ── 네트워크 정의 ────────────────────────────────────
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 호스트와의 외부 통신 차단

# ── 볼륨 정의 ────────────────────────────────────────
volumes:
postgres-data:
nginx-logs:
app-logs:
static-files:

개발/스테이징/프로덕션 환경 분리 전략

docker-compose.override.yml 패턴

Docker Compose는 기본 파일(docker-compose.yml)과 오버라이드 파일을 자동으로 병합합니다. 이를 활용하여 환경별 설정을 분리합니다.

docker-compose.yml              # 공통 기반 설정
docker-compose.override.yml # 개발 환경 (자동 적용)
docker-compose.staging.yml # 스테이징 환경
docker-compose.prod.yml # 프로덕션 환경

개발 환경 오버라이드 (docker-compose.override.yml)

# 개발 환경: 소스 코드 핫 리로드, 포트 직접 노출, 디버그 설정
version: '3.9'

services:
app:
build:
target: development # 멀티 스테이지에서 개발 스테이지 선택
volumes:
- ./src:/app/src # 소스 코드 실시간 마운트
environment:
- SPRING_PROFILES_ACTIVE=dev
- JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
ports:
- "8080:8080" # 디버깅용 직접 접근 허용
- "5005:5005" # 원격 디버그 포트

db:
ports:
- "5432:5432" # DB 클라이언트 직접 접속 허용

스테이징 환경 실행

docker compose -f docker-compose.yml -f docker-compose.staging.yml up -d

프로덕션 환경 실행

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

볼륨 설계

컨테이너는 기본적으로 상태가 없는(stateless) 존재입니다. 컨테이너가 삭제되면 내부 데이터도 함께 사라집니다. 볼륨을 사용하면 컨테이너 라이프사이클과 독립적으로 데이터를 유지할 수 있습니다.

볼륨 유형별 용도

볼륨 유형문법용도
Named Volumepostgres-data:/var/lib/postgresql/dataDB 데이터, 재시작 후 보존 필수
Bind Mount./nginx/nginx.conf:/etc/nginx/nginx.conf:ro설정 파일, 호스트에서 직접 편집
tmpfstype: tmpfs, target: /tmp임시 데이터, 메모리 저장

권장 볼륨 구조

프로젝트 루트/
├── nginx/
│ ├── nginx.conf # Nginx 메인 설정 (bind mount)
│ ├── conf.d/
│ │ └── default.conf # 가상 호스트 설정 (bind mount)
│ └── ssl/ # SSL 인증서 (bind mount, :ro)
├── db/
│ └── init/ # DB 초기화 SQL (bind mount, :ro)
├── .env # 환경 변수 (git 제외)
└── docker-compose.yml

Named Volume은 Docker가 관리: 호스트 경로는 /var/lib/docker/volumes/ 아래 자동 생성됩니다.

설정 파일은 Bind Mount: nginx.conf 등 자주 수정하는 파일은 호스트 경로를 직접 마운트하여 컨테이너 재시작 없이 수정 가능하게 합니다 (Nginx reload 명령 활용).

# 설정 변경 후 컨테이너 재시작 없이 Nginx 리로드
docker exec nginx-proxy nginx -s reload

고수 팁

1. internal: true 네트워크로 DB 외부 노출 완전 차단

백엔드 네트워크에 internal: true를 설정하면 해당 네트워크의 컨테이너는 외부 인터넷과 통신할 수 없습니다. DB 컨테이너의 보안을 강화하는 가장 간단한 방법입니다.

2. Docker Secrets로 민감 정보 관리

Docker Swarm 또는 Compose에서 secrets를 사용하면 환경 변수 대신 파일로 민감 정보를 안전하게 주입할 수 있습니다.

secrets:
db_password:
file: ./secrets/db_password.txt

services:
db:
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password

3. 프로덕션에서 DB 컨테이너 대신 관리형 서비스 사용

AWS RDS, GCP Cloud SQL 등 관리형 DB는 자동 백업, 페일오버, 패치를 제공합니다. 프로덕션에서는 DB 컨테이너를 외부 서비스로 교체하고 WAS 컨테이너의 환경 변수만 변경하면 됩니다.