[IT-방법] 컴포즈 services 설정 예제와 실무 가이드 – docker-compose.yml 핵심 옵션 완벽 정리

services 블록 구성를 설명하는 docker-compose.yml 예제 모음 대표 이미지

복잡한 도커 컴포즈 설정, 왜 자꾸 에러가 날까요?

새벽까지 프로젝트를 붙잡고 있다가 드디어 컨테이너를 띄우려고 docker-compose up 명령어를 입력했어요. 그런데 터미널에 빨간 글씨로 가득한 에러 메시지가 쏟아지면 정말 막막하죠. services 블록의 들여쓰기 하나가 잘못되었거나, 포트 설정 형식이 틀렸을 때 발생하는 이 문제들은 초보 개발자뿐만 아니라 숙련된 운영자에게도 꽤나 번거로운 일이에요.

단순히 컨테이너 하나를 띄우는 건 쉽지만, 데이터베이스와 웹 서버, 그리고 캐시 서버를 하나의 파일로 엮어서 관리하려고 하면 이야기가 달라져요. 네트워크는 어떻게 연결해야 하는지, 데이터가 사라지지 않게 볼륨은 어떻게 잡아야 하는지 고민하다 보면 어느새 docker-compose.yml 파일은 복잡한 미로처럼 변해버리곤 해요. 이런 경험을 한 번이라도 해봤다면, 지금 이 글이 정말 큰 도움이 될 거예요.

이 글은 단순히 설정 옵션을 나열하는 백과사전이 아니에요. 실무에서 바로 복사해서 쓸 수 있는 컴포즈 services 설정 예제를 중심으로, 각 옵션이 실제로 어떻게 동작하는지 원리를 파헤쳐 드려요. 설정값이 잘못되었을 때 어떤 증상이 나타나는지도 함께 다루고 있으니, 에러 메시지 앞에서 당황하지 않고 대처하는 능력을 기를 수 있어요.

💡 이 글에서 다루는 핵심 내용

  • 가장 기본이 되는 최소 구성 예제와 각 옵션의 역할
  • 실무에서 자주 쓰이는 볼륨, 네트워크, 환경 변수 확장법
  • 설정 오류를 빠르게 찾아내는 디버깅 및 검증 방법
  • 재사용 가능한 실무형 템플릿 활용 팁

설정 전 반드시 체크해야 할 핵심 개념

본격적으로 파일을 작성하기 전에, 우리가 무엇을 준비해야 하는지 먼저 살펴볼게요. services 블록은 도커 컴포즈 파일의 심장과 같아요. 이 안에는 어떤 이미지를 사용할지, 어떤 포트를 열어줄지, 그리고 컨테이너끼리 어떻게 대화할지에 대한 모든 설계도가 담겨 있어요. 준비 없이 무작정 코드를 짜기 시작하면 나중에 네트워크 충돌이나 데이터 유실 같은 큰 문제로 이어질 수 있어요.

가장 먼저 확인해야 할 것은 현재 환경에 설치된 도커 엔진(Docker Engine)과 도커 컴포즈(Docker Compose) 버전이에요. 최신 기능인 depends_on의 상세 조건이나 새로운 네트워크 모드를 제대로 활용하려면 버전 확인이 필수적이에요. 또한, 작성하려는 서비스들이 서로 어떤 데이터를 주고받아야 하는지, 어떤 포트를 호스트 컴퓨터에 노출할 것인지 미리 종이에 그려보는 과정이 필요해요.

설정 옵션은 종류가 너무 많아서 헷갈리기 쉬워요. 특히 비슷한 역할을 하는 것처럼 보이는 옵션들이 있어서 주의가 필요하죠. 아래 표를 통해 자주 혼동하는 설정들을 비교해 보았어요. 이 차이점만 명확히 알아도 설정 오류의 절반은 예방할 수 있어요.

비교 항목 옵션 A 옵션 B 차이점 및 선택 기준
이미지 생성 image build 기존 이미지를 쓸지, 직접 Dockerfile로 만들지 결정해요.
포트 제어 ports expose 외부 호스트에 노출할지, 컨테이너 간에만 연결할지 정해요.
데이터 저장 volumes tmpfs 영구 저장할지, 휘발성 메모리에 저장할지 선택해요.
실행 순서 depends_on networks 시작 순서를 정할지, 통신 통로를 만들지 결정해요.
⚠️ 주의
설정 파일을 작성할 때 YAML의 들여쓰기(Indentation)는 탭(Tab)이 아닌 반드시 공백(Space)을 사용해야 해요. 탭을 섞어 쓰면 해석기가 파일을 인식하지 못하고 바로 에러를 뱉어내니 꼭 주의하세요!

실무에 바로 쓰는 단계별 구성 예제

이제 이론을 넘어 실제 코드로 들어가 볼게요. 복잡한 환경을 한 번에 구축하려고 하면 머리가 아프니까, 아주 작은 단위부터 시작해서 하나씩 살을 붙여 나가는 방식으로 진행할게요. 이 순서대로 따라오시면 자연스럽게 숙련된 작성법을 익힐 수 있어요.

STEP 1. 가장 기본적인 최소 구성 만들기

먼저, 세상에서 가장 단순한 웹 서버를 띄워볼게요. 어떤 복잡한 옵션도 없이 이미지 이름과 포트만 지정하는 방식이에요. 이 단계는 서비스가 정상적으로 동작하는지 확인하는 가장 기초적인 테스트 단계로 활용하기 좋아요.

services:
  web-server:
    image: nginx:latest
    ports:
      - "8080:80"

위 예제에서 image: nginx:latest는 도커 허브에서 최신 버전의 nginx 이미지를 가져오라는 뜻이에요. ports: ["8080:80"]는 아주 중요한데, 호스트 컴퓨터의 8080번 포트로 들어오는 요청을 컨테이너 내부의 80번 포트로 전달하겠다는 약속이에요. 이 설정만으로도 브라우저에서 localhost:8080를 입력해 웹 페이지를 확인할 수 있어요.

STEP 2. 데이터 보존을 위한 볼륨 설정하기

웹 서버만 띄우면 금방 끝날 것 같지만, 실제 서비스에서는 설정 파일이나 로그, 혹은 데이터베이스의 데이터를 저장해야 하죠. 컨테이너는 삭제되면 내부 데이터도 모두 날아가 버리는 휘발성을 가지고 있기 때문이에요. 이때 필요한 것이 바로 volumes 옵션이에요.

services:
  db:
    image: postgres:15
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=mysecretpassword

여기서 ./data/postgres:/var/lib/postgresql/data 부분을 자세히 봐주세요. 왼쪽의 ./data/postgres는 내 컴퓨터의 현재 폴더에 있는 경로를 말하고, 오른쪽은 컨테이너 내부의 데이터 저장 경로예요. 이렇게 연결해두면 컨테이너를 껐다 켜거나 심지어 삭제하더라도, 실제 데이터는 내 컴퓨터의 폴더에 안전하게 남아있게 돼요. 또한 environment를 통해 데이터베이스 비밀번호 같은 민감한 정보를 환경 변수로 주입할 수 있어요.

STEP 3. 서비스 간 연결과 의존성 관리

실제 서비스는 혼자 돌아가지 않아요. 웹 서버가 있고, 그 뒤에는 데이터베이스가 있으며, 중간에 Redis 같은 캐시 서버가 붙기도 하죠. 이 서비스들이 서로를 인식하고 올바른 순서로 실행되게 만드는 것이 핵심이에요. 네트워크 설정과 depends_on이 이 역할을 수행해요.

services:
  app:
    build: .
    depends_on:
      - db
    networks:
      - backend-net
  db:
    image: mysql:8.0
    networks:
      - backend-net
networks:
  backend-net:
    driver: bridge

이 구조를 보면 app 서비스가 db 서비스에 의존하고 있다는 것을 알 수 있어요. depends_on 덕분에 데이터베이스가 먼저 실행된 후에 애플리케이션이 실행돼요. 또한 networks를 통해 두 컨테이너를 backend-net라는 가상의 네트워크 공간에 묶어주었어요. 이렇게 하면 app 컨테이너 안에서 데이터베이스 주소를 단순히 db라는 이름으로 바로 호출할 수 있어 매우 편리해요.

STEP 4. 운영을 위한 고급 옵션 활용하기

이제 거의 다 왔어요. 마지막으로 서비스가 갑자기 죽었을 때 자동으로 다시 살아나게 하거나, 메모리 사용량을 제한하는 등의 운영 최적화 옵션을 살펴볼게요. 이건 서버 운영 단계에서 정말 중요한 설정이에요.

services:
  api-server:
    image: my-api:v1
    restart: always
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M

restart: always는 컨테이너가 어떤 이유로든 종료되면 도커가 알아서 다시 실행시켜 주라는 명령어예요. 서버가 재부팅되었을 때도 유용하죠. deploy.resources.limits 설정은 한 컨테이너가 서버의 자원을 독점하는 것을 방지해요. 예를 들어 메모리를 512M로 제한해두면, 특정 서비스에서 메모리 누수가 발생하더라도 전체 서버가 멈추는 대참사를 막을 수 있어요.

💡 실무 꿀팁
비밀번호나 API 키 같은 민감한 정보는 environment에 직접 적지 마세요. 대신 .env 파일을 따로 만들고, 컴포즈 파일에서는 ${DB_PASSWORD}처럼 변수 형태로 호출하는 것이 훨씬 안전해요!

자주 하는 실수와 해결법

아무리 조심해도 설정 파일에는 실수가 생기기 마련이에요. 실제 현장에서 가장 많이 마주치는 문제들을 정리해 보았으니, 에러 메시지가 떴을 때 이 리스트를 먼저 떠올려 보세요.

YAML 들여쓰기 오류
왜 발생하나요? 탭(Tab) 문자를 사용했거나, 스페이스(Space) 개수가 맞지 않을 때 발생해요.
해결법: VS Code 같은 에디터에서 ‘Indent Using Spaces’ 설정을 켜고, 눈으로 들여쓰기 단계를 하나하나 확인하세요.

포트 충돌 (Port already in use)
왜 발생하나요? 이미 다른 프로그램이나 다른 컨테이너가 호스트의 동일한 포트를 사용 중일 때 발생해요.
해결법: netstat -ano 명령어로 사용 중인 포트를 확인하거나, 컴포즈 파일의 호스트 포트 번호를 바꾸세요.

볼륨 경로 오류
왜 발생하나요? 호스트의 경로를 잘못 적었거나, 상대 경로를 사용할 때 기준점이 헷gar졌을 때 발생해요.
해결법: 가능하면 ./를 사용하여 상대 경로를 명확히 하거나, 절대 경로를 사용하여 경로 실수를 줄이세요.

환경 변수 인식 불가
왜 발생하나요? .env 파일의 위치가 docker-compose.yml과 같은 위치에 있지 않을 때 발생해요.
해결법: 설정 파일과 .env 파일이 같은 디렉토리에 있는지 확인하고, 파일명에 오타가 없는지 점검하세요.

의존성 실행 순서 문제
왜 발생하나요? depends_on은 컨테이너의 ‘시작’만 보장할 뿐, 내부 애플리케이션이 ‘준비’될 때까지 기다려주지는 않기 때문이에요.
해결법: healthcheck 옵션을 추가하여 서비스가 완전히 준비되었을 때 다음 서비스를 띄우도록 설정하세요.

자주 묻는 질문

Q. 컴포즈 설정 파일에 문법 오류가 있는지 어떻게 미리 알 수 있나요?

터미널에서 docker compose config 명령어를 입력해 보세요. 이 명령은 파일을 실제로 실행하지 않고 문법적으로 올바른지, 변수들은 제대로 치환되었는지 검증해 줘요. 만약 에러가 있다면 어느 줄이 틀렸는지 바로 알려줍니다.

Q. 특정 서비스 하나만 다시 시작하고 싶을 때는 어떻게 하나요?

전체를 껐다 켤 필요 없이, docker compose restart [서비스명]을 입력하면 돼요. 예를 들어 DB만 재시작하고 싶다면 docker compose restart db라고 쓰면 됩니다.

Q. 컨테이너 내부의 로그를 실시간으로 보고 싶은데 방법이 있나요?

docker compose logs -f 명령어를 사용해 보세요. -f 옵션은 ‘follow’의 약자로, 새로운 로그가 쌓일 때마다 실시간으로 화면에 출력해 줍니다.

Q. docker-compose.yml 파일에서 이미지 태그를 왜 꼭 써야 하나요?

태그를 쓰지 않고 latest만 사용하면 편리하긴 하지만, 나중에 이미지가 업데이트되었을 때 의도치 않게 서비스 버전이 바뀌어 에러가 날 수 있어요. 실무에서는 postgres:15.3처럼 구체적인 버전을 명시하는 것이 훨씬 안전해요.

성공적인 컨테이너 운영을 위한 마무리

도커 컴포즈의 services 블록을 다루는 법을 이제 어느 정도 익히셨을 거예요. 처음에는 복잡해 보였던 설정들도 하나씩 뜯어보면 결국 이미지, 포트, 볼륨, 네트워크라는 네 가지 핵심 기둥으로 이루어져 있다는 걸 알 수 있어요. 이 기둥들만 제대로 세운다면 어떤 복잡한 애플리케이션도 안정적으로 운영할 수 있습니다.

마지막으로, 오늘 배운 내용을 잊지 않도록 아래 체크리스트를 꼭 확인해 보세요. 새로운 프로젝트를 시작할 때 이 리스트를 옆에 두고 하나씩 체크하며 파일을 작성해 보세요.

✅ 핵심 요약

  • 들여쓰기는 반드시 공백(Space)으로 통일했나요?
  • 외부 노출이 필요한 포트는 ports에 정확히 기재했나요?
  • 중요한 데이터는 volumes를 통해 호스트와 연결했나요?
  • 민감한 정보는 .env 파일을 통해 관리하고 있나요?
  • 서비스 간 실행 순서가 중요하다면 depends_on을 설정했나요?
  • 자원 독점을 막기 위해 limits 옵션을 검토했나요?

이제 바로 실행해 볼 차례예요! 오늘 배운 예제 코드를 복사해서 여러분의 로컬 환경에서 직접 실행해 보세요. 에러가 발생한다면 당황하지 말고 docker compose config로 검증하며 하나씩 고쳐나가면 됩니다. 그 과정 자체가 여러분의 실력을 가장 빠르게 키워줄 거예요.

더 깊이 있는 도커 활용법이 궁금하다면, 도커 컴포즈 기본 개념 완벽 정리 — 개념부터 실무 활용까지 한눈에 보는 가이드 글을 함께 읽어보시는 것을 추천해요. 여러분의 성공적인 컨테이너 운영을 응원합니다!

댓글 남기기