본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 26. · 8 Views
Docker 실전 프로젝트 완벽 가이드
프론트엔드, 백엔드, 데이터베이스를 Docker로 컨테이너화하고 Nginx 리버스 프록시를 구성하여 실제 프로덕션 환경에 배포하는 전 과정을 다룹니다. 실무에서 바로 적용할 수 있는 Docker Compose 기반 멀티 컨테이너 애플리케이션 구축법을 배웁니다.
목차
1. 프로젝트 구조 설계
김개발 씨는 새로운 프로젝트를 시작하게 되었습니다. React 프론트엔드, Node.js 백엔드, PostgreSQL 데이터베이스로 구성된 풀스택 애플리케이션입니다.
선배 박시니어 씨가 다가와 물었습니다. "이번엔 Docker로 구성해볼 생각 있어요?"
Docker 프로젝트의 구조 설계는 마치 건물의 설계도를 그리는 것과 같습니다. 어떤 서비스들이 필요하고, 각 서비스가 어떻게 통신할지를 먼저 정해야 합니다.
잘 설계된 프로젝트 구조는 개발, 테스트, 배포 모든 단계에서 일관된 환경을 보장합니다.
다음 코드를 살펴봅시다.
my-docker-project/
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── backend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── nginx/
│ ├── Dockerfile
│ └── nginx.conf
├── docker-compose.yml
├── docker-compose.prod.yml
└── .env.example
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 지금까지는 로컬 환경에서 개발하고, 서버에 직접 파일을 올려 배포하는 방식으로 작업해왔습니다.
그런데 이번 프로젝트는 규모가 커서 기존 방식으론 한계가 있었습니다. 박시니어 씨가 화이트보드 앞으로 김개발 씨를 불렀습니다.
"Docker 프로젝트를 시작하기 전에 먼저 구조부터 잡아야 해요. 마치 집을 짓기 전에 설계도를 그리는 것처럼요." 그렇다면 Docker 프로젝트 구조란 정확히 무엇일까요?
쉽게 비유하자면, Docker 프로젝트 구조는 마치 아파트 단지의 배치도와 같습니다. 아파트 단지에는 여러 동이 있고, 각 동에는 관리사무소, 주차장, 놀이터 같은 공용 시설이 연결되어 있습니다.
Docker 프로젝트도 마찬가지로 여러 서비스(컨테이너)가 있고, 이들이 네트워크로 연결되어 하나의 시스템을 이룹니다. Docker를 사용하지 않던 시절에는 어땠을까요?
개발자들은 "내 컴퓨터에서는 되는데요"라는 말을 입에 달고 살았습니다. 개발 환경과 운영 환경이 달라서 생기는 문제가 빈번했습니다.
Node.js 버전이 다르거나, PostgreSQL 설정이 달라서 배포할 때마다 예상치 못한 버그가 터졌습니다. 바로 이런 문제를 해결하기 위해 Docker가 등장했습니다.
위의 프로젝트 구조를 살펴보면, 크게 네 가지 영역으로 나뉩니다. frontend 폴더에는 React 애플리케이션과 이를 컨테이너화할 Dockerfile이 들어갑니다.
backend 폴더에는 Node.js API 서버 코드가 위치합니다. nginx 폴더는 리버스 프록시 설정을 담당합니다.
가장 중요한 파일은 루트에 있는 docker-compose.yml입니다. 이 파일이 모든 서비스를 하나로 묶어주는 오케스트라 지휘자 역할을 합니다.
개발용과 프로덕션용을 분리하여 docker-compose.prod.yml도 함께 관리합니다. 실제 현업에서는 이 구조를 기반으로 CI/CD 파이프라인을 구축합니다.
GitHub Actions나 Jenkins가 이 구조를 읽어서 자동으로 빌드하고 배포합니다. 처음에 구조를 잘 잡아두면 나중에 확장하기도 훨씬 수월합니다.
초보 개발자들이 흔히 하는 실수 중 하나는 모든 것을 하나의 컨테이너에 넣으려는 것입니다. 프론트엔드, 백엔드, 데이터베이스를 한 컨테이너에 담으면 관리가 어려워지고 확장성이 떨어집니다.
각 서비스는 반드시 분리해야 합니다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 폴더를 이렇게 나누는 거군요!" 이제 본격적으로 각 서비스를 컨테이너화할 준비가 되었습니다.
실전 팁
💡 - 각 서비스는 독립적인 폴더와 Dockerfile을 가져야 합니다
- .env.example 파일을 만들어 환경변수 템플릿을 공유하세요
- .dockerignore 파일로 불필요한 파일이 이미지에 포함되지 않도록 하세요
2. Frontend 컨테이너화
프로젝트 구조를 잡은 김개발 씨는 이제 React 프론트엔드를 컨테이너화하려고 합니다. 박시니어 씨가 옆에서 조언합니다.
"프론트엔드는 빌드 단계와 서빙 단계를 분리하는 게 핵심이에요. 멀티 스테이지 빌드라고 하죠."
프론트엔드 컨테이너화는 React 애플리케이션을 빌드하고 정적 파일을 효율적으로 서빙하는 과정입니다. 멀티 스테이지 빌드를 사용하면 빌드에 필요한 도구들은 최종 이미지에 포함시키지 않아 이미지 크기를 획기적으로 줄일 수 있습니다.
이는 마치 요리를 완성한 후 조리도구는 치우고 음식만 서빙하는 것과 같습니다.
다음 코드를 살펴봅시다.
# frontend/Dockerfile
# 1단계: 빌드 스테이지
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 2단계: 프로덕션 스테이지
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
김개발 씨는 frontend 폴더에 들어가 Dockerfile을 작성하기 시작했습니다. 처음에는 단순하게 Node.js 이미지에 모든 것을 담으려 했습니다.
하지만 박시니어 씨가 말렸습니다. "잠깐, 그렇게 하면 이미지 크기가 1GB가 넘어갈 거예요.
프로덕션에서는 빌드 도구가 필요 없잖아요?" 멀티 스테이지 빌드란 정확히 무엇일까요? 쉽게 비유하자면, 멀티 스테이지 빌드는 마치 케이크를 만드는 과정과 같습니다.
케이크를 만들 때는 믹서, 오븐, 각종 도구가 필요합니다. 하지만 손님에게 서빙할 때는 완성된 케이크만 예쁜 접시에 담아 내놓습니다.
Docker 멀티 스테이지 빌드도 마찬가지로, 빌드에 필요한 모든 도구는 첫 번째 단계에서만 사용하고, 최종 이미지에는 결과물만 담습니다. 코드를 한 줄씩 살펴보겠습니다.
첫 번째 단계에서는 node:20-alpine 이미지를 사용합니다. Alpine 리눅스 기반이라 가볍습니다.
AS builder라고 이름을 붙여서 나중에 참조할 수 있게 합니다. npm ci 명령어는 package-lock.json을 기반으로 정확한 버전의 패키지를 설치합니다.
npm install보다 빠르고 일관성이 있습니다. 두 번째 단계가 핵심입니다.
nginx:alpine 이미지는 약 20MB 정도로 매우 가볍습니다. COPY --from=builder를 통해 첫 번째 단계에서 빌드된 결과물만 가져옵니다.
node_modules나 소스 코드는 포함되지 않습니다. 실제 현업에서 이 패턴을 사용하면 이미지 크기가 1GB에서 50MB 이하로 줄어듭니다.
배포 속도가 빨라지고, 저장 공간도 절약됩니다. 특히 쿠버네티스 환경에서는 이미지 크기가 Pod 시작 시간에 직접적인 영향을 미칩니다.
주의할 점도 있습니다. .dockerignore 파일을 반드시 만들어야 합니다.
node_modules 폴더가 COPY 명령어에 포함되면 빌드 시간이 크게 늘어납니다. 또한 환경변수를 빌드 타임에 주입해야 하는 경우 ARG를 사용해야 합니다.
김개발 씨가 docker build를 실행하자 깔끔하게 이미지가 만들어졌습니다. "와, 정말 50MB밖에 안 되네요!" 프론트엔드 컨테이너화의 첫 관문을 성공적으로 통과했습니다.
실전 팁
💡 - .dockerignore에 node_modules, .git, .env를 반드시 추가하세요
- 환경변수가 필요하면 빌드 시 ARG로 전달하거나 런타임에 주입하세요
- 캐시 효율을 위해 package.json을 먼저 복사하고 npm ci를 실행하세요
3. Backend API 컨테이너화
프론트엔드를 마친 김개발 씨는 이제 Node.js 백엔드 API를 컨테이너화합니다. Express로 만든 REST API 서버입니다.
박시니어 씨가 말합니다. "백엔드는 프론트엔드와 달리 런타임이 필요해요.
그리고 보안도 더 신경 써야 하죠."
백엔드 API 컨테이너화는 Node.js 애플리케이션을 격리된 환경에서 안전하게 실행하는 것입니다. 프론트엔드와 달리 런타임 환경이 필요하므로 Node.js 이미지를 그대로 사용하되, 보안을 위해 비루트 사용자로 실행합니다.
프로덕션에서는 반드시 이 점을 지켜야 합니다.
다음 코드를 살펴봅시다.
# backend/Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS production
WORKDIR /app
# 보안: 비루트 사용자 생성
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]
김개발 씨는 backend 폴더로 이동해 Dockerfile을 작성하기 시작했습니다. 프론트엔드와 비슷하게 멀티 스테이지 빌드를 사용하되, 한 가지 큰 차이가 있었습니다.
박시니어 씨가 설명했습니다. "프론트엔드는 빌드 결과물만 Nginx에서 서빙하면 되지만, 백엔드는 Node.js 런타임이 있어야 코드가 실행돼요.
그래서 node_modules도 함께 복사해야 해요." 백엔드 컨테이너화에서 가장 중요한 것은 무엇일까요? 바로 보안입니다.
쉽게 비유하자면, 컨테이너를 루트 사용자로 실행하는 것은 마치 집 현관문을 열어놓고 외출하는 것과 같습니다. 컨테이너가 해킹당하면 공격자가 루트 권한을 얻게 됩니다.
반면 비루트 사용자로 실행하면 피해를 최소화할 수 있습니다. 코드의 핵심 부분을 살펴보겠습니다.
addgroup과 adduser 명령어로 nodejs라는 전용 사용자를 만듭니다. UID와 GID를 1001로 지정하는 이유는 호스트 시스템과의 권한 충돌을 피하기 위해서입니다.
USER nodejs 명령어 이후의 모든 명령은 이 사용자 권한으로 실행됩니다. 프로덕션 스테이지에서 node_modules를 복사하는 것을 볼 수 있습니다.
TypeScript로 작성된 코드가 빌드되어 dist 폴더에 JavaScript로 변환되지만, 외부 라이브러리들은 여전히 필요하기 때문입니다. 실제 현업에서는 여기에 더해 헬스체크도 추가합니다.
쿠버네티스나 Docker Swarm이 컨테이너 상태를 모니터링하려면 HEALTHCHECK 명령어가 필요합니다. 또한 graceful shutdown을 위해 SIGTERM 시그널을 제대로 처리하는 코드도 작성해야 합니다.
주의할 점이 있습니다. 개발 의존성(devDependencies)이 프로덕션 이미지에 포함되면 안 됩니다.
npm ci --only=production을 사용하거나, 빌드 스테이지와 프로덕션 스테이지에서 npm ci를 각각 실행하는 방법이 있습니다. 김개발 씨가 이미지를 빌드하고 컨테이너를 실행해보았습니다.
docker exec로 들어가 whoami를 실행하니 nodejs가 출력되었습니다. "보안까지 챙기니까 더 든든하네요!"
실전 팁
💡 - 프로덕션에서는 반드시 비루트 사용자로 실행하세요
- HEALTHCHECK를 추가하여 컨테이너 상태를 모니터링하세요
- 환경변수로 설정을 주입하고, 비밀 정보는 Docker Secrets를 사용하세요
4. DB 컨테이너 설정
프론트엔드와 백엔드를 완성한 김개발 씨는 이제 PostgreSQL 데이터베이스를 설정합니다. 박시니어 씨가 진지하게 말합니다.
"데이터베이스는 특히 조심해야 해요. 데이터가 날아가면 끝이니까요.
볼륨 설정이 핵심입니다."
데이터베이스 컨테이너는 공식 이미지를 사용하되, 데이터 영속성을 위한 볼륨 설정이 필수입니다. 컨테이너가 삭제되어도 데이터가 유지되어야 하며, 초기 스키마와 시드 데이터를 자동으로 실행하는 설정도 필요합니다.
이는 마치 은행 금고처럼 데이터를 안전하게 보관하는 장치입니다.
다음 코드를 살펴봅시다.
# docker-compose.yml의 데이터베이스 부분
services:
db:
image: postgres:16-alpine
container_name: myapp-db
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
# 데이터 영속성을 위한 볼륨
- postgres_data:/var/lib/postgresql/data
# 초기화 스크립트 자동 실행
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
김개발 씨는 docker-compose.yml 파일을 열고 데이터베이스 서비스를 추가하기 시작했습니다. PostgreSQL 공식 이미지를 사용하면 Dockerfile을 따로 작성할 필요가 없습니다.
박시니어 씨가 강조했습니다. "데이터베이스에서 가장 중요한 건 볼륨이에요.
볼륨 없이 컨테이너를 돌리면, 컨테이너가 삭제될 때 모든 데이터가 함께 사라져요." 볼륨이란 정확히 무엇일까요? 쉽게 비유하자면, 볼륨은 마치 외장 하드디스크와 같습니다.
컴퓨터를 포맷해도 외장 하드디스크의 데이터는 그대로 남아있습니다. Docker 볼륨도 마찬가지로, 컨테이너가 삭제되거나 재생성되어도 볼륨에 저장된 데이터는 유지됩니다.
코드를 자세히 살펴보겠습니다. postgres:16-alpine 이미지는 PostgreSQL 16 버전의 경량 이미지입니다.
환경변수로 사용자명, 비밀번호, 데이터베이스명을 설정합니다. 이 값들은 .env 파일에서 읽어옵니다.
volumes 섹션에서 두 가지 마운트가 눈에 띕니다. 첫 번째는 postgres_data라는 명명된 볼륨입니다.
이 볼륨이 /var/lib/postgresql/data 경로에 마운트되어 모든 데이터베이스 파일을 저장합니다. 두 번째는 init.sql 파일입니다.
PostgreSQL 컨테이너는 /docker-entrypoint-initdb.d 폴더의 SQL 파일을 첫 실행 시 자동으로 실행합니다. healthcheck도 중요합니다.
pg_isready 명령어로 데이터베이스가 연결을 받을 준비가 되었는지 확인합니다. 다른 서비스들이 데이터베이스가 준비되기 전에 연결을 시도하면 오류가 발생하기 때문입니다.
실제 현업에서는 프로덕션 환경에서 포트를 외부에 노출하지 않습니다. 위 예제의 ports 설정은 개발 환경에서만 사용하고, 프로덕션에서는 같은 Docker 네트워크 안에서만 접근하도록 합니다.
주의할 점이 있습니다. 비밀번호를 docker-compose.yml에 직접 쓰면 안 됩니다.
.env 파일을 사용하되, 이 파일은 .gitignore에 추가해야 합니다. 프로덕션에서는 Docker Secrets나 외부 비밀 관리 도구를 사용합니다.
김개발 씨가 docker compose up -d로 데이터베이스를 실행했습니다. psql로 접속해보니 init.sql에 정의한 테이블들이 자동으로 생성되어 있었습니다.
"볼륨 덕분에 컨테이너를 지웠다 다시 만들어도 데이터가 그대로네요!"
실전 팁
💡 - 볼륨은 명명된 볼륨(named volume)을 사용하세요. 바인드 마운트보다 관리가 쉽습니다
- 프로덕션에서는 데이터베이스 포트를 외부에 노출하지 마세요
- 정기적인 백업 스크립트를 구성하여 볼륨 데이터를 백업하세요
5. Nginx 리버스 프록시
개별 서비스들이 모두 준비되었습니다. 이제 이들을 하나로 묶어줄 Nginx 리버스 프록시를 설정할 차례입니다.
박시니어 씨가 설명합니다. "리버스 프록시는 마치 호텔 컨시어지 같아요.
손님의 요청을 받아서 적절한 곳으로 안내해주죠."
Nginx 리버스 프록시는 클라이언트의 모든 요청을 받아 적절한 백엔드 서비스로 라우팅하는 역할을 합니다. /api로 시작하는 요청은 백엔드로, 나머지는 프론트엔드로 전달합니다.
이를 통해 단일 도메인으로 여러 서비스를 제공하고, SSL 인증서도 한 곳에서 관리할 수 있습니다.
다음 코드를 살펴봅시다.
# nginx/nginx.conf
upstream frontend {
server frontend:80;
}
upstream backend {
server backend:3000;
}
server {
listen 80;
server_name localhost;
# API 요청은 백엔드로
location /api {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 나머지는 프론트엔드로
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
}
}
김개발 씨는 프론트엔드가 3000번 포트, 백엔드가 4000번 포트에서 실행되는 상황이 불편했습니다. 사용자가 두 개의 포트를 기억해야 하고, CORS 설정도 복잡해졌습니다.
박시니어 씨가 해결책을 제시했습니다. "Nginx 리버스 프록시를 사용하면 80번 포트 하나로 모든 서비스에 접근할 수 있어요." 리버스 프록시란 정확히 무엇일까요?
쉽게 비유하자면, 리버스 프록시는 마치 큰 회사의 안내 데스크와 같습니다. 방문객이 "영업부 찾아왔습니다"라고 하면 영업부로, "기술지원 문의입니다"라고 하면 기술지원팀으로 안내합니다.
방문객은 안내 데스크만 알면 되고, 각 부서의 위치를 몰라도 됩니다. nginx.conf 파일을 자세히 살펴보겠습니다.
upstream 블록은 백엔드 서버 그룹을 정의합니다. frontend와 backend는 Docker Compose에서 정의한 서비스 이름입니다.
Docker의 내부 DNS가 이 이름을 실제 컨테이너 IP로 해석해줍니다. location /api 블록이 핵심입니다.
/api로 시작하는 모든 요청을 백엔드 서버로 전달합니다. proxy_set_header들은 원본 클라이언트 정보를 백엔드에 전달하는 역할을 합니다.
X-Real-IP가 없으면 백엔드에서는 모든 요청이 Nginx에서 온 것처럼 보입니다. location / 블록은 그 외 모든 요청을 프론트엔드로 보냅니다.
React의 SPA 라우팅을 위해 try_files 설정을 추가하기도 합니다. 실제 현업에서는 여기에 SSL 설정을 추가합니다.
Let's Encrypt로 무료 인증서를 발급받아 HTTPS를 적용합니다. 또한 gzip 압축, 캐시 헤더 설정 등 성능 최적화도 Nginx에서 담당합니다.
주의할 점이 있습니다. WebSocket을 사용한다면 별도의 설정이 필요합니다.
proxy_http_version 1.1과 Upgrade 헤더 설정을 추가해야 WebSocket 연결이 제대로 프록시됩니다. 김개발 씨가 브라우저에서 localhost를 열었습니다.
프론트엔드 화면이 나타났고, /api/users를 호출하니 백엔드에서 데이터가 왔습니다. "포트 번호 없이 깔끔하게 되네요!"
실전 팁
💡 - WebSocket 사용 시 Upgrade 헤더와 Connection 헤더를 추가로 설정하세요
- 프로덕션에서는 반드시 SSL을 적용하세요
- 정적 파일은 Nginx에서 직접 서빙하면 성능이 향상됩니다
6. 프로덕션 배포 완성
모든 조각이 맞춰졌습니다. 이제 docker-compose.yml을 완성하고 프로덕션 환경에 배포할 차례입니다.
박시니어 씨가 마지막으로 강조합니다. "개발 환경과 프로덕션 환경은 분리해야 해요.
그래야 안전하게 배포할 수 있습니다."
프로덕션 배포는 모든 서비스를 하나의 docker-compose.yml로 오케스트레이션하는 것입니다. 서비스 간 의존성을 정의하고, 네트워크를 구성하며, 환경변수를 관리합니다.
개발용과 프로덕션용 설정을 분리하여 각 환경에 맞는 최적화를 적용합니다.
다음 코드를 살펴봅시다.
# docker-compose.prod.yml
version: '3.8'
services:
nginx:
build: ./nginx
ports:
- "80:80"
- "443:443"
depends_on:
- frontend
- backend
networks:
- app-network
frontend:
build:
context: ./frontend
target: production
networks:
- app-network
backend:
build:
context: ./backend
target: production
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
depends_on:
db:
condition: service_healthy
networks:
- app-network
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
postgres_data:
김개발 씨는 드디어 마지막 단계에 도달했습니다. 지금까지 만든 모든 서비스를 하나로 묶어 프로덕션에 배포해야 합니다.
박시니어 씨가 화면을 보며 말했습니다. "docker-compose.yml은 오케스트라 지휘자 같아요.
각 악기가 언제 연주를 시작할지, 어떤 볼륨으로 연주할지 모두 지휘하죠." docker-compose.yml의 역할은 정확히 무엇일까요? 쉽게 비유하자면, docker-compose.yml은 마치 레고 조립 설명서와 같습니다.
어떤 블록을 어떤 순서로 조립해야 하는지, 각 블록이 어디에 연결되어야 하는지 모두 정의되어 있습니다. 이 설명서 하나면 누구나 똑같은 결과물을 만들 수 있습니다.
파일의 핵심 부분을 살펴보겠습니다. depends_on은 서비스 간 의존성을 정의합니다.
nginx는 frontend와 backend가 먼저 시작되어야 합니다. 더 중요한 것은 backend와 db의 관계입니다.
condition: service_healthy를 사용하면 데이터베이스의 healthcheck가 통과한 후에야 백엔드가 시작됩니다. networks 섹션에서 app-network라는 가상 네트워크를 만듭니다.
같은 네트워크에 속한 컨테이너들은 서비스 이름으로 서로를 찾을 수 있습니다. backend 컨테이너에서 db라는 호스트명으로 데이터베이스에 접근할 수 있는 이유입니다.
target: production은 멀티 스테이지 빌드에서 특정 스테이지만 빌드하도록 지정합니다. Dockerfile에서 AS production으로 명명한 스테이지까지만 빌드됩니다.
실제 배포는 어떻게 할까요? 서버에 접속해서 다음 명령어를 실행합니다.
docker compose -f docker-compose.prod.yml up -d --build 이 명령어 하나로 모든 이미지가 빌드되고, 컨테이너가 올바른 순서로 시작됩니다. 업데이트할 때도 같은 명령어를 실행하면 변경된 서비스만 다시 빌드되고 재시작됩니다.
주의할 점이 있습니다. 프로덕션에서는 .env 파일의 보안에 특히 신경 써야 합니다.
서버에 직접 .env 파일을 두거나, CI/CD 파이프라인에서 환경변수를 주입하는 방식을 사용합니다. 절대로 .env 파일을 Git에 커밋하면 안 됩니다.
김개발 씨가 서버에서 docker compose up -d를 실행했습니다. 몇 분 후, 모든 서비스가 올라오고 브라우저에서 접속이 되었습니다.
"드디어 완성이다!" 박시니어 씨가 축하하며 말했습니다. "이제 어디서든 이 docker-compose.yml 파일만 있으면 똑같은 환경을 구축할 수 있어요.
개발자의 로컬, 테스트 서버, 프로덕션 서버 모두 동일한 환경으로 실행되는 거죠." 김개발 씨는 Docker의 진정한 가치를 깨달았습니다. 환경 차이로 인한 버그는 이제 과거의 일이 되었습니다.
실전 팁
💡 - 개발용과 프로덕션용 docker-compose 파일을 분리하세요
- docker compose logs -f로 모든 서비스의 로그를 실시간으로 확인할 수 있습니다
- docker compose down -v를 실행하면 볼륨까지 삭제되니 주의하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Roundcube 웹메일 인터페이스 완벽 가이드
Docker 컨테이너 기반으로 Roundcube 웹메일을 구축하고, Nginx 리버스 프록시부터 플러그인 관리, 테마 커스터마이징까지 전체 과정을 다룹니다. 초급 개발자도 쉽게 따라할 수 있는 실무 중심 가이드입니다.
SSL/TLS 인증서 설정 완벽 가이드 (Let's Encrypt)
메일 서버 운영에 필수적인 SSL/TLS 인증서 설정 방법을 다룹니다. Let's Encrypt를 활용한 무료 인증서 발급부터 자동 갱신까지, 실무에서 바로 적용할 수 있는 내용을 담았습니다.
Docker 환경 준비 및 docker-mailserver 설치
나만의 메일 서버를 Docker로 구축하는 방법을 처음부터 끝까지 안내합니다. docker-mailserver를 활용하여 실무에서 바로 사용할 수 있는 메일 시스템을 단계별로 설정해봅니다.
메일 서버 아키텍처 설계 완벽 가이드
메일 서버를 직접 구축하고 운영하기 위한 아키텍처 설계 방법을 다룹니다. MTA, MDA부터 고가용성 설계까지 실무에서 필요한 모든 것을 초급자도 이해할 수 있도록 쉽게 설명합니다.
Docker 보안 베스트 프랙티스 완벽 가이드
Docker 컨테이너 환경에서 보안을 강화하는 필수 기법들을 다룹니다. 루트 사용자 제한부터 시크릿 관리, 리소스 제한까지 실무에서 바로 적용할 수 있는 보안 설정을 배워봅니다.