# 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 명령어가 간편해 보입니다. 하지만 서비스가 늘어나면 문제는 심각해집니다.
- 휘발성: 명령어를 치고 엔터를 누르는 순간, 내가 어떤 옵션을 줬는지 기록이 남지 않습니다. (history 명령어를 뒤적거려야 하죠.)
- 복잡성: 데이터베이스(DB)와 웹 서버처럼 서로 연결된 컨테이너들을 띄울 때, 네트워크 설정이 매우 골치 아픕니다.
- 유지보수 불가: 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 작성 시 주의사항 (제발 이것만은!)
- 탭(Tab) 금지: 무조건 **스페이스바(공백)**를 사용해야 합니다. 보통 스페이스 2칸을 1레벨로 칩니다.
- 계층 구조: 들여쓰기 라인이 맞지 않으면 에러가 납니다. 부모-자식 관계를 들여쓰기로 표현합니다.
- 콜론 뒤 공백:
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를 쓰면서 숨 쉬듯이 쓰게 될 명령어들입니다. 외워두세요.
-
실행 (Update)
docker-compose up -d가장 많이 씁니다. 내용을 수정했나요? 그냥 이거 다시 치면 바뀐 부분만 알아서 다시 띄워줍니다. (Recreate)
-
종료 (Stop & Remove)
docker-compose down컨테이너를 끄고, 삭제까지 합니다. 하지만 볼륨(데이터)은 지우지 않으니 안심하세요.
-
로그 확인 (Logs)
docker-compose logs -f뭔가 안 되나요? 로그를 보세요.
-f는 실시간(follow)으로 보겠다는 뜻입니다. -
상태 확인 (Process Status)
docker-compose ps현재 이 스택의 컨테이너들이 살았는지 죽었는지 보여줍니다.
-
업데이트 (Pull)
docker-compose pull docker-compose up -d이미지를 최신 버전으로 받고 싶을 때 씁니다.
pull후에up -d를 해야 적용됩니다.
6. Portainer의 ‘Stacks’ 기능
만약 터미널(검은 화면) 알레르기가 있으시다면? 지난 시간에 소개해 드린 Portainer를 쓰면 됩니다. Portainer 메뉴 중 **[Stacks]**가 바로 Docker Compose를 웹에서 쓰는 기능입니다.
- Portainer 접속 -> Stacks -> Add stack 클릭
- 이름(Name) 짓고, Web editor에
docker-compose.yml내용 붙여넣기 - 아래 [Environment variables] 버튼 눌러서
.env내용 추가 - [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-compose와 docker 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를 실행하면, 기존 컨테이너는 (이름이 같다면) 재성성되어 관리하에 들어오게 됩니다.