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 ───┘
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 Volume | postgres-data:/var/lib/postgresql/data | DB 데이터, 재시작 후 보존 필수 |
| Bind Mount | ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro | 설정 파일, 호스트에서 직접 편집 |
| tmpfs | type: 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 컨테이너의 환경 변수만 변경하면 됩니다.