Docker로 로컬 HTTPS 통신 구현하기: 개발 환경 구축부터 실무 적용까지
최근 프로젝트를 진행하면서 로컬 개발 환경에서도 HTTPS 통신이 필요한 상황이 자주 발생했습니다. 특히 OAuth 인증이나 결제 모듈 테스트와 같이 보안이 중요한 기능을 개발할 때는 HTTPS 환경이 필수적입니다. 이번 글에서는 Docker를 활용하여 로컬 개발 환경에서 HTTPS 통신을 구현하는 방법을 실제 경험을 바탕으로 공유하고자 합니다.
Docker를 사용하면 개발, 테스트, 배포 환경을 일관되게 유지할 수 있다는 장점이 있지만, HTTPS 설정은 다소 복잡한 부분이 있습니다. 이 글에서는 도메인 설정부터 리버스 프록시 구성, 컨테이너 간 통신 방법까지 단계별로 설명하여 실무에서 바로 적용할 수 있도록 안내해 드리겠습니다.
1. 환경 구축 요건
로컬 개발 환경에서 HTTPS 통신을 구현하기 위해서는 다음과 같은 요소들이 필요합니다:
- Docker 및 Docker Compose
- 로컬 도메인 설정
- SSL 인증서 (자체 서명 또는 mkcert와 같은 도구 활용)
- 리버스 프록시 (Nginx, Traefik 등)
- 프론트엔드 및 백엔드 서비스 컨테이너
이 글에서는 Nginx를 리버스 프록시로 사용하고, Docker Compose를 통해 여러 서비스를 관리하는 방법을 중점적으로 다룹니다.
2. HTTPS 접속을 위한 필수 설정
2.1 도메인 설정
로컬 환경에서 도메인을 사용하기 위해서는 hosts 파일을 수정해야 합니다. 이렇게 하면 특정 도메인 이름을 로컬 IP 주소로 매핑할 수 있습니다.
Mac/Linux의 경우 /etc/hosts
파일을, Windows의 경우 C:\Windows\System32\drivers\etc\hosts
파일을 수정합니다.
127.0.0.1 local.example.com api.local.example.com
위와 같이 설정하면 local.example.com
와 api.local.example.com
으로 접속할 때 로컬 시스템(127.0.0.1)으로 연결됩니다.
2.2 SSL 인증서 생성
로컬 개발 환경에서는 자체 서명된 인증서를 사용하거나, mkcert
와 같은 도구를 활용하여 로컬에서 유효한 인증서를 생성할 수 있습니다. 여기서는 mkcert를 사용한 방법을 소개합니다.
# mkcert 설치 (macOS 기준)
brew install mkcert
mkcert -install
# 도메인용 인증서 생성
mkdir -p certs
mkcert -key-file certs/local.key -cert-file certs/local.crt "local.example.com" "*.local.example.com"
이렇게 생성한 인증서 파일(local.crt
)과 키 파일(local.key
)은 Nginx 설정에서 사용하게 됩니다.
2.3 리버스 프록시 설정
Nginx를 리버스 프록시로 사용하여 HTTPS 요청을 내부 서비스로 전달하는 설정을 구성합니다. 다음은 기본적인 Nginx 설정 예시입니다.
server {
listen 443 ssl;
server_name local.example.com;
ssl_certificate /etc/nginx/certs/local.crt;
ssl_certificate_key /etc/nginx/certs/local.key;
location / {
proxy_pass http://frontend:3000;
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;
}
}
server {
listen 443 ssl;
server_name api.local.example.com;
ssl_certificate /etc/nginx/certs/local.crt;
ssl_certificate_key /etc/nginx/certs/local.key;
location / {
proxy_pass http://backend:8080;
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;
}
}
이 설정은 local.example.com
도메인으로 들어오는 요청을 프론트엔드 서비스(포트 3000)로, api.local.example.com
도메인으로 들어오는 요청을 백엔드 서비스(포트 8080)로 전달합니다.
2.4 프론트엔드 Nginx 설정 예시
프론트엔드 애플리케이션을 서빙하기 위한 별도의 Nginx 설정이 필요한 경우, 다음과 같이 구성할 수 있습니다.
server {
listen 3000;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# API 요청을 백엔드로 프록시
location /api {
proxy_pass http://backend:8080;
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;
}
}
이 설정은 React, Vue 등의 SPA 프레임워크로 개발된 프론트엔드 애플리케이션을 서빙하는 데 적합합니다. /api
경로로 들어오는 요청은 백엔드 서비스로 전달됩니다.
3. 프론트엔드와 백엔드의 구분
Docker 환경에서 프론트엔드와 백엔드를 구분하여 관리하는 것은 여러 장점이 있습니다.
- 각 서비스의 독립적인 개발 및 배포 가능
- 스케일링 용이성
- 기술 스택 분리
- 장애 격리
일반적으로 프론트엔드는 정적 파일을 서빙하는 경량 컨테이너로, 백엔드는 비즈니스 로직과 데이터베이스 연동을 담당하는 독립적인 컨테이너로 구성합니다. 이렇게 분리된 구조에서는 컨테이너 간 통신 방법이 중요합니다.
4. 컨테이너 간 통신 방법
Docker 환경에서 컨테이너 간 통신은 크게 외부 네트워크와 내부 네트워크로 구분할 수 있습니다.
4.1 외부 네트워크 (External Internet)
외부 네트워크는 호스트 시스템과 컨테이너 간의 통신, 또는 외부 인터넷과의 통신을 의미합니다. Docker의 포트 매핑 기능을 사용하여 호스트의 포트를 컨테이너의 포트와 연결합니다.
ports:
- "443:443" # 호스트의 443 포트를 컨테이너의 443 포트와 연결
4.2 내부 네트워크 (Docker 내부 네트워크)
Docker Compose를 사용하면 같은 네트워크에 속한 컨테이너들은 서비스 이름으로 서로를 참조할 수 있습니다. 이것이 내부 네트워크 통신의 핵심입니다.
networks:
app-network:
driver: bridge
각 서비스에 이 네트워크를 연결하면 서비스 이름을 호스트명으로 사용할 수 있습니다.
4.3 컨테이너 간 통신 예시
백엔드 서비스에서 데이터베이스 컨테이너에 접속하는 경우:
// Node.js 백엔드 예시
const mongoose = require('mongoose');
mongoose.connect('mongodb://database:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
여기서 database
는 Docker Compose에 정의된 MongoDB 서비스의 이름입니다. 내부 네트워크에서는 이 이름을 사용하여 통신할 수 있습니다.
5. hostname 옵션 활용하기
Docker Compose에서 hostname
옵션을 설정하면, 컨테이너 내부에서 사용되는 호스트 이름을 지정할 수 있습니다. 이는 특정 서비스가 고정된 호스트 이름으로 접근해야 하는 경우 유용합니다.
version: '3'
services:
nginx:
image: nginx:alpine
hostname: gateway
ports:
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./certs:/etc/nginx/certs
networks:
- app-network
frontend:
build: ./frontend
hostname: frontend
networks:
- app-network
backend:
build: ./backend
hostname: backend
environment:
- DATABASE_URL=mongodb://database:27017/myapp
networks:
- app-network
database:
image: mongo:4
hostname: database
volumes:
- db-data:/data/db
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
db-data:
위 예시에서는 각 서비스에 hostname
옵션을 지정하여, 컨테이너 내부에서 일관된 호스트 이름을 사용할 수 있도록 설정했습니다. 이는 설정 파일에서 하드코딩된 호스트 이름을 사용해야 하는 경우 특히 유용합니다.
6. 실제 적용 사례: OAuth 인증 연동
실무에서 가장 HTTPS가 필요한 경우 중 하나는 OAuth 인증 연동입니다. 대부분의 OAuth 제공자(Google, Facebook 등)는 보안상의 이유로 리디렉션 URL에 HTTPS를 요구합니다.
실제 프로젝트에서는 다음과 같은 과정으로 OAuth 연동을 구현했습니다:
local.example.com
도메인 설정- mkcert를 사용하여 로컬 SSL 인증서 생성
- OAuth 제공자 콘솔에서
https://local.example.com/auth/callback
을 리디렉션 URL로 등록 - Nginx 리버스 프록시 설정으로 HTTPS 요청 처리
- 백엔드 서비스에서 OAuth 인증 로직 구현
이러한 구성을 통해 로컬 개발 환경에서도 실제 운영 환경과 동일한 방식으로 OAuth 인증 흐름을 테스트할 수 있었습니다.
마치며
이 글에서는 Docker를 활용하여 로컬 개발 환경에서 HTTPS 통신을 구현하는 방법을 살펴보았습니다. 도메인 설정부터 SSL 인증서 생성, 리버스 프록시 구성, 그리고 컨테이너 간 통신 방법까지 실무에서 적용할 수 있는 내용을 다루었습니다.
로컬 환경에서 HTTPS를 구현하면 개발 단계에서부터 보안 관련 이슈를 조기에 발견하고 해결할 수 있으며, 운영 환경과 유사한 조건에서 테스트할 수 있다는 장점이 있습니다. 특히 OAuth, 결제 모듈 등 HTTPS가 필수적인 기능을 개발할 때 매우 유용합니다.
Docker의 강력한 네트워킹 기능과 Nginx의 유연한 프록시 설정을 조합하면, 복잡한 마이크로서비스 아키텍처도 로컬 환경에서 효과적으로 구현할 수 있습니다. 이러한 환경 구성 경험은 실제 프로덕션 환경 설계에도 큰 도움이 됩니다.
여러분의 개발 환경에도 이 글에서 소개한 방법이 도움이 되길 바랍니다.
Comments
Post a Comment