본문 바로가기
IT/HomeLabs

[HomeLabs] Docker Compose 2026 홈랩 멀티컨테이너 완전 가이드: 실전 Compose 설정

by 수누다 2026. 4. 8.

홈랩에서 Docker Compose가 정말 빛나는 순간

혹시 이런 경험 있으신가요? 홈서버에 이것저것 서비스를 올리다 보니 어느 순간 컨테이너가 10개, 20개를 넘어서고, docker run 명령어를 매번 손으로 치고 있는 자신을 발견하는 그 순간. 저도 그랬거든요. 3년 전쯤 홈랩을 본격적으로 운영하기 시작했을 때, Plex, Jellyfin, Nextcloud, Pi-hole까지 올리고 나니까 진짜 관리가 엉망이 됐었습니다.

그때 Docker Compose를 제대로 파고들기 시작했는데, 2026년 현재 버전은 제가 처음 쓰던 것과 꽤 많이 달라졌더라고요. Docker Compose 2026 최신 버전에서는 홈랩 멀티컨테이너 구성이 훨씬 더 직관적이고 강력해졌습니다. 이 글에서는 제가 직접 홈서버에 적용하면서 겪은 삽질과 노하우를 솔직하게 공유해 드릴게요.

Docker Compose 홈랩 멀티컨테이너 전체 아키텍처 다이어그램 — Traefik 리버스 프록시와 각 서비스 연결 구조

▲ 홈랩 환경에서 Docker Compose로 구성한 멀티컨테이너 전체 아키텍처 — Reverse Proxy부터 미디어 서버, 모니터링 스택까지 한눈에 보이는 구성도입니다.


Docker Compose란 뭔가요? — 쉽게 말해서

기술적인 정의는 잠깐 접어두고요. 쉽게 말해서 Docker Compose는 여러 개의 컨테이너를 하나의 설정 파일로 한꺼번에 관리하는 도구입니다. 컨테이너 오케스트레이션의 가장 기본적인 형태라고 보시면 돼요.

예를 들어 WordPress를 올린다고 하면, 웹 서버 컨테이너 하나, MySQL 데이터베이스 컨테이너 하나, 이렇게 최소 두 개가 필요하잖아요. 이걸 docker run으로 두 번 치고, 네트워크 연결하고, 볼륨 마운트하고... 매번 이 과정을 반복하는 건 진짜 고통입니다.

Docker Compose를 쓰면 docker compose up -d 명령어 하나로 끝나거든요. 그게 핵심입니다.

2026년 Docker Compose 최신 버전 현황

예전에는 docker-compose(하이픈 있음)와 docker compose(하이픈 없음) 두 가지가 공존했었는데, 지금은 완전히 정리됐습니다. 2026년 현재는 Docker Compose V2가 표준이고, Docker Engine에 플러그인 형태로 내장되어 있어요. 별도 설치가 필요 없습니다.

구분 구버전 (V1) 현재 (V2, 2026)
명령어 docker-compose up docker compose up
설치 방식 별도 Python 패키지 Docker Engine 내장 플러그인
성능 상대적으로 느림 Go 언어 기반, 빠름
설정 파일 version 필드 필수 version 필드 불필요 (deprecated)
Watch 모드 미지원 ✅ 지원 (개발 환경에 유용)

💡 : 2026년에는 compose.yaml이 권장 파일명입니다. 물론 docker-compose.yml도 여전히 인식하지만, 새로 만드신다면 compose.yaml로 쓰는 게 좋아요.


홈랩 환경 준비 — 제 실제 서버 스펙 공개

글을 읽으시는 분들이 어떤 환경에서 따라 하실지 모르니까, 제 홈랩 환경을 먼저 공개할게요. 비싼 장비 없어도 됩니다. 저도 중고 미니PC로 시작했거든요.

  • 하드웨어: Intel N100 미니PC, RAM 16GB, SSD 256GB + HDD 4TB
  • OS: Ubuntu Server 24.04 LTS
  • Docker: Docker Engine 27.x (2026년 최신)
  • 네트워크: 공유기 포트포워딩 + Cloudflare Tunnel

Docker Engine 설치 확인

먼저 Docker가 제대로 설치되어 있는지 확인해 봅시다.

# Docker 버전 확인
docker --version
# Docker Engine 27.x.x 이상이면 OK

# Compose 플러그인 확인
docker compose version
# Docker Compose version v2.x.x 이상이면 OK

# Docker 서비스 자동 시작 설정
sudo systemctl enable docker
sudo systemctl start docker

저는 처음에 docker compose version을 쳤는데 명령어를 못 찾는다는 에러가 났었어요. 알고 보니 Docker Desktop이 아닌 Docker Engine만 설치했을 때 Compose 플러그인이 빠져있는 경우가 있더라고요. 그럴 땐 아래 명령어로 추가 설치하시면 됩니다.

sudo apt-get install docker-compose-plugin

실전 구현 — 홈랩 멀티컨테이너 스택 구성하기

자, 이제 본론입니다. 제가 홈랩에서 실제로 운영 중인 Docker Compose 스택을 기반으로 설명드릴게요. 구성은 이렇습니다.

  • Traefik(트래픽, Reverse Proxy): 외부 트래픽을 각 서비스로 라우팅
  • Jellyfin: 미디어 서버
  • Nextcloud: 개인 클라우드 스토리지
  • Portainer: 컨테이너 관리 웹 UI
  • Prometheus + Grafana: 모니터링 스택

1단계: 디렉터리 구조 잡기

홈랩에서 제일 처음 배운 교훈이 있어요. 디렉터리 구조를 처음부터 잘 잡아야 나중에 고생을 안 한다는 거예요. 저는 처음에 막 아무 데나 올렸다가 나중에 다 갈아엎었습니다 ㅎㅎ

mkdir -p ~/homelab
cd ~/homelab

# 서비스별 디렉터리 생성
mkdir -p traefik/config
mkdir -p jellyfin/config
mkdir -p nextcloud/{config,data}
mkdir -p portainer/data
mkdir -p monitoring/{prometheus,grafana}

# 공통 환경변수 파일 생성
touch .env

2단계: 환경변수 파일(.env) 설정

비밀번호나 도메인 같은 민감한 정보는 절대 compose.yaml에 직접 쓰면 안 됩니다. 이건 진짜 중요한 포인트예요! .env 파일에 분리하고, Git에 올릴 때는 반드시 .gitignore에 추가하세요.

# ~/homelab/.env

# 도메인 설정
DOMAIN=homelab.example.com

# Traefik 설정
ACME_EMAIL=your@email.com

# Nextcloud 설정
NC_DB_PASSWORD=super_secure_password_here
NC_ADMIN_USER=admin
NC_ADMIN_PASSWORD=another_secure_password

# Grafana 설정
GF_ADMIN_PASSWORD=grafana_password

# 타임존
TZ=Asia/Seoul

3단계: 메인 compose.yaml 작성

드디어 핵심입니다. 아래가 제가 실제로 쓰는 Docker Compose 구성 파일이에요. 처음 보면 좀 길어 보이지만, 섹션별로 나눠서 설명드릴게요.

Docker Compose compose.yaml 서비스 구조와 내부 네트워크 격리 다이어그램

▲ compose.yaml 파일에서 각 서비스(Traefik, Jellyfin, Nextcloud, Monitoring)가 내부 네트워크로 연결되고 Traefik을 통해 외부에 노출되는 구조를 보여주는 다이어그램입니다.

# ~/homelab/compose.yaml
# Docker Compose 2026 최신 형식 (version 필드 생략)

services:

  # ================================
  # Traefik — Reverse Proxy (역방향 프록시)
  # ================================
  traefik:
    image: traefik:v3.2
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/config/traefik.yml:/traefik.yml:ro
      - ./traefik/config/acme.json:/acme.json
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
      - "traefik.http.services.traefik.loadbalancer.server.port=8080"

  # ================================
  # Jellyfin — 미디어 서버
  # ================================
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    environment:
      - TZ=${TZ}
    volumes:
      - ./jellyfin/config:/config
      - /mnt/hdd/media:/media:ro  # HDD 미디어 폴더 마운트
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jellyfin.rule=Host(`media.${DOMAIN}`)"
      - "traefik.http.routers.jellyfin.entrypoints=websecure"
      - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
      - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
    devices:
      - /dev/dri:/dev/dri  # Intel 하드웨어 트랜스코딩 활성화

  # ================================
  # Nextcloud — 개인 클라우드
  # ================================
  nextcloud-db:
    image: mariadb:11
    container_name: nextcloud-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${NC_DB_PASSWORD}
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=${NC_DB_PASSWORD}
    volumes:
      - ./nextcloud/db:/var/lib/mysql
    networks:
      - nextcloud-internal
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  nextcloud:
    image: nextcloud:29
    container_name: nextcloud
    restart: unless-stopped
    depends_on:
      nextcloud-db:
        condition: service_healthy
    environment:
      - MYSQL_HOST=nextcloud-db
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=${NC_DB_PASSWORD}
      - NEXTCLOUD_ADMIN_USER=${NC_ADMIN_USER}
      - NEXTCLOUD_ADMIN_PASSWORD=${NC_ADMIN_PASSWORD}
      - TZ=${TZ}
    volumes:
      - ./nextcloud/config:/var/www/html
      - ./nextcloud/data:/var/www/html/data
    networks:
      - proxy
      - nextcloud-internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nextcloud.rule=Host(`cloud.${DOMAIN}`)"
      - "traefik.http.routers.nextcloud.entrypoints=websecure"
      - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
      - "traefik.http.services.nextcloud.loadbalancer.server.port=80"

  # ================================
  # Portainer — 컨테이너 관리 UI
  # ================================
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./portainer/data:/data
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN}`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
      - "traefik.http.services.portainer.loadbalancer.server.port=9000"

  # ================================
  # Prometheus — 메트릭 수집
  # ================================
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./monitoring/prometheus:/etc/prometheus
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
    networks:
      - monitoring
      - proxy

  # ================================
  # Grafana — 모니터링 대시보드
  # ================================
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GF_ADMIN_PASSWORD}
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - ./monitoring/grafana:/var/lib/grafana
    depends_on:
      - prometheus
    networks:
      - monitoring
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.rule=Host(`monitor.${DOMAIN}`)"
      - "traefik.http.routers.grafana.entrypoints=websecure"
      - "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
      - "traefik.http.services.grafana.loadbalancer.server.port=3000"

# ================================
# 네트워크 정의
# ================================
networks:
  proxy:
    name: proxy
    driver: bridge
  nextcloud-internal:
    name: nextcloud-internal
    driver: bridge
    internal: true  # 외부 접근 차단
  monitoring:
    name: monitoring
    driver: bridge

# ================================
# 볼륨 정의
# ================================
volumes:
  prometheus_data:
    driver: local

4단계: Traefik 설정 파일 작성

Traefik은 설정이 살짝 까다롭습니다. 저도 처음엔 이게 뭔가 싶었는데, 한 번 이해하고 나면 진짜 편해요.

# ~/homelab/traefik/config/traefik.yml

api:
  dashboard: true

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt

certificatesResolvers:
  letsencrypt:
    acme:
      email: "${ACME_EMAIL}"
      storage: /acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: proxy

log:
  level: INFO
# acme.json 파일 권한 설정 (이거 빠뜨리면 Traefik이 시작 안 됩니다!)
touch ~/homelab/traefik/config/acme.json
chmod 600 ~/homelab/traefik/config/acme.json

5단계: 스택 실행

드디어 실행할 차례입니다!

cd ~/homelab

# 백그라운드로 전체 스택 시작
docker compose up -d

# 로그 확인 (실시간)
docker compose logs -f

# 특정 서비스 로그만 보기
docker compose logs -f traefik

# 실행 중인 컨테이너 상태 확인
docker compose ps

⚠️ 삽질 모음 — 제가 겪은 트러블슈팅

이 섹션이 사실 제일 중요할 수도 있어요. 저도 처음에 온갖 에러를 다 만났거든요. 미리 알고 계시면 몇 시간은 절약하실 수 있을 겁니다.

문제 1: "network proxy not found" 에러

여러 개의 compose.yaml 파일을 쓸 때 자주 생기는 문제입니다. 특히 Traefik을 별도 파일로 분리했을 때요.

# 해결방법: 네트워크를 external로 선언하고 미리 생성
docker network create proxy

# 또는 compose.yaml에서 external 설정
networks:
  proxy:
    external: true  # 이미 존재하는 네트워크를 사용

문제 2: Nextcloud 설치 후 "신뢰할 수 없는 도메인" 에러

Nextcloud를 처음 올리면 이 에러 꼭 만납니다. 도메인을 신뢰 목록에 추가해야 해요.

# Nextcloud 컨테이너 내부에서 실행
docker exec -u www-data nextcloud php occ config:system:set trusted_domains 1 --value=cloud.homelab.example.com

문제 3: 컨테이너 시작 순서 문제 (depends_on과 healthcheck)

이거 진짜 헷갈리는 부분인데요. depends_on은 컨테이너가 시작되는 것만 기다리고, 서비스가 준비되는 것은 기다리지 않습니다. MySQL이 완전히 초기화되기 전에 Nextcloud가 연결을 시도해서 에러가 나는 경우가 많아요. 2026년 방식은 healthcheck를 활용하는 거거든요.

# healthcheck를 통한 의존성 관리 (위 compose.yaml 코드 참조)
nextcloud-db:
  healthcheck:
    test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
    interval: 10s
    timeout: 5s
    retries: 5
    start_period: 30s

nextcloud:
  depends_on:
    nextcloud-db:
      condition: service_healthy  # healthy 상태가 될 때까지 대기

문제 4: 볼륨 권한 문제

컨테이너 안에서 파일을 쓰지 못한다는 에러가 나면 대부분 권한 문제입니다.

# 호스트에서 볼륨 디렉터리 권한 조정
sudo chown -R 1000:1000 ~/homelab/grafana
# 또는 컨테이너 내부 UID에 맞게 조정 (Grafana는 472)
sudo chown -R 472:472 ~/homelab/monitoring/grafana

2026년 Docker Compose 신기능 — Watch 모드와 Profiles

최신 버전에서 추가된 기능 중에 홈랩에서 진짜 유용하게 쓰는 것들 소개해 드릴게요.

Profiles — 환경별 선택적 실행

개발 환경에서만 쓰는 서비스가 있잖아요. profiles를 쓰면 상황에 따라 특정 서비스만 선택적으로 올릴 수 있어요.

services:
  # 항상 실행
  traefik:
    image: traefik:v3.2
    # profiles 없으면 기본 실행

  # debug 프로파일일 때만 실행
  phpmyadmin:
    image: phpmyadmin:latest
    profiles:
      - debug
    # ...

# 기본 실행 (phpmyadmin 제외)
docker compose up -d

# debug 프로파일 포함해서 실행
docker compose --profile debug up -d

Watch 모드 — 개발 환경에서 파일 변경 자동 감지

홈랩에서 직접 개발하시는 분들한테 유용한 기능이에요. 파일이 변경되면 자동으로 컨테이너를 업데이트해 줍니다.

services:
  myapp:
    image: node:20
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: package.json

# Watch 모드로 실행
docker compose watch

결과 확인 — 홈랩 모니터링 대시보드

모든 서비스가 올라가고 나면 이런 것들을 확인하실 수 있어요. 처음에 Grafana 대시보드가 뜨는 순간 드디어 됐다! 하는 느낌이 오거든요. 그 뿌듯함은 직접 경험해 보셔야 알아요.

# 전체 서비스 상태 한눈에 보기
docker compose ps

# 출력 예시:
# NAME            IMAGE                    STATUS          PORTS
# traefik         traefik:v3.2             Up 2 hours      0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
# jellyfin        jellyfin/jellyfin        Up 2 hours
# nextcloud       nextcloud:29             Up 2 hours
# nextcloud-db    mariadb:11               Up 2 hours
# portainer       portainer/portainer-ce   Up 2 hours
# prometheus      prom/prometheus          Up 2 hours
# grafana         grafana/grafana          Up 2 hours

# 리소스 사용량 실시간 모니터링
docker stats

# 전체 스택 재시작
docker compose restart

# 이미지 업데이트 후 재배포
docker compose pull && docker compose up -d
Grafana 대시보드에서 홈랩 Docker 컨테이너 CPU, 메모리, 네트워크 실시간 모니터링 화면

▲ Grafana 대시보드에서 각 컨테이너의 CPU, 메모리, 네트워크 사용량을 실시간으로 모니터링하는 화면입니다. Prometheus와 연동하면 이런 대시보드를 손쉽게 구성할 수 있습니다.

서비스 접속 URL 정리

서비스 접속 URL 용도
Traefik Dashboard https://traefik.homelab.example.com 라우팅 현황 확인
Jellyfin https://media.homelab.example.com 미디어 스트리밍
Nextcloud https://cloud.homelab.example.com 파일 관리
Portainer https://portainer.homelab.example.com 컨테이너 관리
Grafana https://monitor.homelab.example.com 모니터링 대시보드

자주 묻는 질문 (FAQ)

Q. Docker Compose와 Kubernetes(쿠버네티스) 중 홈랩엔 뭐가 낫나요?

솔직히 말씀드리면, 홈랩 규모에서는 Docker Compose가 훨씬 현실적입니다. Kubernetes는 학습 곡선이 가파르고, 리소스 오버헤드도 상당해요. 컨테이너 오케스트레이션을 배우는 목적이라면 Docker Compose를 완전히 익히신 다음에 K3s(경량 쿠버네티스)로 넘어가시는 걸 추천드려요. 저도 그 순서로 왔거든요.

Q. compose.yaml 파일을 여러 개로 나눌 수 있나요?

네, 가능합니다! -f 옵션으로 여러 파일을 합쳐서 실행할 수 있어요.

docker compose -f compose.yaml -f compose.monitoring.yaml up -d

Q. 서버 재부팅 시 자동으로 컨테이너가 시작되나요?

restart: unless-stopped 옵션을 넣으면 됩니다. Docker 서비스가 자동 시작(systemctl enable docker)으로 설정되어 있어야 해요.


Docker Compose 홈랩 구성 단계별 요약 인포그래픽 — 설치부터 모니터링까지 전체 로드맵

▲ Docker Compose 홈랩 구성 전체 흐름 요약 인포그래픽 — 설치부터 서비스 배포, 모니터링까지 단계별 로드맵을 한눈에 정리한 이미지입니다.

마무리 — 그래서 뭘 배웠냐고요?

13년 동안 인프라 엔지니어로 일하면서 느끼는 건데, 결국 좋은 도구는 복잡한 것을 단순하게 만들어 줄 때 진가가 드러나더라고요. Docker Compose 2026 버전이 딱 그런 도구입니다. 홈랩 멀티컨테이너 구성이 이렇게 파일 하나로 관리되는 게 처음엔 신기했는데, 지금은 없으면 못 살 것 같아요.

오늘 다룬 내용을 정리하면:

  • Docker Compose V2는 Docker Engine에 내장, 별도 설치 불필요
  • version 필드는 2026년에 deprecated, 생략하세요
  • .env 파일로 민감 정보 분리는 필수
  • depends_onhealthcheck를 함께 써야 제대로 된 의존성 관리
  • 네트워크 분리로 서비스 간 격리 (internal 네트워크 활용)
  • Profiles로 환경별 선택적 실행 가능

다음 글에서는 이 구성을 바탕으로 자동 업데이트(Watchtower)와 백업 자동화를 적용하는 방법을 다룰 예정이에요. 홈서버 운영에서 자동화는 정말 게임 체인저거든요. 이전 글에서 홈랩 초기 세팅 방법도 다뤘으니 참고하시면 좋을 것 같습니다.

궁금한 점이나 삽질 경험 있으시면 댓글로 편하게 남겨주세요. 저도 아직 배우는 중이라, 같이 이야기 나누는 게 좋더라고요. 🎉