본문으로 건너뛰기
Docker Compose 로고와 코드 블록, 컨테이너들이 조립되는 모습

# Docker Compose 완벽 가이드: 복잡한 서버 관리, 텍스트 파일 하나로 종결하기

Table of Contents

홈서버에 Docker를 설치하고 컨테이너를 하나둘씩 띄우다 보면, 어느 순간 ‘현타’가 오는 시점이 있습니다. 바로 컨테이너를 업데이트하거나 재설정할 때입니다.

docker run -d --name my-plex -p 32400:32400 -v /mnt/data/movies:/movies -v /mnt/config/plex:/config --restart always --device /dev/dri:/dev/dri plexinc/pms-docker

이 긴 명령어를 매번 기억할 수 있을까요? 옵션 하나를 빼먹어서 트랜스코딩이 안 되거나, 볼륨 매핑을 잘못해서 데이터를 날리는 실수는 초보 시절 누구나 겪는 통과의례입니다.

이제 우리는 아마추어 단계를 벗어나야 합니다. 서버 관리자(SysAdmin)처럼 우아하고 체계적으로 서버를 관리하는 방법, 바로 Docker Compose를 소개합니다. 이 글을 읽고 나면, 여러분의 서버 관리는 ‘기억력 테스트’에서 ‘체계적인 기록’으로 진화할 것입니다.

1. 왜 Docker Compose인가요?

docker run의 악몽

처음에는 docker run 명령어가 간편해 보입니다. 하지만 서비스가 늘어나면 문제는 심각해집니다.

  1. 휘발성: 명령어를 치고 엔터를 누르는 순간, 내가 어떤 옵션을 줬는지 기록이 남지 않습니다. (history 명령어를 뒤적거려야 하죠.)
  2. 복잡성: 데이터베이스(DB)와 웹 서버처럼 서로 연결된 컨테이너들을 띄울 때, 네트워크 설정이 매우 골치 아픕니다.
  3. 유지보수 불가: 1년 뒤에 이 서버를 다시 세팅해야 한다면? 그때 그 옵션들을 완벽하게 기억해 낼 수 있을까요?

구세주, Docker Compose

Docker Compose는 **“인프라를 코드로 관리(Infrastructure as Code, IaC)“**하는 도구입니다. 복잡한 실행 옵션들을 docker-compose.yml이라는 텍스트 파일 하나에 적어두고, 명령어 한 줄로 실행합니다.

docker-compose up -d

이 한 줄의 마법이 가져오는 변화는 실로 엄청납니다.

  • 문서화: 파일 자체가 곧 설계도이자 설명서입니다.
  • 재사용성: 이 파일을 친구에게 주거나 다른 서버로 복사하면, 똑같은 환경이 1초 만에 구축됩니다.
  • 연결성: 여러 컨테이너(예: WordPress + MySQL)를 하나의 세트(Stack)로 묶어서 관리해 줍니다.

2. YAML 문법: 컴퓨터와 대화하는 언어

Docker Compose를 쓰려면 **YAML(야몰)**이라는 언어를 알아야 합니다. 겁먹지 마세요. 개발자가 아니어도 10분이면 배웁니다. 핵심은 딱 하나, **“들여쓰기(Indentation)“**입니다.

version: '3.8'  # 버전 정보

services:       # 여기부터 서비스 정의 시작
  web-server:   # 서비스 이름 (내 맘대로)
    image: nginx:latest  # 사용할 이미지
    ports:
      - "80:80" # 포트 연결 (외부:내부)

⚠️ YAML 작성 시 주의사항 (제발 이것만은!)

  1. 탭(Tab) 금지: 무조건 **스페이스바(공백)**를 사용해야 합니다. 보통 스페이스 2칸을 1레벨로 칩니다.
  2. 계층 구조: 들여쓰기 라인이 맞지 않으면 에러가 납니다. 부모-자식 관계를 들여쓰기로 표현합니다.
  3. 콜론 뒤 공백: image:nginx (X) -> image: nginx (O). 콜론 뒤에는 반드시 한 칸 띄어야 합니다.

3. 실전! 나만의 미디어 서버 스택 만들기

이제 실제로 docker-compose.yml 파일을 작성해 보겠습니다. 가장 인기 있는 조합인 **Jellyfin(미디어 서버)**과 **Jellyseerr(요청 프로그램)**를 한 방에 띄워보겠습니다.

파일을 만들 폴더를 생성하고 편집기를 엽니다.

mkdir my-media-stack
cd my-media-stack
nano docker-compose.yml

그리고 아래 내용을 복사해서 붙여넣습니다.

version: '3.8'

services:
  # 1. 젤리핀 (넷플릭스 같은 서버)
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: my-jellyfin
    environment:
      - PUID=1000   # 리눅스 사용자 ID
      - PGID=1000   # 리눅스 그룹 ID
      - TZ=Asia/Seoul
    volumes:
      - ./config:/config          # 설정 파일 저장소
      - ./media:/data/media       # 영화 파일 저장소
    ports:
      - "8096:8096"
    restart: unless-stopped
    devices:
      - /dev/dri:/dev/dri         # 하드웨어 가속(트랜스코딩)용

  # 2. 젤리시어 (보고 싶은 영화 요청)
  jellyseerr:
    image: fallenbagel/jellyseerr:latest
    container_name: my-jellyseerr
    environment:
      - LOG_LEVEL=debug
      - TZ=Asia/Seoul
    ports:
      - "5055:5055"
    volumes:
      - ./jellyseerr-config:/app/config
    restart: unless-stopped
    depends_on:
      - jellyfin  # 젤리핀이 켜져야 얘도 켜짐

실행하기

저장(Ctrl+O, Enter)하고 나가서(Ctrl+X), 실행합니다.

docker-compose up -d
  • up: 실행해라 (없으면 만들고, 있으면 켜라)
  • -d: Detached mode (백그라운드에서 실행해라)

이제 브라우저에서 http://서버IP:8096으로 접속하면 Jellyfin이, http://서버IP:5055로 접속하면 Jellyseerr가 반겨줄 것입니다. depends_on 옵션 덕분에 Jellyfin이 먼저 실행된 후 Jellyseerr가 실행됩니다. 이런 순서 제어는 docker run으로는 하기 힘든 작업입니다.

4. 환경변수(.env)로 비밀 지키기

서버를 운영하다 보면 비밀번호, API 키 같은 민감한 정보를 다루게 됩니다. docker-compose.yml 파일에 비밀번호를 쌩으로 적어두고 깃허브(GitHub)에 올렸다가는? 해킹의 지름길입니다.

이때 사용하는 것이 .env (도트 엔브) 파일입니다. 변수만 따로 빼서 관리하는 것이죠.

1단계: .env 파일 만들기

nano .env
# .env 파일 내용
MY_ROOT_PASSWORD=supersecretpassword123
MY_TIMEZONE=Asia/Seoul
PLEX_CLAIM_TOKEN=claim-xxxxxxx

2단계: docker-compose.yml에서 불러오기

services:
  database:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=${MY_ROOT_PASSWORD} # ${변수명} 형태로 사용
      - TZ=${MY_TIMEZONE}

이렇게 하면 docker-compose.yml 파일은 누구에게 보여줘도 안전합니다. 실제 비밀번호는 내 서버 안의 .env 파일에만 존재하니까요. 이것이 보안의 기본입니다.

5. 자주 쓰는 명령어 5대장

Docker Compose를 쓰면서 숨 쉬듯이 쓰게 될 명령어들입니다. 외워두세요.

  1. 실행 (Update)

    docker-compose up -d

    가장 많이 씁니다. 내용을 수정했나요? 그냥 이거 다시 치면 바뀐 부분만 알아서 다시 띄워줍니다. (Recreate)

  2. 종료 (Stop & Remove)

    docker-compose down

    컨테이너를 끄고, 삭제까지 합니다. 하지만 볼륨(데이터)은 지우지 않으니 안심하세요.

  3. 로그 확인 (Logs)

    docker-compose logs -f

    뭔가 안 되나요? 로그를 보세요. -f는 실시간(follow)으로 보겠다는 뜻입니다.

  4. 상태 확인 (Process Status)

    docker-compose ps

    현재 이 스택의 컨테이너들이 살았는지 죽었는지 보여줍니다.

  5. 업데이트 (Pull)

    docker-compose pull
    docker-compose up -d

    이미지를 최신 버전으로 받고 싶을 때 씁니다. pull 후에 up -d를 해야 적용됩니다.

6. Portainer의 ‘Stacks’ 기능

만약 터미널(검은 화면) 알레르기가 있으시다면? 지난 시간에 소개해 드린 Portainer를 쓰면 됩니다. Portainer 메뉴 중 **[Stacks]**가 바로 Docker Compose를 웹에서 쓰는 기능입니다.

  1. Portainer 접속 -> Stacks -> Add stack 클릭
  2. 이름(Name) 짓고, Web editor에 docker-compose.yml 내용 붙여넣기
  3. 아래 [Environment variables] 버튼 눌러서 .env 내용 추가
  4. [Deploy the stack] 버튼 클릭

끝입니다. 터미널 접속 없이도 웹 브라우저에서 편안하게 코드로 인프라를 관리할 수 있습니다. 대부분의 홈서버 유저들은 이 방식을 가장 선호합니다. 수정하기도 쉽고, 눈에 잘 들어오니까요.

7. 심화: 네트워크 격리하기

Docker Compose를 쓰면 서비스마다 전용 네트워크를 만들어 서로 격리할 수 있습니다. 보안상 매우 중요한 개념입니다.

services:
  wp:
    image: wordpress
    networks:
      - frontend  # 웹 서버랑만 통신
      - backend   # DB랑만 통신

  db:
    image: mysql
    networks:
      - backend   # 웹 서버랑만 통신 (외부 인터넷 차단 가능)

networks:
  frontend:
  backend:
    internal: true # 외부 접속 차단

이렇게 하면 해커가 웹 서버(WordPress)를 뚫더라도, 데이터베이스(DB)로 직접 접근하는 경로는 막혀있게 됩니다. docker run으로는 이런 설정을 하려면 머리가 빠지지만, Compose로는 몇 줄이면 끝납니다.

8. 마치며: 당신은 이제 DevOps 엔지니어입니다

축하합니다. 여러분은 이제 단순히 남이 만든 명령어를 복사/붙여넣기 하는 수준을 넘어섰습니다. 스스로 시스템의 구조를 설계하고(YAML 작성), 변수를 관리하며(Environment), 코드로 인프라를 통제(IaC)하는 DevOps 엔지니어의 첫걸음을 떼신 겁니다.

Docker Compose 파일은 여러분의 자산입니다. 이 파일 하나만 잘 백업해 두면, 서버가 불타 없어져도 새 하드웨어에 5분 만에 원래 환경을 완벽하게 복구할 수 있습니다. 이것이 진정한 의미의 ‘백업’이자 ‘재해 복구(DR)‘입니다.

다음 포스팅에서는 이 Docker 컨테이너들을 예쁘게 관리하고 시각화해 주는 “Portainer로 Docker 웹 GUI 관리하기” 편으로 찾아오겠습니다. 이제 검은 화면보다는 예쁜 웹 화면을 더 자주 보게 될 것입니다.


Q&A: 혹시 이런 게 궁금하신가요?

Q. docker-composedocker compose (하이픈 없음)의 차이는 뭔가요? A. 아주 좋은 질문입니다!

  • docker-compose (하이픈 있음): 옛날 버전(v1)입니다. 파이썬으로 만들어졌고 이제는 잘 안 씁니다.
  • docker compose (하이픈 없음): 최신 버전(v2)입니다. Docker 자체에 내장된 기능입니다. 이제부터는 하이픈 없는 docker compose를 쓰시면 됩니다. (기능은 99% 똑같습니다.)

Q. version: '3.8' 이거 꼭 써야 하나요? A. 최신 v2 버전에서는 생략해도 됩니다. 하지만 구버전과의 호환성이나 관습적으로 명시해 주는 경우가 많습니다. 없어도 큰 문제는 없습니다.

Q. 기존에 docker run으로 띄운 컨테이너를 Compose로 바꿀 수 있나요? A. 네, 가능합니다! ‘Composerize’ 같은 웹사이트를 이용하면 docker run 명령어를 넣었을 때 docker-compose.yml 양식으로 변환해 줍니다. 변환된 내용을 파일로 저장하고 docker-compose up -d를 실행하면, 기존 컨테이너는 (이름이 같다면) 재성성되어 관리하에 들어오게 됩니다.

이 글 공유하기:
다음 글: Portainer 완벽 가이드: 검은 화면 공포증을 치료해 드립니다
My avatar

글을 마치며

이 글이 도움이 되었기를 바랍니다. 궁금한 점이나 의견이 있다면 댓글로 남겨주세요.

더 많은 기술 인사이트와 개발 경험을 공유하고 있으니, 다른 포스트도 확인해보세요.

유럽살며 여행하며 코딩하는 노마드의 여정을 함께 나누며, 함께 성장하는 개발자 커뮤니티를 만들어가요! 🚀


홈서버 마스터 클래스 시리즈