
갑자기 맡게 된 서버 관리, 서비스 블록부터 막히나요?
어느 날 갑자기 선임자로부터 “이제 서버 운영은 담당해 주세요”라는 말을 들었다면 정말 눈앞이 캄캄할 거예요. 특히 모니터를 가득 채운 검은 화면과 알 수 없는 영어 단어들이 가득한 docker-compose.yml 파일을 마주했을 때의 그 막막함은 경험해 본 사람만 알 수 있어요.
파일을 열어보니 가장 먼저 보이는 건 services:라는 문구예요. 이 밑에 적힌 수많은 설정값이 대체 무슨 뜻인지, 하나라도 잘못 적으면 서버가 통째로 멈추는 건 아닌지 걱정이 앞서기 마련이죠. 기존 개발자들에게는 숨 쉬듯 당연한 용어들이지만, 비전공자나 서버 운영 초보자에게는 외계어처럼 느껴지는 게 당연해요.
하지만 너무 걱정하지 마세요. 서비스 블록은 생각보다 아주 논리적이고 규칙적인 구조로 이루어져 있어요. 이 규칙만 이해하면 복잡해 보이던 설정값들도 마치 잘 짜인 레시피처럼 읽히기 시작할 거예요. 오늘 이 글을 끝까지 읽고 나면, 더 이상 서비스 블록 앞에서 머뭇거리지 않고 자신 있게 설정을 수정할 수 있게 돼요.
이 글에서는 다음과 같은 내용을 차근차근 다룰 예정이에요.
- 서비스 블록을 구성하는 핵심 용어의 쉬운 정의
- 설정값에 따라 컨테이너가 어떻게 변하는지 보여주는 비교표
- 실제 코드를 활용한 단계별 서비스 구성 방법
- 실무에서 가장 자주 겪는 설정 오류와 해결책
본격적인 시작 전, 꼭 알아야 할 기본 개념 정리
설정 파일을 만지기 전에 우리가 사용하는 언어부터 통일할 필요가 있어요. 도커 컴포즈(Docker Compose)라는 도구는 여러 개의 컨테이너를 하나의 묶음으로 관리해 주는 비서 같은 존재예요. 그리고 그 비서가 관리하는 개별 작업 단위가 바로 service예요.
서비스 블록을 작성할 때 가장 헷갈리는 세 가지 개념을 먼저 확실히 짚고 넘어갈게요. 이 차이를 모르면 나중에 “왜 제가 설정한 대로 작동하지 않죠?”라는 질문을 스스로 던지게 될 수도 있어요.
| 개념 | 비유하자면? | 핵심 역할 |
|---|---|---|
| 이미지(Image) | 요리 레시피 | 프로그램을 실행하기 위한 모든 재료와 설명서 |
| 컨테이너(Container) | 실제 요리 | 이미지를 바탕으로 실제로 살아 움직이는 프로세스 |
| 서비스(Service) | 주문 시스템 | 어떤 이미지를 쓰고, 어떤 포트를 열지 정의한 설정값 |
서비스 블록은 결국 “어떤 레시피(Image)를 사용해서, 어떤 환경(Environment)을 만들고, 어떤 문(Port)을 열어줄 것인가”를 적어두는 명세서라고 생각하면 쉬워요.
컴포즈 파일은 YAML 형식을 사용해요. YAML에서 가장 무서운 건 들여쓰기(Indentation)예요. 탭(Tab) 대신 스페이스(Space)를 사용하는 것이 원칙이며, 줄 간격이 한 칸이라도 어긋나면 컴포즈는 명령을 이해하지 못하고 에러를 뱉어내요.
이제 준비는 끝났어요. 본격적으로 서비스 블록의 내부를 하나씩 파헤쳐 볼게요.
서비스 블록 완벽 정복: 단계별 설정 가이드
이제 실제 docker-compose.yml 파일의 속으로 들어가 볼 시간이에요. 서비스 블록 하나를 제대로 만들기 위해 거쳐야 하는 5단계를 차근차근 설명해 드릴게요.
STEP 1. 서비스의 이름과 이미지 결정하기
서비스 블록의 시작은 이름표를 붙이는 일이에요. services: 아래에 적는 첫 번째 이름은 여러분이 관리하기 편한 이름이면 돼요. 예를 들어 웹 서버라면 web:이라고 적으면 충분해요.
그다음 가장 중요한 것이 image 설정이에요. 이건 어떤 프로그램을 가져올지를 정하는 건데, 마치 앱스토어에서 앱을 다운로드받는 것과 비슷해요. image: nginx:latest라고 적으면 최신 버전의 Nginx 웹 서버를 가져오겠다는 뜻이 돼요. 여기서 주의할 점은 버전(Tag)을 명시하는 습관이에요. 단순히 latest라고만 적으면 나중에 업데이트될 때 예기치 못한 문제가 생길 수 있으니, 가급적 특정 숫자가 적힌 버전을 사용하는 게 안전해요.
STEP 2. 외부와 소통할 통로, 포트(Ports) 설정하기
이미지를 가져왔다고 해서 바로 웹사이트가 보이는 건 아니에요. 컴퓨터 내부의 특정 문을 열어서 외부 사람이 들어올 수 있게 해줘야 하는데, 이게 바로 ports 설정이에요. 포트 설정은 항상 호스트_포트:컨테이너_포트 형식을 따라요.
예를 들어 ports: - "8080:80"이라고 적었다면, 외부 사용자가 내 서버의 8080번 포트로 접속했을 때 컨테이너 내부의 80번 포트로 연결해 주겠다는 의미예요. 마치 아파트 호수(컨테이너 포트)로 가기 위해 단지 정문(호스트 포트)의 특정 게이트를 통과하는 것과 같아요.
STEP 3. 개인화된 설정값, 환경 변수(Environment) 넣기
프로그램을 실행할 때 비밀번호나 데이터베이스 주소처럼 매번 바뀌는 정보들이 있죠? 이걸 일일이 코드에 적으면 보안에도 안 좋고 수정도 힘들어요. 이럴 때 쓰는 것이 environment예요. 서비스 블록 안에 환경 변수를 정의해 두면, 프로그램이 실행될 때 그 값을 읽어서 동작하게 돼요. 예를 들어 데이터베이스의 관리자 비밀번호를 설정하는 식이죠. 이 과정은 마치 요리할 때 소금의 양을 조절하는 것과 같아서, 같은 요리법이라도 환경 변수에 따라 맛(동작)이 달라질 수 있어요.
STEP 4. 데이터 저장소, 볼륨(Volumes) 연결하기
컨테이너는 아주 깔끔한 성격이에요. 컨테이너가 꺼지거나 삭제되면 그 안에서 작업했던 데이터도 모두 사라져 버려요. 만약 웹 서버에 올린 사진이나 데이터베이스에 저장한 게시글이 사라진다면 정말 큰일이겠죠? 이를 방지하기 위해 volumes를 사용해요.
볼륨은 컨테이너 내부의 폴더와 실제 서버의 폴더를 연결해 주는 작업이에요. 이렇게 연결해 두면 컨테이너가 아무리 생겼다 사라져도, 실제 데이터는 서버의 하드디스크에 안전하게 남아있게 돼요. 마치 USB 메모리를 컴퓨터에 꽂아서 파일을 저장하는 것과 비슷한 원리라고 이해하면 쉬워요.
STEP 5. 서비스 간의 순서 정하기, 의존성(Depends_on) 관리
실제 서비스는 혼자 작동하지 않아요. 웹 서버는 반드시 데이터베이스가 먼저 켜져 있어야 제대로 작동할 수 있죠. 이때 사용하는 것이 depends_on이에요. 이 설정을 통해 “웹 서버를 켜기 전에 데이터베이스부터 먼저 실행해 줘”라고 순서를 정해줄 수 있어요. 순서가 꼬여서 데이터베이스가 준비되지 않았는데 웹 서버가 먼저 달려들면 에러가 발생하니까요.
실무에서 가장 많이 사용하는 구성은 Web + DB 조합이에요. 웹 서버는 사용자에게 화면을 보여주고, DB는 데이터를 저장하는 역할을 맡아 각자의 서비스 블록을 가집니다. 이 두 블록이 서로 통신할 수 있도록 같은 네트워크 안에 두는 것이 핵심이에요.
자, 이제 배운 내용을 바탕으로 실제 예제를 살펴볼까요? 아래는 웹 서버와 데이터베이스를 연결하는 아주 간단한 구성표예요.
[실무 적용 예제: 웹 서버와 DB 연결하기]
이 시나리오는 사용자가 접속할 수 있는 웹 페이지와 데이터를 담아둘 데이터베이스를 동시에 띄우는 예시예요.
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: mysecretpassword
volumes:
- ./db_data:/var/lib/mysql
web:
image: nginx:latest
ports:
- "80:80"
depends_on:
- db
위 코드를 보면 db 서비스가 먼저 정의되어 있고, web 서비스는 depends_on: - db를 통해 DB가 먼저 켜지기를 기다려요. 또한 DB의 데이터는 ./db_data라는 현재 폴더의 디렉토리에 안전하게 저장되도록 설정되어 있죠.
이처럼 서비스 블록을 하나씩 쌓아 올리다 보면, 복잡해 보이던 전체 서버 구조가 머릿속에 그려지기 시작할 거예요.
자주 하는 실수와 해결법 및 FAQ
설정 파일을 작성하다 보면 분명히 맞게 쓴 것 같은데 왜 안 되는지 답답할 때가 많죠? 초보자들이 가장 많이 겪는 5가지 실수를 정리했어요.
- ❌ YAML 들여쓰기 오류 → 줄 맞춤을 잘못해서 컴포즈가 파일을 읽지 못해요.
✅ 해결법: 반드시 스페이스(Space)를 사용하고, 메모장보다는 VS Code 같은 전문 에디터를 사용해 눈금선(Indent Guide)을 확인하세요. - ❌ 포트 충돌 → 이미 다른 프로그램이 사용 중인 포트를 쓰려고 할 때 발생해요.
✅ 해결법:ports: - "8080:80"처럼 호스트 포트를 바꿔가며 사용 가능한 번호를 찾으세요. - ❌ 볼륨 경로 오류 → 데이터가 저장될 폴더 경로를 잘못 적으면 에러가 나요.
✅ 해결법: 상대 경로(./)와 절대 경로(/)를 명확히 구분해서 적어주세요. - ❌ 환경 변수 오타 →
MYSQL_PASSWORD를MYSQL_PWD라고 적는 경우예요.
✅ 해결법: 공식 문서에서 제공하는 정확한 변수 이름을 복사해서 사용하세요. - ❌ 이미지 버전 미지정 →
latest만 써서 갑자기 설정이 깨져요.
✅ 해결법: nginx:1.21처럼 숫자가 포함된 버전을 사용하세요.
설정을 바꾼 후에는 단순히 저장만 한다고 적용되지 않아요. 반드시
docker-compose up -d 명령어를 다시 실행해서 변경 사항을 반영해야 해요.자주 묻는 질문
Q. 설정 파일을 수정했는데 왜 예전 버전이 그대로 돌아가나요?
A. 도커는 이미지를 새로 생성하지 않으면 기존에 만들어진 컨테이너를 그대로 사용하려고 해요. docker-compose up --build 명령어를 써서 강제로 다시 빌드하거나, 기존 컨테이너를 삭제하고 다시 생성해 보세요.
Q. 컨테이너가 계속 껐다 켜졌다를 반복(Restart Loop)해요.
A. 대부분 설정 오류 때문이에요. 로그를 확인하는 것이 가장 빨라요. docker-compose logs -f [서비스이름] 명령어를 입력해 어떤 에러 메시지가 뜨는지 꼭 확인해 보세요.
Q. 포트 번호 80은 꼭 써야 하나요?
A. 아니요, 꼭 그럴 필요는 없어요. 서버에 이미 다른 웹 서비스가 있다면 8080이나 8000 같은 다른 번호를 호스트 포트로 사용하면 돼요.
Q. 볼륨을 설정했는데 왜 파일이 안 보이죠?
A. 경로 설정이 잘못되었을 가능성이 커요. 호스트 쪽 경로(왼쪽)가 실제로 존재하는지, 그리고 쓰기 권한이 있는지 확인해 보세요.
Q. 서비스 이름을 마음대로 지어도 상관없나요?
A. 네, 상관없어요. 다만 다른 서비스에서 이 서비스에 접속할 때는 이 이름을 호스트 이름(Hostname)처럼 사용하게 된다는 점만 기억하세요.
이제 당신도 서비스 블록을 다룰 수 있어요!
처음에는 그저 암호 같은 문자의 나열이었겠지만, 이제는 각 줄이 어떤 역할을 하는지 조금은 눈에 들어오시나요? 서비스 블록은 결국 정해진 규칙에 따라 재료(Image)를 준비하고, 문(Port)을 열고, 창고(Volume)를 마련하는 과정일 뿐이에요. 이 원리만 잊지 않는다면 어떤 복잡한 설정 파일도 두렵지 않을 거예요.
- 이미지(Image): 실행할 프로그램의 설계도예요.
- 포트(Ports): 외부 접속을 위한 통로예요. (호스트:컨테이너)
- 환경 변수(Env): 프로그램에 전달할 맞춤 설정값이에요.
- 볼륨(Volumes): 데이터를 영구적으로 저장하는 창고예요.
- 의존성(Depends_on): 서비스 간의 실행 순서를 결정해요.
- YAML 규칙: 들여쓰기 한 칸이 전체를 망칠 수 있어요.
오늘 배운 내용을 바탕으로 지금 당장 아주 작은 웹 서버 하나를 직접 띄워보는 건 어떨까요? 이론으로 백 번 보는 것보다, 직접 코드를 치고 에러를 마주하며 해결해 보는 한 번의 경험이 훨씬 강력한 힘을 길러줄 거예요.
이번 주 실천 과제:
- 직접 메모장이나 에디터를 열어 아주 간단한 Nginx 서비스 블록을 작성해 보세요.
- 작성한 코드를 실제 도커 컴포즈로 실행해 보세요.
- 포트 번호를 바꿔가며 접속이 잘 되는지 테스트해 보세요.
이해가 안 되는 용어가 있다면 그냥 넘기지 말고, 꼭 예제로 한 번씩 실행해 보세요. 직접 눈으로 확인하는 것이 가장 빠른 학습법이에요. 다음에는 서비스 블록을 넘어, 컨테이너를 더 세밀하게 제어하는 Dockerfile 작성법에 대해 이야기해 볼게요.
함께 공부하면 좋은 글: 도커 컴포즈 기본 개념 완벽 정리 — 개념부터 실무 활용까지 한눈에 보는 가이드