
왜 개발 환경의 설정을 운영 서버로 그대로 가져가면 안 될까요
새벽 2시, 야심 차게 준비한 서비스를 클라우드 서버에 배포하려고 도커 컴포즈 명령어를 입력했어요. 그런데 예상치 못한 오류가 발생하며 빌드가 실패해요. 서버 환경에 필요한 의존성이 부족하거나, 로컬에만 있는 파일 경로를 참조하고 있기 때문이에요. 이런 상황은 1인 개발자나 소규모 팀이 겪는 가장 흔하고도 뼈아픈 실수 중 하나예요.
대부분의 개발자는 로컬에서 build 옵션을 사용해 편리하게 컨테이너를 띄워요. 하지만 운영 서버는 환경이 달라요. 서버에서 직접 소스 코드를 빌드하는 방식은 시간이 너무 오래 걸릴 뿐만 아니라, 빌드할 때마다 환경이 미세하게 달라져 서비스의 안정성을 해칠 수 있어요. 그래서 우리는 컴포즈 image 옵션 실전 구축 방식을 익혀야 해요.
이 방식의 핵심은 미리 빌드된 완성품(Image)을 레지스트리에 올려두고, 운영 서버에서는 이를 내려받아 바로 실행만 하는 거예요. 이렇게 하면 배포 속도가 비약적으로 빨라지고, 어떤 서버에서 실행하든 동일한 환경을 보장할 수 있어요. 서버 이전이나 확장 시에도 설정 파일만 있으면 즉시 서비스가 재개되는 마법을 경험하게 돼요.
오늘 가이드를 통해 다음 내용들을 확실히 마스터할 수 있어요.
- 빌드 방식과 이미지 방식의 결정적인 차이점 이해하기
- 이미지를 레지스트리에 안전하게 푸시하고 관리하는 법
- 운영 서버용 도커 컴포즈 파일 작성 노하우
- 배포 전 반드시 확인해야 할 체크리스트
본격적인 구축 전 반드시 갖춰야 할 사전 준비 사항
무작정 명령어를 입력하기 전에, 우리 서비스에 어떤 방식이 적합한지 판단할 기준이 필요해요. 이미지 기반 배포를 위해서는 단순히 도커만 설치해서는 안 돼요. 이미지를 보관하고 전달할 중간 기지가 필요하기 때문이에요.
필수 준비물과 용어 정리
먼저 도커 레지스트리(Docker Registry)가 필요해요. Docker Hub 같은 공용 저장소를 쓸 수도 있지만, 보안이 중요하다면 AWS ECR이나 GitHub Packages 같은 프라이빗 저장소를 고려해야 해요. 또한, 서비스의 버전을 관리할 수 있는 태그(Tag) 전략도 미리 세워두어야 해요. 단순히 latest라는 태그만 쓰다가는 나중에 어떤 버전이 운영 중인지 알 수 없는 재앙을 맞이할 수 있어요.
레지스트리는 이미지를 저장하는 창고예요. 개발자가 만든 이미지를 이 창고에 넣어두면, 운영 서버가 이 창고에서 이미지를 가져가(Pull) 사용하게 되는 구조랍니다.
빌드 방식 vs 이미지 방식 비교
현재 여러분의 운영 방식이 어떤 단계인지 아래 표를 보고 판단해 보세요. 소규모 서비스라면 단계적으로 넘어가는 것을 추천해요.
| 비교 항목 | build 옵션 활용 (개발용) | image 옵션 활용 (운영용) |
|---|---|---|
| 배포 속도 | 소스 코드 빌드 시간이 필요해 느림 | 이미 다운로드된 이미지를 쓰므로 매우 빠름 |
| 일관성 | 서버 환경에 따라 빌드 결과가 달라질 위험 있음 | 검증된 동일한 이미지를 그대로 사용함 |
| 서버 요구 사양 | 컴파일러, 소스 코드 등 높은 자원 필요 | 도커 런타임만 있으면 됨 (가벼움) |
| 주요 용도 | 로컬 개발, 코드 수정 즉시 반영 | 스테이징, 프로덕션 환경 배포 |
만약 현재 서버에서 코드를 직접 빌드하고 있다면, 운영 안정성을 위해 반드시 image 옵션으로 전환하는 것을 권장해요. 서버의 CPU와 메모리를 빌드 과정에서 낭비하지 않고, 오직 서비스 제공에만 집중할 수 있기 때문이에요.
실전! 컴포즈 image 옵션 기반 배포 프로세스 5단계
이제 이론을 넘어 실제로 서비스를 구축해 볼 시간이에요. 1인 개발자라도 이 흐름만 익혀두면 대규모 클러스터 운영 부럽지 않은 체계적인 배포가 가능해져요. 실제 Node.js 애플리케이션을 예시로 들어 단계별로 설명해 드릴게요.
STEP 1. 디렉터리와 파일 구조 설계하기
체계적인 관리를 위해 프로젝트 폴더 구조를 명확히 나누어야 해요. 단순히 파일들이 섞여 있으면 나중에 어떤 이미지가 어떤 설정 파일과 연결되는지 헷갈리게 돼요. 운영 환경과 개발 환경을 구분하는 것이 첫걸음이에요.
가장 권장하는 구조는 다음과 같아요.
/my-project: 전체 프로젝트 루트/my-project/src: 애플리케이션 소스 코드/my-project/Dockerfile: 이미지 빌드 정의 파일/my-project/docker-compose.yml: 개발 환경용 컴포즈 파일/my-project/deploy/docker-compose.prod.yml: 운영 서버용 컴포즈 파일/my-project/.env: 환경 변수 관리 파일
이렇게 구조를 잡으면, 개발 중에는 루트의 파일을 쓰고 운영 서버에서는 deploy 폴더 안의 파일을 사용하게 되어 실수를 원천 차단할 수 있어요.
STEP 2. 서비스 정의와 의존관계 구성하기
운영 서버용 컴포즈 파일인 docker-compose.prod.yml을 작성할 차례예요. 여기서 가장 중요한 점은 build 명령어를 과감히 삭제하고 image 옵션을 넣는 것이에요.
예시 코드를 살펴볼까요?
services:
web-app:
image: my-registry.com/my-user/my-app:v1.0.2 # 빌드 대신 이미지를 직접 지정
ports:
- "80:3000"
restart: always
env_file:
- ../.env
depends_on:
- database
database:
image: postgres:15-alpine
volumes:
- db_data:/var/lib/postgresql/data
위 설정에서 주목할 점은 image 부분이에요. 레지스트리 주소, 사용자명, 앱 이름, 그리고 버전 태그(v1.0.2)가 포함되어야 해요. 이렇게 작성하면 서버는 빌드를 시도하지 않고, 지정된 레지스트리에서 해당 버전을 찾아 즉시 내려받아요.
STEP 3. 이미지 빌드와 레지스트리 푸시하기
이제 로컬 컴퓨터에서 이미지를 만들고 창고(레지스트리)로 옮겨야 해요. 이 과정은 서비스 코드가 수정될 때마다 수행하게 돼요. 총 3단계의 명령어로 이루어져요.
- 이미지 빌드:
docker build -t my-app:local .(로컬에서 먼저 확인용으로 빌드해요.) - 태그 생성:
docker tag my-app:local my-registry.com/my-user/my-app:v1.0.2(레지스트리 주소에 맞게 이름을 바꿔줘요.) - 이미지 푸시:
docker push my-registry.com/my-user/my-app:v1.0.2(창고로 이미지를 전송해요.)
이때 태그를 latest로만 관리하면, 나중에 새로운 버전이 나왔을 때 이전 버전으로 되돌리는(Rollback) 것이 매우 힘들어져요. 반드시 v1.0.1, v1.0.2 처럼 숫자로 된 태그를 사용하는 습관을 들이세요.
STEP 4. 운영 서버에서 이미지 불러오기와 실행하기
이미지가 레지스트리에 잘 올라갔다면, 이제 실제 운영 서버(VPS 등)에 접속할 차례예요. 서버에서는 복잡한 소스 코드가 필요 없어요. 오직 docker-compose.prod.yml 파일과 .env 파일만 있으면 돼요.
실행 순서는 다음과 같아요.
- 레지스트리 로그인:
docker login my-registry.com(프라이빗 저장소라면 인증이 필수예요.) - 이미지 가져오기:
docker compose -f deploy/docker-compose.prod.yml pull(새로운 이미지를 미리 내려받아요.) - 컨테이너 실행:
docker compose -f deploy/docker-compose.prod.yml up -d(백그라운드에서 서비스를 실행해요.)
pull 명령어를 먼저 실행하는 이유는 서비스 중단 시간을 최소화하기 위해서예요. 이미지를 미리 다 받아놓은 상태에서 up을 하면, 컨테이너 교체 시 발생하는 찰나의 공백을 줄일 수 있답니다.STEP 5. 운영 전환 전 최종 점검 항목
배포 직전, 마지막으로 이 질문들에 스스로 답해 보세요. 하나라도 막힌다면 배포를 잠시 멈춰야 해요.
- 운영 서버의 환경 변수(.env) 파일에 데이터베이스 비밀번호나 API 키가 제대로 들어있는가?
- 데이터 보존을 위한 볼륨(Volume) 설정이 올바르게 되어 있는가? (이게 없으면 컨테이너 재시작 시 데이터가 다 날아가요!)
- 방화벽에서 서비스 포트(예: 80, 443)가 열려 있는가?
- 이미지 태그가
latest가 아닌 특정 버전으로 명시되어 있는가?
이 과정만 거치면 여러분의 서비스는 훨씬 견고하고 전문적인 배포 프로세스를 갖추게 된 거예요.
자주 하는 실수와 해결법 및 궁금한 점 해결하기
실전 구축 과정에서는 누구나 실수를 해요. 하지만 어떤 실수를 하는지 미리 안다면 당황하지 않고 빠르게 복구할 수 있어요.
자주 하는 실수와 해결법
❌ 실수: 운영 서버 컴포즈 파일에 build 경로를 남겨둔 경우
왜 발생하는가: 로컬 개발 환경의 설정을 그대로 복사해서 붙여넣기 때문이에요.
✅ 해결법: 운영용 파일에서는 build: . 문구를 삭제하고, 반드시 image: [저장소주소/이미지명:태그] 형태로 수정해야 해요.
❌ 실수: 레지스트리 로그인을 하지 않고 pull을 시도한 경우
왜 발생하는가: 프라이빗 레지스트리를 사용하면서도 인증 단계를 간과하기 때문이에요.
✅ 해결법: 배포 전 반드시 docker login 명령어로 인증을 완료하세요.
❌ 실수: 모든 이미지 태그를 ‘latest’로 통일한 경우
왜 발생하는가: 버전 관리가 귀찮아서 가장 편한 방법을 택했기 때문이에요.
✅ 해결법: 절대로 latest만 쓰지 마세요. v1.0.1, v1.0.2처럼 숫자를 붙여야 문제가 생겼을 때 즉시 이전 버전으로 되돌릴 수 있어요.
❌ 실수: 환경 변수(.env) 파일을 서버에 올리지 않은 경우
왜 발생하는가: 보안을 위해 .gitignore에 넣는 것을 잊어버리기 때문이에요.
✅ 해결법: .env 파일은 Git에 올리지 않되, 서버에는 직접 수동으로 생성하거나 보안 저장소를 통해 전달해야 해요.
❌ 실수: 컨테이너 내부의 데이터가 사라지는 현상
왜 발생하는가: 데이터베이스 데이터를 이미지 내부에 저장했기 때문이에요.
✅ 해결법: 반드시 호스트 디렉터리와 볼륨(Volume)을 연결하여 데이터가 컨테이너 외부(서버 디스크)에 남도록 설정하세요.
자주 묻는 질문
Q. 이미지를 업데이트했는데 서버에 반영이 안 돼요. 어떻게 하나요?
가장 먼저 docker compose pull 명령어를 통해 새로운 이미지를 받아왔는지 확인해야 해요. 그 다음 docker compose up -d를 실행하면 컨테이너가 새 이미지로 교체돼요. 만약 태그가 같다면 docker compose up -d --force-recreate를 사용해 보세요.
Q. build와 image 옵션을 한 파일에 동시에 쓸 수 있나요?
네, 가능해요! 보통 로컬 개발용 컴포즈 파일에서는 두 옵션을 모두 써서, 이미지를 직접 빌드할 수도 있고 이미 빌드된 이미지를 가져올 수도 있게 구성해요. 하지만 운영 서버용 파일에는 반드시 image만 남겨두는 것이 원칙이에요.
Q. 프라이빗 레지스트리를 쓰면 비용이 많이 드나요?
사용량에 따라 다르지만, 소규모 서비스라면 AWS ECR의 프리 티어나 GitHub Packages의 무료 용량 범위 내에서 충분히 운영 가능해요. 보안을 생각한다면 비용을 조금 지불하더라도 프라이빗 환경을 쓰는 것이 훨씬 이득이에요.
Q. 배포 중에 서비스가 잠시 끊기는 게 걱정돼요. 방법이 없을까요?
완벽한 무중단 배포는 조금 더 복잡한 설정(Nginx 로드밸런싱 등)이 필요하지만, docker compose pull을 먼저 실행해 이미지를 다 받아놓는 것만으로도 서비스 중단 시간을 획기적으로 줄일 수 있어요.
Q. 태그를 어떻게 정하는 게 가장 효율적인가요?
가장 추천하는 방식은 SemVer(유의적 버전) 방식이에요. major.minor.patch(예: 1.2.3) 형식을 사용하면, 기능 추가인지 버그 수정인지 직관적으로 알 수 있어 관리가 매우 편해져요.
지속 가능한 운영을 위한 마지막 한 걸음
지금까지 컴포즈 image 옵션을 활용해 안정적인 서버 운영 환경을 구축하는 방법을 알아보았어요. 처음에는 과정이 복잡해 보일 수 있지만, 한 번 이 체계를 잡아두면 서비스 규모가 커져도 흔들리지 않는 탄탄한 기반이 될 거예요.
- 개발 환경에서는 build, 운영 환경에서는 image 옵션을 사용하세요.
- 이미지는 반드시 레지스트리에 푸시하여 관리하세요.
- 태그는 latest 대신 v1.0.0 같은 버전을 명시하세요.
- 운영 서버 배포 전 환경 변수와 볼륨 설정을 꼭 확인하세요.
- 배포 시에는 pull을 먼저 실행해 다운로드 시간을 확보하세요.
오늘 배운 내용을 바탕으로 당장 실천해 볼 수 있는 계획을 제안할게요.
- 오늘 할 일: 현재 프로젝트의 컴포즈 파일에서 build 옵션을 제거하고 image 옵션으로 바꾸는 시뮬레이션을 해보세요.
- 이번 주 할 일: 작은 테스트용 레지스트리를 만들고, 이미지를 빌드해서 푸시하는 전 과정을 직접 완료해 보세요.
- 실행 직전 할 일: 운영 서버의 환경 변수 파일(.env)이 최신 상태인지 점검하세요.
구축 절차를 문서로 남겨 두면 다음 서버 이전이나 팀원이 늘어났을 때 훨씬 쉽게 대응할 수 있어요. 여러분의 서비스가 더 넓은 세상으로 나아갈 수 있도록, 오늘 알려드린 단계를 차근차근 적용해 보세요!
함께 읽으면 좋은 글: 도커 컴포즈 기본 개념 완벽 정리 — 개념부터 실무 활용까지 한눈에 보는 가이드