SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-27

Ubuntu 24.04 Docker Compose 설치 및 설정 가이드

Ubuntu 24.04 환경에서 Docker Engine과 Compose v2를 올바르게 설치하는 방법을 안내합니다. UFW 방화벽 포트 노출 문제 해결과 데이터 볼륨 백업 전략을 포함하여 실무에서 즉시 활용 가능한 compose.yml 작성법을 상세히 설명합니다.

구축할 내용

Docker Compose는 이 사이트에서 다루는 거의 모든 서비스의 기반입니다. Nextcloud, Vaultwarden, n8n, Immich, Rocket.Chat 등 모든 가이드는 "이 compose 파일을 작성하십시오"라는 문구로 시작하며, 이 페이지는 해당 파일이 실제로 무엇을 의미하는지 설명합니다. Ubuntu 24.04의 공식 Docker apt 저장소에서 Docker Engine과 Compose v2 플러그인을 설치한 다음, 실제 두 개의 서비스로 구성된 스택을 실행합니다. RSS 리더인 Miniflux와 PostgreSQL을 조합하는 이유는 이 쌍이 대규모 애플리케이션에서 사용하는 모든 패턴(고정된 이미지, 헬스체크가 포함된 데이터베이스, 명명된 볼륨, .env 파일 내의 비밀값, localhost로만 공개된 포트)을 연습할 수 있기 때문입니다.

설치는 5분 정도 소요됩니다. 이 가이드의 나머지 부분에서는 나중에 문제가 될 수 있는 요소들을 다룹니다. 예를 들어 root와 다를 바 없는 docker 그룹 권한, ufw 규칙을 우회하여 노출되는 포트, 그리고 확인 절차 없이 데이터베이스를 삭제해 버리는 docker compose down의 특정 플래그 등이 이에 해당합니다.

사전 요구 사항: 새로 설치된 Ubuntu 24.04 KVM VPS, sudo 권한이 있는 사용자, 1GB 이상의 RAM. 기존에 Docker가 설치되어 있어도 괜찮으며, 첫 번째 섹션에서 제거해야 할 항목을 다룹니다.

Ubuntu 저장소가 아닌 Docker 저장소에서 설치하기

첫 번째 명령어를 실행하기 전에 피해야 할 두 가지 실수가 있습니다. Ubuntu의 자체 docker.io 패키지는 작동은 하지만 Docker의 최신 릴리스보다 버전이 뒤처지며, 다른 모든 도구가 가정하는 플러그인 레이아웃을 따르지 않습니다. 또한 하이픈이 포함된 독립형 docker-compose 바이너리는 Compose v1으로, Python 기반이며 2023년부터 지원이 종료되었습니다. 이것이 오래된 튜토리얼이 작동하지 않는 이유입니다. 현재의 Compose는 공백이 포함된 docker compose이며, 엔진과 동일한 저장소에서 설치되는 CLI 플러그인입니다.

만약 시스템에 이러한 패키지가 이미 설치되어 있다면, Ubuntu의 자체 플러그인 패키지인 docker-compose-v2을 포함하여 모두 삭제하십시오. 그래야 모든 구성 요소가 하나의 저장소에서 관리됩니다.

sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc

새로운 VPS라면 Package 'docker.io' is not installed, so not removed이 정상적인 출력입니다. 그 후 Docker 저장소를 추가하고 설치를 진행합니다.

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

세 가지 계층을 모두 확인합니다.

docker --version
docker compose version
sudo docker run --rm hello-world

앞의 두 명령어는 버전 문자열을 출력하며, Docker Compose version v2.x.x는 사용 중인 것이 v1 바이너리가 아닌 플러그인임을 확인해 줍니다. hello-world 실행 결과는 Hello from Docker!로 끝나야 합니다. 패키지는 부팅 시 서비스를 자동으로 활성화하며, systemctl is-enabled dockerenabled을 출력합니다.

docker 그룹은 root 권한과 같으므로 신중하게 결정하십시오

현재 모든 docker 명령은 sudo 권한이 필요합니다. 이는 /var/run/docker.sock에 위치한 데몬 소켓의 소유자가 root이며 docker 그룹에 속해 있기 때문입니다. 이 그룹에 포함되지 않으면 Docker와 관련하여 가장 많이 검색되는 다음과 같은 오류가 발생합니다.

permission denied while trying to connect to the Docker daemon socket at
unix:///var/run/docker.sock

일반적인 해결 방법은 다음과 같습니다.

sudo usermod -aG docker $USER

그룹 멤버십은 로그인 시점에 적용되므로 현재 셸에서는 오류가 계속 발생합니다. 이번 세션에만 적용하려면 newgrp docker을 실행하거나, 로그아웃 후 다시 로그인하십시오. 그러면 id 명령을 실행했을 때 그룹 목록에 docker이 포함되어야 합니다.

이제 솔직하게 말씀드리겠습니다. docker 그룹의 멤버가 되는 것은 호스트의 root 권한을 갖는 것과 같습니다. 'root와 유사한' 권한이나 '상승된' 권한이 아니라, 완전히 root입니다. 해당 그룹에 속한 사용자는 누구나 docker run --rm -it -v /:/host alpine chroot /host를 실행하여 비밀번호 없이 전체 파일 시스템을 장악할 수 있습니다. 이 그룹은 보안 격리가 아닌 편의를 위해 존재합니다.

Docker의 rootless 모드가 진정한 대안입니다. 이 모드에서는 데몬 자체가 권한이 없는 일반 사용자 계정으로 실행됩니다. 다만 감수해야 할 점이 있습니다. 1024 미만의 포트를 사용하려면 추가 설정이 필요하고, 네트워킹은 사용자 공간 shim을 거치므로 측정 가능한 오버헤드가 발생하며, 일부 이미지는 실제 root 권한이 없으면 정상적으로 작동하지 않을 수 있습니다. 관리자가 한 명뿐이고 이미 sudo 권한을 가진 VPS 환경에서는 이 그룹 변경이 실질적으로 큰 차이를 만들지 않으며, 이 가이드의 모든 내용도 이를 전제로 합니다. 다만, 이 권한이 sudo보다 낮은 수준인 것처럼 취급해서는 안 됩니다.

Compose 파일의 구조

각 스택을 별도의 디렉터리에 배치하십시오. 디렉터리 이름이 프로젝트 이름이 되며, 이는 컨테이너, 네트워크, 볼륨 이름의 접두사로 사용됩니다.

sudo mkdir -p /opt/miniflux && sudo chown $USER /opt/miniflux && cd /opt/miniflux

compose.yml를 생성하십시오(최신 명칭이며, docker-compose.yml도 여전히 작동합니다). 구식 version: 키는 사용하지 마십시오. 이는 더 이상 사용되지 않으며 Compose가 이를 감지하면 경고를 표시합니다.

services:
  miniflux:
    image: miniflux/miniflux:2.2.9
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      - DATABASE_URL=postgres://miniflux:${POSTGRES_PASSWORD}@db/miniflux?sslmode=disable
      - RUN_MIGRATIONS=1
      - CREATE_ADMIN=1
      - ADMIN_USERNAME=admin
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER=miniflux
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=miniflux
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "miniflux", "-d", "miniflux"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  db-data:

위의 모든 줄은 의사결정의 결과입니다. 하나씩 살펴보겠습니다.

이미지 버전 고정하기: :latest와 pull은 자동 업데이트를 유발합니다

postgres:latest가 아닌 postgres:16-alpine을 사용하십시오. 태그는 고정된 값이 아닙니다. :latest은 pull을 수행할 때마다 관리자가 가장 최근에 푸시한 이미지로 다시 확인됩니다. 이를 곧 배우게 될 일상적인 업데이트 습관인 docker compose pull && docker compose up -d과 결합하면, :latest는 사용자가 원할 때가 아니라 업스트림에서 배포할 때마다 메이저 버전이 점프하게 됨을 의미합니다. PostgreSQL의 경우 이는 가상의 문제가 아닙니다. 16에서 17로 갑작스럽게 버전이 올라가면 데이터 디렉터리 호환성 문제로 컨테이너가 크래시 루프에 빠집니다. Postgres 메이저 업그레이드는 재시작이 아니라 덤프 및 복원 과정이 필요하기 때문입니다.

최소한 메이저 버전은 고정하십시오(postgres:16-alpine은 16.x 패치 릴리스를 따릅니다). 애플리케이션은 miniflux/miniflux:2.2.9와 같이 정확한 릴리스 버전으로 고정하십시오. 프로젝트의 릴리스 페이지를 확인하고 파일을 작성할 당시의 최신 버전을 사용하십시오. 이렇게 하면 업그레이드는 의도적으로 수행하는 한 줄 수정이 되며, git diff에서 변경 사항을 확인할 수 있습니다.

127.0.0.1로 게시하기: Docker는 ufw를 우회합니다

"127.0.0.1:8080:8080"은 호스트 주소, 호스트 포트, 컨테이너 포트 순입니다. 대부분의 튜토리얼은 "8080:8080"을 사용하는데, 이는 0.0.0.0:8080:8080의 약어입니다. 즉, 공용 인터페이스를 포함한 모든 인터페이스에서 수신 대기합니다.

여기서 함정이 발생하며, 거의 모든 사람이 한 번씩은 겪게 됩니다. Docker는 필터링이 발생하기 에 패킷의 목적지를 컨테이너의 내부 IP로 재작성하는 DNAT 규칙을 작성하여 포트를 게시합니다. 따라서 패킷은 FORWARD 경로를 타게 되며, ufw 규칙이 존재하는 INPUT을 전혀 거치지 않습니다. sudo ufw deny 8080은 성공을 보고하고, ufw status는 포트가 거부된 것처럼 보이지만, 서비스는 여전히 인터넷 전체에 응답합니다. 방화벽이 고장 난 것이 아니라, 설계상 우회되고 있는 것입니다. Docker가 ufw를 우회하는 이유와 컨테이너 트래픽을 제대로 필터링하는 방법에서 이 메커니즘과 공용으로 유지해야 하는 포트를 위한 DOCKER-USER 해결책을 다룹니다.

이 문제를 완전히 해결하는 습관은 다음과 같습니다. 특별한 이유가 없다면 게시된 포트를 127.0.0.1에 바인딩하고, 외부로 노출해야 하는 모든 서비스 앞단에 리버스 프록시를 두십시오. 이것이 바로 이 페이지 다음 단계에서 Traefik 리버스 프록시 가이드가 구축하는 내용입니다. 포트 80과 443을 점유하고 TLS를 적용하여 호스트 이름별로 모든 서비스에 라우팅하는 컨테이너를 하나 운영하는 방식입니다. (이전 Traefik v2 설정을 사용 중이라면 Traefik v2에서 v3로의 마이그레이션 가이드에서 이름 변경 및 규칙 변경 사항을 확인하십시오.)

스택을 시작한 후 바인딩을 확인하십시오. sudo ss -tlnp | grep 8080 명령 결과에 0.0.0.0:8080이나 *:8080이 아닌 127.0.0.1:8080이 표시되어야 합니다.

네임드 볼륨 vs 바인드 마운트

db-data:/var/lib/postgresql/data는 네임드 볼륨입니다. Docker는 /var/lib/docker/volumes/ 아래에 디렉터리를 생성 및 관리하고 이를 컨테이너에 마운트합니다. 대안은 바인드 마운트인 ./data:/var/lib/postgresql/data이며, 이는 호스트에서 선택한 경로를 매핑합니다.

실무에서 통용되는 구분은 다음과 같습니다. 컨테이너만 접근하는 데이터(특히 데이터베이스)에는 네임드 볼륨을 사용하십시오. Docker가 이미지가 기대하는 소유권으로 볼륨을 초기화하므로 파일 권한 문제가 발생하지 않습니다. 호스트에서 직접 수정하는 파일에는 바인드 마운트를 사용하십시오. 텍스트 편집기로 수정하는 설정 파일, rsync로 동기화하는 미디어 라이브러리 등 경로가 명확해야 하는 모든 경우에 적합합니다. 바인드 마운트에서 흔히 발생하는 실패는 소유권 문제입니다. 컨테이너는 UID 999로 실행되는데 호스트 디렉터리는 UID 1000이 소유하고 있어, 앱이 시작 시 로그에 permission denied를 남기며 종료되는 경우입니다. 네임드 볼륨은 데이터가 Docker 관리 경로에 저장된다는 단점이 있지만, 이러한 유형의 버그를 대부분 제거해 줍니다.

environment와 .env, git에서 비밀 정보 제외하기

${POSTGRES_PASSWORD}은 셸에서 읽어오는 것이 아닙니다. Compose는 compose.yml 옆에 있는 .env 파일에서 값을 보간합니다. 파일을 생성하십시오:

cat > .env <<'EOF'
POSTGRES_PASSWORD=change-me-to-something-long
ADMIN_PASSWORD=change-me-too
EOF
chmod 600 .env
echo ".env" >> .gitignore

openssl rand -hex 24을 사용하여 실제 값을 생성하십시오. 의도적으로 base64가 아닌 16진수를 사용합니다. 이 비밀번호는 DATABASE_URL 연결 문자열 내부에 포함되는데, base64가 생성하는 /, +, = 문자는 URL 파싱을 방해하기 때문입니다. 이는 구문 오류가 아닌 인증 오류로 나타나며, 해결하는 데 많은 시간을 허비하게 만듭니다. .gitignore 줄은 첫 커밋 전에 추가하십시오. compose 파일은 게시하고 버전 관리해도 안전하지만, .env 파일은 절대 안 됩니다. git 기록에 포함된 비밀 정보는 이미 유출된 것으로 간주하고 교체해야 합니다. 변수가 누락된 상태로 스택을 시작하면 Compose는 경고를 표시하고 빈 문자열로 계속 진행합니다. Postgres 비밀번호의 경우 이는 배포 실패를 의미합니다:

WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string.

docker compose config은 완전히 보간된 파일을 출력합니다. 컨테이너가 실제로 어떤 값을 받게 될지 확인하는 가장 빠른 방법입니다. 출력 내용에 비밀 정보가 포함되어 있음을 기억하십시오.

depends_on은 healthcheck를 추가하지 않으면 아무것도 기다리지 않습니다

단순한 depends_on: [db]는 시작 순서만 제어합니다. Compose는 Postgres를 먼저 실행하고 잠시 후 앱을 실행하는데, 이때 Postgres는 아직 연결을 수락할 준비가 되지 않은 상태일 수 있습니다. 앱은 데이터베이스에 접속을 시도하다 실패하고, 작성 방식에 따라 크래시가 나거나 재시도하게 됩니다.

신뢰할 수 있는 방식은 위 파일에서 사용하는 형태입니다. db 서비스는 healthcheck을 정의하고(Postgres는 이를 위해 pg_isready을 제공합니다), 앱은 condition: service_healthy를 사용하여 depends_on을 선언합니다. Compose는 데이터베이스를 시작하고 10초마다 상태를 확인하며, 확인이 통과된 후에만 Miniflux를 시작합니다. 비밀번호 오류나 볼륨 손상 등으로 데이터베이스가 정상 상태가 되지 않으면 앱은 시작되지 않으며, Compose는 어떤 의존성에서 실패했는지 알려줍니다:

dependency failed to start: container miniflux-db-1 is unhealthy

해당 메시지는 실제 오류가 발생하는 docker compose logs db을 가리킵니다.

restart: unless-stopped

두 서비스 모두에 restart: unless-stopped을 설정하면 크래시 발생 후나 VPS 재부팅 후에도 컨테이너가 다시 살아납니다. 하지만 사용자가 의도적으로 docker compose stop를 실행한 경우에는 종료된 상태를 유지합니다. 대안인 always은 수동으로 중지한 후에도 컨테이너를 다시 살리는데, 이는 보통 의도한 동작이 아닐 것입니다. 재시작 정책이 없으면 새벽 4시에 커널 업데이트로 재부팅될 때 사용자가 인지할 때까지 서비스가 조용히 중단된 상태로 남게 됩니다.

일상적인 명령어

매일 수행하는 작업은 프로젝트 디렉터리에서 실행하는 5가지 명령어로 충분합니다.

docker compose up -d        # create and start; idempotent, recreates only what changed
docker compose ps           # status, ports, and health of this project's containers
docker compose logs -f miniflux   # follow one service's logs; --tail 100 for recent history
docker compose pull && docker compose up -d   # upgrade to the pinned tags
docker compose down         # stop and remove containers and the network

up -d는 반복해서 실행해도 안전합니다. 이 명령어는 파일과 실제 상태를 비교하여 설정이나 이미지가 변경된 서비스만 처리합니다. 업그레이드 쌍인 두 명령어는 고정된 태그가 가리키는 최신 버전을 가져옵니다. postgres:16-alpine는 패치 릴리스를 업데이트하며, 버전을 정확히 고정한 경우에는 직접 수정하기 전까지는 아무것도 변경하지 않습니다. 이것이 바로 고정의 목적입니다. 업그레이드 후에는 이전 이미지들이 쌓이게 되는데, docker image prune -f을 사용하여 디스크 공간을 확보하십시오.

이제 파괴적인 명령어를 강조하여 설명합니다. docker compose down은 안전합니다. 컨테이너와 네트워크는 일회성이며 데이터는 볼륨에 저장되기 때문입니다. docker compose down -v은 명명된 볼륨까지 삭제합니다. 데이터베이스가 즉시 삭제되며, 확인 절차나 되돌리기 기능은 없습니다. -v 플래그는 실험적인 환경을 정리할 때 사용합니다. 실제 데이터가 포함된 스택에서는 rm -rf을 다루듯이 신중하게 사용하십시오. /var/lib/docker/volumes/에는 휴지통이 없습니다.

실행 중인 컨테이너 내부에서 일회성 셸을 사용하려면 다음을 이용하십시오. docker compose exec db psql -U miniflux는 데이터베이스 내부로 진입하게 해주며, docker compose exec miniflux sh은 애플리케이션 컨테이너의 셸을 제공합니다.

데이터가 실제로 저장되는 위치

Named volume에는 프로젝트 접두사가 붙으므로, miniflux라는 디렉터리 내의 db-dataminiflux_db-data이 됩니다:

docker volume ls
docker volume inspect miniflux_db-data

inspect 출력 내용 중 중요한 줄은 다음과 같습니다:

"Mountpoint": "/var/lib/docker/volumes/miniflux_db-data/_data"

해당 디렉터리는 호스트 파일 시스템에 위치하며 root 소유인 데이터베이스입니다. 이 디렉터리는 down, 업그레이드, 컨테이너 재빌드 후에도 유지됩니다. 또한 백업 시 반드시 포함해야 하는 대상이기도 합니다.

Named 볼륨 백업하기

표준적인 방식은 볼륨을 읽기 전용으로 마운트한 일회용 컨테이너를 호스트 디렉터리 옆에 두고, tar로 압축하는 것입니다.

docker run --rm \
  -v miniflux_db-data:/data:ro \
  -v "$PWD":/backup \
  alpine:3.22 tar czf /backup/miniflux-db-$(date +%F).tar.gz -C /data .

별도의 설치가 필요 없으며 실행 중인 프로세스도 남지 않습니다. 복구는 이와 반대로 마운트를 설정한 뒤 tar xzf를 사용하여 비어 있는 새 볼륨에 압축을 푸는 방식입니다.

데이터베이스 사용 시 주의할 점이 있습니다. 실행 중인 Postgres 데이터 디렉터리를 tar로 압축하면 쓰기 도중의 상태가 캡처되어 정상적으로 시작되지 않을 수 있습니다. tar 작업이 진행되는 몇 초 동안 docker compose stop를 수행하거나, 더 나은 방법으로 구조적으로 일관성이 보장되는 논리적 덤프를 생성하십시오.

docker compose exec -T db pg_dump -U miniflux miniflux | gzip > miniflux-$(date +%F).sql.gz

-T 옵션은 Compose가 기본적으로 할당하는 의사 터미널(pseudo-terminal)을 비활성화합니다. 덤프 출력을 TTY로 파이핑하면 데이터가 손상될 수 있기 때문입니다. 이 명령 중 하나를 cron에 등록하고 결과물을 VPS 외부로 복사하십시오. 원본 데이터와 같은 디스크에 저장된 백업은 진정한 백업이 아니라 단순한 복사본일 뿐입니다. Nextcloud 가이드에서는 정확히 이 두 가지 패턴을 활용하여 전체 백업 루틴을 구성합니다.

실패 유형 및 확인 가능한 메시지

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock는 아직 docker 그룹에 포함되지 않았거나, 그룹에 추가되었더라도 현재 세션이 이를 반영하지 못했을 때 발생합니다. id 명령으로 현재 적용된 그룹을 확인하고, newgrp docker 명령으로 현재 셸에 즉시 적용할 수 있습니다. 로그아웃 후 다시 로그인하면 모든 세션에 변경 사항이 반영됩니다.

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?는 다른 문제입니다. Docker 데몬 자체가 실행 중이지 않은 상태입니다. sudo systemctl status dockersudo journalctl -u docker -n 50 명령으로 원인을 파악할 수 있습니다. VPS 환경에서는 디스크 용량 부족이 흔한 원인이므로 df -h /var/lib/docker 명령으로 먼저 확인하십시오.

Bind for 127.0.0.1:8080 failed: port is already allocated는 다른 컨테이너가 이미 해당 호스트 포트를 점유하고 있을 때 발생합니다. docker ps 명령으로 점유 중인 컨테이너를 확인할 수 있으며, 보통 docker run 주 전에 테스트로 실행했던 컨테이너가 남아있는 경우가 많습니다. docker ps 명령으로 확인했을 때 컨테이너가 없다면, Docker 외부의 프로세스가 포트를 점유 중일 수 있습니다. sudo ss -tlnp | grep 8080 명령으로 해당 프로세스를 찾을 수 있습니다.

yaml: line 14: did not find expected key는 명시된 줄 또는 그 바로 윗줄의 들여쓰기 오류입니다. Compose 파일은 YAML 형식을 따르며, 2칸 들여쓰기를 사용해야 합니다. 탭 문자가 포함되면 오류가 발생하므로 반드시 공백만 사용하십시오. docker compose config 명령은 컨테이너를 시작하지 않고 파일의 유효성을 검사합니다. 수정할 때마다 이 명령을 실행하는 습관을 들이는 것이 좋습니다.

ufw 관련 문제는 별도의 오류 메시지를 출력하지 않아 위험합니다. 배포는 성공한 것처럼 보이고 ufw status 명령 결과도 정상으로 나타나지만, 외부에서 포트 스캔을 수행하면 데이터베이스 포트가 노출될 수 있습니다. 앞서 설명한 포트 섹션을 다시 읽고, 모든 ports: 항목에 127.0.0.1: 접두사가 누락되지 않았는지 확인하십시오. 다른 기기에서 curl http://your-vps-ip:8080 명령을 실행하여 connection refused 응답이 오는지 확인하는 것이 확실합니다.

이제 Traefik 가이드를 통해 단일 스택을 하나의 HTTPS 진입점 뒤에 여러 애플리케이션을 배치하는 구조로 확장할 수 있으며, 2026년에 직접 호스팅할 가치가 있는 서비스 목록을 참고하여 운영할 서비스를 선택할 수 있습니다. 여러 스택을 운영하면서 각 서비스마다 로그인 화면을 관리하는 것이 번거롭다면, Authentik과 같은 셀프 호스팅 SSO 서버를 사용하여 동일한 프록시 뒤에서 하나의 계정으로 통합 관리할 수 있습니다.

VPS에서 운영하는 Minecraft 서버 같은 게임 서버는 Compose를 처음 연습하기에 적합하다. 매일 사용하는 서비스로 배우고 싶다면 self-hosted 운동 기록 관리 도구인 openGym을 사용할 수 있다. 이 도구는 이미지 태그가 아니라 git 태그로 버전이 고정된 소규모 스택이다. 첫 번째 passkey를 등록하기 전에 앞단에 TLS를 구성해야 한다. 사진은 다른 사람이 운영하는 클라우드에서 가장 먼저 되찾고 싶어 하는 데이터인 경우가 많다. PhotoPrism과 Immich 비교를 먼저 확인하면 어느 쪽에든 볼륨을 연결하기 전에 필요한 최소 RAM과 백업 방식을 결정할 수 있다. 서비스 2개로 부족하다고 느껴지면 Notion 스타일의 작업 공간으로 AFFiNE 구축하기를 시도할 수 있다. 이는 동일한 패턴을 컨테이너 4개에 적용하는 작업이며, 앞에서 설정한 태그 고정, healthcheck, named volume이 습관으로 자리 잡았는지 확인하는 좋은 시험이 된다.

FAQ

Docker daemon socket에 연결하려고 할 때 "permission denied"가 발생하는 이유는 무엇입니까?

사용자가 docker 그룹에 속해 있지 않거나, 현재 세션이 시작된 이후에 그룹에 추가되었기 때문입니다. 그룹 권한은 로그인 시점에만 적용됩니다. sudo usermod -aG docker $USER을 실행한 뒤 newgrp docker를 수행하거나, 로그아웃 후 다시 로그인하십시오. 그 후 id으로 확인합니다. 이 그룹은 호스트에 대해 root와 동일한 권한을 부여하므로, sudo 권한을 줄 수 있는 사용자에게만 추가하십시오.

docker compose down을 실행하면 데이터가 삭제됩니까?

일반적인 docker compose down는 데이터를 삭제하지 않으며, 컨테이너와 프로젝트 네트워크만 제거합니다. 명명된 볼륨(named volume)은 유지되며, 다음 up -d 실행 시 다시 연결됩니다. docker compose down -v은 파괴적인 명령어로, 명명된 볼륨을 삭제하므로 데이터베이스 데이터가 사라집니다. 이 과정은 확인 절차 없이 진행되며 되돌릴 수 없습니다. 검증된 백업이 없다면 실제 데이터가 있는 스택에 -v을 절대 실행하지 마십시오.

docker-compose와 docker compose의 차이점은 무엇입니까?

docker-compose(하이픈 포함)은 Compose v1으로, 2023년에 수명이 종료된 독립형 Python 바이너리입니다. 새 서버에는 설치하지 않아야 합니다. docker compose(공백 포함)은 Compose v2로, Docker CLI를 위한 Go 플러그인이며 Docker의 apt 저장소에서 docker-compose-plugin으로 설치됩니다. 명령어와 YAML 형식은 거의 완벽하게 호환되므로, 오래된 튜토리얼에서 docker-compose up을 사용하라고 안내한다면 docker compose up를 입력하십시오.

ufw가 포트를 차단하고 있는데도 인터넷에서 Docker 컨테이너에 접근할 수 있는 이유는 무엇입니까?

Docker는 iptables의 PREROUTING 체인에 DNAT 규칙을 사용하여 포트를 게시합니다. 재작성된 패킷은 Docker 자체 체인을 통해 FORWARD 경로로 이동하므로, ufw 규칙이 적용되는 INPUT 체인을 거치지 않습니다. 따라서 ufw deny 8080은 게시된 컨테이너 포트에 아무런 영향을 주지 못합니다. 근본적인 해결책은 127.0.0.1:로 게시하고, 리버스 프록시를 통해 서비스를 노출하는 것입니다.

명명된 볼륨(named volume)과 바인드 마운트(bind mount) 중 무엇을 사용해야 합니까?

컨테이너만 사용하는 데이터, 특히 데이터베이스에는 명명된 볼륨을 사용하십시오. Docker가 이미지에서 요구하는 소유권을 설정하므로 권한 문제가 발생하지 않습니다. 호스트에서도 직접 다루는 파일, 즉 편집해야 하는 설정 파일이나 업로드하는 미디어 파일 등 경로가 명확해야 하는 경우에는 바인드 마운트를 사용하십시오. 바인드 마운트 사용 시 컨테이너가 시작되면서 permission denied 오류가 발생한다면, 호스트와 컨테이너 간의 UID 불일치를 가장 먼저 확인해야 합니다.