
운영 서버가 멈추는 순간, 무엇이 잘못되었을까요?
야심 차게 준비한 서비스를 서버에 올렸는데, 갑자기 데이터베이스가 응답하지 않거나 웹 서버가 무한 재부팅에 빠지는 경험을 해보셨나요? 아마도 컨테이너 간의 연결 고리가 느슨하거나, 서비스가 실행되는 순서가 엉망이었을 확률이 높아요. 컴포즈 services 실전 구축 과정에서 가장 빈번하게 발생하는 이 문제는 단순히 명령어를 한두 번 잘못 입력해서 생기는 일이 아니에요.
많은 1인 개발자분들이 처음에는 도커 명령어로 컨테이너를 하나씩 띄우는 데 집중해요. 하지만 서비스 규모가 커지고 관리해야 할 컨테이너가 늘어나면, 각 컨테이너가 어떤 환경 변수를 쓰는지, 서로 어떻게 통신하는지 일일이 기억하기란 불가능에 가까워요. 결국 설정 파일 하나가 꼬이면서 서비스 전체가 마비되는 상황이 발생하죠.
이 글은 바로 그런 혼란을 막기 위해 준비했어요. 단순히 명령어를 나열하는 가이드가 아니라, 실제 운영 환경에서 서비스들이 어떻게 유기적으로 움직여야 하는지 그 설계 원칙을 다뤄요. 이 과정을 잘 따라오시면, 서버를 재시작하더라도 모든 서비스가 정해진 순서대로, 정해진 설정대로 완벽하게 살아나는 경험을 하시게 될 거예요.
도커 컴포즈의 services 블록은 단순한 목록이 아니에요. 각 서비스의 생명 주기, 네트워크 위치, 데이터 저장소, 그리고 서비스 간의 의존 관계를 정의하는 서버 설계도라고 이해하는 것이 정확해요.
이번 가이드에서는 다음과 같은 내용을 구체적으로 살펴볼 예정이에요.
- 실패 없는 프로젝트 디렉터리 구조 설계법
- 서비스 간 의존 관계와 네트워크 설정 노하우
- 데이터 유실을 막는 볼륨 구성 전략
- 운영 전환 전 반드시 확인해야 할 체크리스트
안정적인 구축을 위한 사전 준비와 판단 기준
본격적으로 코드를 작성하기 전에, 우리가 어떤 환경에서 싸워야 하는지 먼저 파악해야 해요. 무턱대고 파일을 만들기 시작하면 나중에 환경 변수가 꼬이거나 경로 문제로 고생하기 십상이에요. 컴포즈 services 실전 구축을 시작하기 전에는 반드시 자신의 서비스 규모와 운영 환경을 냉정하게 분석해야 해요.
필수 준비물 및 환경 점검
가장 먼저 서버에 Docker와 Docker Compose가 최신 버전으로 설치되어 있는지 확인하세요. 버전이 너무 낮으면 최신 문법인 depends_on의 헬스체크 기능을 제대로 활용하지 못할 수도 있거든요. 또한, 서비스에서 사용할 환경 변수들을 따로 관리할 수 있는 .env 파일의 관리 계획도 미리 세워두는 것이 좋아요.
서비스 구성 방식 선택하기
프로젝트의 성격에 따라 구성 방식이 달라져야 해요. 모든 것을 하나의 파일에 담을지, 아니면 서비스별로 분리할지를 결정하는 것이 첫 번째 관문이에요. 아래 표를 보고 현재 여러분의 상황에 가장 적합한 모델을 선택해 보세요.
| 구성 모델 | 주요 특징 | 추천 대상 | 주의사항 |
|---|---|---|---|
| 단일 파일 모델 | 모든 서비스를 하나의 docker-compose.yml에 정의 | 소규모 토이 프로젝트, 1인 개발자 | 파일이 길어지면 가독성 저하 |
| 분리형 모델 | 기능별로 compose 파일을 나누고 프로파일 사용 | 중규모 이상, 마이크로서비스 지향 | 서비스 간 네트워크 연결 설정 복잡 |
| 멀티 서버 모델 | 여러 대의 서버에 서비스를 분산 배치 | 트래픽이 높은 상용 서비스 | Docker Swarm이나 K8s 검토 필요 |
대부분의 1인 개발자분들은 단일 파일 모델로 시작하는 것이 관리 효율성 측면에서 가장 유리해요. 하지만 서비스가 늘어날 것을 대비해 디렉터리 구조만큼은 미리 깔끔하게 정리해 두는 습관이 필요해요.
환경 변수를 절대 docker-compose.yml 파일 안에 직접 적지 마세요. 보안 사고의 가장 흔한 원인이며, 나중에 서버를 이전할 때 모든 파일을 일일이 수정해야 하는 재앙을 초래할 수 있어요.
실전 구축: 서비스 설계부터 실행까지의 5단계
이제 본격적으로 컴포즈 services 실전 구축을 시작해 볼게요. 이 과정은 단순히 코드를 타이핑하는 것이 아니라, 컨테이너들이 서로 어떻게 대화하고 데이터를 어디에 쌓을지 결정하는 매우 중요한 설계 작업이에요.
STEP 1. 체계적인 디렉터리 구조 설계하기
파일을 아무 데나 두면 나중에 볼륨을 마운트하거나 설정을 불러올 때 경로 문제로 고통받게 돼요. 프로젝트 루트 디렉터리를 기준으로 기능별로 명확하게 구분하는 것이 원칙이에요. 추천하는 구조는 다음과 같아요.
./config: 각 서비스의 설정 파일(nginx.conf, my.cnf 등)을 보관해요../data: 데이터베이스 파일이나 사용자 업로드 파일이 저장되는 곳이에요../logs: 컨테이너 로그를 파일로 남기고 싶을 때 사용해요../src: 실제 애플리케이션 소스 코드가 들어가는 공간이에요.
이렇게 미리 공간을 나누어 두면, 나중에 volumes 설정을 할 때 경로를 관리하기가 훨씬 수월해져요. 서버를 통째로 백업할 때도 이 루트 디렉터리만 챙기면 되니 매우 편리하죠.
STEP 2. 서비스 정의와 이미지 전략 세우기
services 블록의 핵심은 각 컨테이너가 무엇을 할지 정의하는 것이에요. 이때 이미지(image)를 어떻게 가져올지 결정해야 해요. 이미 만들어진 이미지를 사용할 것인지, 아니면 내가 만든 Dockerfile로 직접 빌드할 것인지 선택해야 하죠.
예를 들어, 웹 서버는 nginx:stable-alpine 같은 공식 이미지를 사용하는 것이 안정적이에요. 하지만 내가 직접 만든 파이썬이나 노드 애플리케이션은 build: . 설정을 통해 현재 디렉터리의 소스 코드를 바탕으로 직접 이미지를 생성하도록 구성해야 해요. 이때 restart 정책은 반드시 unless-stopped로 설정하는 것을 추천해요. 그래야 서버가 갑자기 재부팅되어도 내가 수동으로 끈 서비스가 아니라면 자동으로 다시 살아나거든요.
STEP 3. 네트워크와 데이터 볼륨 연결하기
컨테이너들이 서로를 찾지 못해 헤매는 상황은 정말 허망해요. 도커 컴포즈는 기본적으로 하나의 네트워크를 생성해 서비스들을 묶어주지만, 보안을 위해 서비스별로 네트워크를 분리하는 것이 좋아요. 예를 들어, 웹 서버는 사용자에게 노출되는 frontend 네트워크에, 데이터베이스는 내부 전용인 backend 네트워크에 배치하는 방식이죠.
데이터 저장 또한 매우 중요해요. 컨테이너는 삭제되면 내부 데이터도 함께 사라지기 때문에, 반드시 볼륨(Volume)을 설정해야 해요. 데이터베이스의 경우, 호스트의 특정 디렉터리와 컨테이너 내부의 데이터 경로를 연결하는 바인드 마운트(Bind Mount) 방식을 사용하면 호스트 서버에서 직접 데이터를 백업하거나 검사하기가 매우 쉬워져요.
STEP 4. 의존성 관리와 헬스체크 구현
가장 많은 실수가 발생하는 지점이 바로 여기예요.
자주 하는 실수와 해결법 + FAQ
실전 구축 과정에서는 예상치 못한 변수가 끊임없이 나타나요. 많은 개발자가 같은 지점에서 머리를 싸매곤 하죠. 제가 직접 겪으며 정리한 실패 사례들을 통해 여러분의 시행착오를 줄여드릴게요.
자주 하는 실수와 해결법
❌ 실수: 컨테이너를 띄웠는데 DB에 저장한 데이터가 사라졌어요.
왜 발생하는가: 볼륨(Volumes) 설정을 하지 않고 컨테이너 내부 경로에만 데이터를 저장했기 때문이에요. 컨테이너는 휘발성이라 삭제 시 데이터도 함께 날아가요.
✅ 해결법: 반드시 호스트 디렉터리나 명명된 볼륨(Named Volume)을 사용하여 데이터를 외부로 빼두세요.
❌ 실수: 웹 서버에서 DB 서비스 이름으로 접속이 안 돼요.
왜 발생하는가: 서비스들이 서로 다른 네트워크에 격리되어 있거나, 오타로 인해 잘못된 서비스 이름을 사용했기 때문이에요.
✅ 해결법: 모든 관련 서비스가 동일한 networks 설정 안에 있는지 확인하고, 도커 컴포즈가 부여한 서비스 이름을 정확히 입력하세요.
❌ 실수: 포트 충돌로 컨테이너 실행이 거부돼요.
왜 발생하는가: 호스트 서버에서 이미 다른 프로세스(예: 이미 설치된 Nginx)가 해당 포트를 사용 중이기 때문이에요.
✅ 해결법: ports: ["8080:80"]처럼 호스트 포트를 변경하여 충돌을 피하세요.
❌ 실수: 환경 변수가 적용되지 않아요.
왜 발생하는가: .env 파일을 수정했지만, 컨테이너를 다시 생성(down 후 up)하지 않았거나 파일 경로가 잘못되었기 때문이에요.
✅ 해결법: 설정 변경 후에는 반드시 docker-compose up -d를 다시 실행하여 컨테이너를 재생성하세요.
❌ 실수: 빌드 과정에서 권한 오류(Permission Denied)가 발생해요.
왜 발생하는가: 호스트의 디렉터리 권한과 컨테이너 내부 사용자의 UID/GID가 일치하지 않기 때문이에요.
✅ 해결법: 호스트 디렉터리의 권한을 조정하거나, Dockerfile 내에서 USER 명령어를 통해 권한을 적절히 맞추어 주세요.
자주 묻는 질문
Q. 컴포즈 파일을 여러 개로 나누면 관리가 더 힘들어지지 않나요?
처음에는 파일 하나가 편할 수 있지만, 서비스가 5개 이상 넘어가면 한 눈에 파악하기 어려워져요. 기능별로 나누고 extends나 profiles 기능을 활용하면 훨씬 체계적으로 관리할 수 있어요.
Q. 운영 서버에서는 반드시 특정 태그를 사용해야 하나요?
네, 맞아요. latest 태그는 사용하지 않는 것이 원칙이에요. 최신 버전이 업데이트될 때마다 내가 의도하지 않은 환경 변화가 생길 수 있으니까요. 반드시 nginx:1.25-alpine처럼 명확한 버전을 명시하세요.
Q. 컨테이너 로그를 어떻게 효율적으로 확인할 수 있을까요?
단순히 docker-compose logs -f를 쓰는 것도 좋지만, 서비스가 많다면 docker-compose logs -f [서비스명]을 사용하여 특정 서비스의 로그만 필터링해서 보는 습관을 들이는 게 좋아요.
Q. 볼륨을 사용할 때 성능 저하가 있지는 않나요?
리눅스 환경에서는 거의 체감하기 어렵지만, macOS나 Windows에서 Docker Desktop을 사용하는 경우 파일 시스템 브릿지 때문에 성능 저하가 있을 수 있어요. 이럴 땐 Named Volume을 사용하는 것이 성능 면에서 훨씬 유리해요.
완벽한 구축을 위한 마지막 점검과 다음 단계
긴 여정을 마치느라 고생 많으셨어요. 이제 여러분의 서버에는 단순히 컨테이너가 떠 있는 것이 아니라, 서로 긴밀하게 소통하며 스스로를 보호할 준비가 된 견고한 서비스 인프라가 자리 잡고 있을 거예요. 마지막으로 놓친 것은 없는지 체크리스트를 통해 점검해 보세요.
- 디렉터리 구조를 기능별(config, data, logs)로 미리 나누었나요?
- 민감한 정보가 .env 파일로 분리되어 있고 Git에서 제외되었나요?
- 서비스 간 의존 관계에 헬스체크(healthcheck)를 적용했나요?
- 데이터 보존을 위한 볼륨(Volumes) 설정이 완료되었나요?
- 이미지 태그를 ‘latest’ 대신 구체적인 버전으로 지정했나요?
- 서비스별로 필요한 네트워크가 적절히 격리되어 있나요?
이제 실전으로 나갈 시간이에요. 당장 오늘 해야 할 일과 이번 주에 준비해야 할 로드맵을 제안해 드릴게요.
앞으로의 실행 계획
- 오늘 할 일: 현재 운영 중인 서비스의 구조를 종이에 그려보세요. 어떤 서비스가 어떤 데이터와 연결되는지 시각화하는 것만으로도 설계 오류의 80%를 잡을 수 있어요.
- 이번 주 할 일: 위에서 배운 대로 새로운 프로젝트를 만들어 직접
docker-compose.yml파일을 작성하고, 서버에 배포해 보세요. - 실행 직전 할 일: 구축한 서비스의 데이터 볼륨이 실제로 호스트 서버에 잘 생성되었는지, 권한 문제는 없는지 반드시 직접 확인하세요.
구축 절차를 꼼꼼히 문서로 남겨 두면, 나중에 서버를 이전하거나 새로운 기능을 추가할 때 훨씬 쉽고 빠르게 대응할 수 있어요. 오늘 만든 이 설계도가 여러분의 서비스 운영을 지탱하는 가장 강력한 무기가 될 거예요.
더 자세한 컨테이너 활용법이 궁금하다면, 도커 컴포즈 기본 개념 완벽 정리 — 개념부터 실무 활용까지 한눈에 보는 가이드를 함께 읽어보시는 것을 추천해요.