Django: VirtualHost vs Docker
Django 배포 전략 비교
Ubuntu 환경에서 VirtualHost 방식 vs Docker 방식 — 정의·구조·비교·권장사항
① 정의
🖥️ VirtualHost 방식
Native / Bare-metal 배포
Ubuntu 서버에 Python, Django, Gunicorn을 직접 설치하고 Nginx의 VirtualHost(server block)로 도메인별 트래픽을 분기하는 전통적인 배포 방식.
- Ubuntu OS 위에 직접 설치
- Nginx → Gunicorn → Django 체인
- systemd로 프로세스 관리
- virtualenv로 Python 환경 격리
- 도메인당 별도 소켓 파일(.sock)
🐳 Docker 방식
Container 기반 배포
Django 앱과 모든 의존성을 컨테이너 이미지로 패키징하여 격리된 환경에서 실행. docker-compose로 Nginx·Django·DB·Redis를 통합 관리.
- OS와 완전히 격리된 컨테이너
- docker-compose로 서비스 오케스트레이션
- Dockerfile로 환경 코드화
- 서비스별 독립 컨테이너 운영
- 이미지 단위 버전 관리
② 아키텍처 구조
🖥️ VirtualHost 구조
[ 클라이언트 ]
│ HTTP/HTTPS
▼
[ Nginx ] ← VirtualHost 분기
├─ site-a.com → gunicorn-a.sock
└─ site-b.com → gunicorn-b.sock
│
▼
[ Gunicorn ] (systemd 관리)
│
▼
[ Django App ] (venv)
│
▼
[ PostgreSQL ] (OS 직접 설치)
🐳 Docker 구조
[ 클라이언트 ]
│ HTTP/HTTPS
▼
[ nginx 컨테이너 ] ← 리버스 프록시
├─ site-a.com → django_a 컨테이너
└─ site-b.com → django_b 컨테이너
│
▼
[ Django + Gunicorn ] (컨테이너)
│
▼
[ PostgreSQL 컨테이너 ]
│
[ Redis 컨테이너 ]
(모두 docker network로 통신)
③ 항목별 상세 비교
| 비교 항목 |
🖥️ VirtualHost |
🐳 Docker |
| 초기 설정 |
단순 OS 패키지 설치 수준 |
중간 Docker, compose 학습 필요 |
| 환경 격리 |
부분적 venv로 Python만 격리. OS 패키지는 공유 |
완전 OS 수준의 완전한 격리. 의존성 충돌 없음 |
| 확장성(Scale) |
어려움 수동 설정 변경 필요. 수평 확장 복잡 |
우수 docker-compose scale 또는 Swarm/K8s 연동 |
| 배포 속도 |
느림 git pull → pip install → restart 수동 절차 |
빠름 이미지 빌드 후 docker-compose up -d |
| 롤백 |
어려움 이전 상태 복원이 복잡하고 리스크 큼 |
쉬움 이전 이미지 태그로 즉시 롤백 가능 |
| 리소스 사용 |
효율적 컨테이너 오버헤드 없음. 성능 우수 |
약간 높음 컨테이너 레이어 오버헤드 (미미한 수준) |
| 개발/운영 일관성 |
불일치 "내 PC에서는 됐는데..." 문제 발생 |
동일 개발·스테이징·운영 환경 완전 동일 |
| SSL/HTTPS |
Certbot + Nginx 직접 설정 |
Certbot 컨테이너 or Nginx Proxy Manager 사용 |
| 로그 관리 |
journalctl, /var/log 직접 확인 |
docker logs, ELK Stack 등 연동 용이 |
| DB 관리 |
안정적 OS에 직접 설치, 성능 최적 |
주의 필요 볼륨 마운트 설정 필수 (데이터 영속성) |
| 팀 협업 |
불편 신규 팀원 환경 구성에 긴 시간 |
편리 docker-compose up 한 줄로 즉시 개발 가능 |
| CI/CD 연동 |
SSH 배포 스크립트 작성 필요 |
표준화 GitHub Actions, GitLab CI 템플릿 풍부 |
| 모니터링 |
htop, netstat 등 수동 확인 |
Portainer, Grafana, Prometheus 연동 용이 |
| 적합한 규모 |
소규모 1~3개 사이트 |
중~대규모, 다중 서비스 |
④ 언제 무엇을 선택할까?
🖥️ VirtualHost 추천 상황
- 혼자 운영하는 소규모 1~3개 사이트
- 서버 리소스가 극히 제한적인 경우 (1GB RAM 이하)
- Docker 학습 비용이 부담스러운 경우
- 레거시 시스템 유지보수
- 빠른 1회성 프로토타입 서버 구성
🐳 Docker 추천 상황
- 팀 단위 협업 프로젝트
- CI/CD 파이프라인을 구성하는 경우
- 여러 Django 프로젝트를 한 서버에서 운영
- 스테이징/운영 환경의 일관성이 중요할 때
- 미래에 Kubernetes 확장을 고려하는 경우
- 빠른 롤백이 필요한 서비스
⑤ 최상의 구성 (Best Practice) — Docker Compose
my-project/
├── docker-compose.yml # 서비스 오케스트레이션
├── docker-compose.prod.yml # 운영 환경 오버라이드
├── Dockerfile # Django 이미지 빌드
├── .env # 환경변수 (git 제외!)
├── .env.example # 환경변수 템플릿
├── nginx/
│ ├── nginx.conf
│ └── conf.d/
│ └── default.conf
└── app/
├── manage.py
├── requirements.txt
└── config/
├── settings/
│ ├── base.py
│ ├── local.py
│ └── production.py
└── wsgi.py
# 1단계: 빌드 스테이지 (의존성 설치)
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt
# 2단계: 실행 스테이지 (최소 이미지)
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \ # .pyc 파일 생성 방지
PYTHONUNBUFFERED=1 # 로그 실시간 출력
WORKDIR /app
# 빌더에서 설치된 패키지만 복사 (이미지 크기 최소화)
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY ./app .
RUN python manage.py collectstatic --noinput --settings=config.settings.production
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "config.wsgi:application"]
version: '3.9'
services:
# ── Django 애플리케이션 ──────────────────────────
web:
build: .
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3
volumes:
- ./app:/app # 개발 시 코드 실시간 반영
- static_volume:/app/staticfiles
- media_volume:/app/media
env_file:
- .env
depends_on:
db:
condition: service_healthy # DB 준비 완료 후 시작
redis:
condition: service_started
# ── Nginx 리버스 프록시 ──────────────────────────
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- static_volume:/static
- media_volume:/media
- ./certbot/conf:/etc/letsencrypt # SSL 인증서
depends_on:
- web
# ── PostgreSQL ───────────────────────────────────
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data # 데이터 영속성!
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck: # DB 헬스체크
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
# ── Redis (Celery/캐시용) ──────────────────────
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
# ── Celery Worker (비동기 작업) ───────────────
celery:
build: .
command: celery -A config worker --loglevel=info
volumes:
- ./app:/app
env_file:
- .env
depends_on:
- db
- redis
volumes: # Named volumes 정의
postgres_data:
redis_data:
static_volume:
media_volume:
# 업스트림: Django 컨테이너
upstream django {
server web:8000; # docker-compose 서비스명 사용
}
server {
listen 80;
server_name your-domain.com;
# Static 파일 직접 서빙 (Django를 거치지 않음)
location /static/ {
alias /static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /media/;
}
# Django 앱으로 프록시
location / {
proxy_pass http://django;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10M;
}
}
# Django
DJANGO_SETTINGS_MODULE=config.settings.production
SECRET_KEY=your-super-secret-key-here
DEBUG=False
ALLOWED_HOSTS=your-domain.com,www.your-domain.com
# Database
DB_NAME=mydb
DB_USER=myuser
DB_PASSWORD=strongpassword123
DATABASE_URL=postgresql://myuser:strongpassword123@db:5432/mydb
# Redis
REDIS_URL=redis://redis:6379/0
# Email
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_HOST_USER=your@gmail.com
EMAIL_HOST_PASSWORD=app-password
# 처음 서버 설정
$ docker-compose up -d --build
# DB 마이그레이션
$ docker-compose exec web python manage.py migrate
# 슈퍼유저 생성
$ docker-compose exec web python manage.py createsuperuser
# 새 코드 배포 (무중단에 가까운 방식)
$ git pull
$ docker-compose up -d --build --no-deps web # web 서비스만 재빌드
# 로그 확인
$ docker-compose logs -f web
$ docker-compose logs -f nginx
# 이전 버전으로 롤백
$ docker-compose down
$ docker tag myproject_web:latest myproject_web:backup
$ docker-compose up -d
⑥ 최종 권장사항
🎯 솔로 개발자 / 소규모 클라이언트 프로젝트 최적 전략
-
Docker Compose를 기본 전략으로 채택
초기 학습 비용이 있지만, 두 번째 프로젝트부터는 압도적으로 빠르고 안전합니다. 환경 재현성과 롤백 능력이 실무에서 핵심 가치입니다.
-
한 서버에서 여러 사이트: Nginx Proxy Manager 활용
docker-compose 기반으로 여러 프로젝트를 운영할 때, Nginx Proxy Manager 컨테이너 하나로 SSL 자동 갱신 + 도메인 분기를 GUI로 관리할 수 있습니다.
-
PostgreSQL은 컨테이너 vs 호스트 직접 설치 고민
소규모라면 컨테이너로 충분합니다. 데이터 볼륨을 named volume으로 관리하고, 정기 pg_dump 백업 스크립트를 cron으로 설정하세요.
-
CI/CD: GitHub Actions + Docker Hub 연동
main 브랜치 push 시 자동으로 이미지를 빌드하고 서버에 배포하는 파이프라인을 구성하면 배포가 버튼 하나로 완성됩니다.
-
현재 VirtualHost 환경이라면 점진적 마이그레이션
기존 서비스는 건드리지 않고, 신규 프로젝트부터 Docker로 전환하세요. 서버에 Docker를 설치해도 기존 Nginx + Gunicorn 환경은 그대로 동작합니다.