일반 서버용 self-contained 배포 폴더.
이 디렉토리만 서버로 복사한 뒤 .env를 준비하면 docker compose로 배포할 수 있다.
SERVICE=app|linkpie|deskpie로 서비스 종류를 지정할 수 있다. 다만 APP_IMAGE는 선택한 SERVICE와 같은 이미지를 가리켜야 한다.
deploy/single-host만 일반 서버 배포의 SSOT로 사용한다.- 이 폴더는 바깥
deploy/shared/monitoring,deploy/compose/tencent/dev,deploy/compose/tencent/stage를 참조하지 않는다. - 배포에 필요한 설정, provisioning, template, helper script를 같은 폴더 안에 닫아 둔다.
- KISS를 위해 앱 내부 포트는
8011로 고정하고, 외부에서 필요한 값만.env로 노출한다. - 동적 처리가 필요한 부분은 monitoring service label 렌더링처럼 작은 shell script로만 제한한다.
docker-compose.dev.yamldocker-compose.prod.yaml.env.example.env.prod.examplecaddy/Caddyfiledb/Dockerfile.postgresmonitoring/loki/config.yamlmonitoring/prometheus/config.tmpl.yamlmonitoring/prometheus/render-config.shmonitoring/promtail/config.tmpl.yamlmonitoring/promtail/render-config.shmonitoring/grafana/provisioning/
- 서버 DNS가
APP_BASE_URL호스트를 현재 서버 IP로 가리키도록 설정한다. - 서버
80/tcp,443/tcp포트를 연다. - 예시 env를 복사해
.env를 만든다.
cp .env.example .env또는 환경별 예시를 그대로 시작점으로 쓸 수 있다.
cp .env.prod.example .env필수 수정 값:
SERVICEAPP_IMAGEDATABASE_URLAPP_BASE_URLACME_EMAILAPP_CRYPTO_CURRENT_PROVIDER=ENVAPP_CRYPTO_PROVIDERS_ENV_KEKAPP_CRYPTO_PROVIDERS_ENV_KEK_KID(선택, 기본값kek-v1)GRAFANA_ADMIN_PASSWORD
bundled PostgreSQL 비밀번호를 기본값(deck)이 아닌 값으로 바꿀 경우에는 아래도 함께 맞춘다.
DB_PASSWORDDATABASE_URL
DATABASE_URL의 user, password에 &, =, %, ?, # 같은 reserved character가 들어가면 percent-encoding해서 넣는다.
시작 순서 제어는 별도 wait-for-pg.sh 없이 처리한다.
dbhealthcheck가pg_isready로 준비 상태를 판단한다.app은depends_on: condition: service_healthy로 DB 준비 후 시작한다.- 불필요한 wrapper script를 두지 않아 single-host 번들을 더 단순하게 유지한다.
기본 포트 노출 정책:
- 외부 공개는
caddy의80/443만 담당한다. app와db는127.0.0.1에만 bind 되므로 서버 외부에서 직접 접근되지 않는다.- 앱 내부 포트는 고정
8011이며, 외부에서 localhost로 붙을 때만APP_PORT를 바꾼다. DB_PORT는 bundled PostgreSQL의 localhost bind용이므로 운영 서버에서는 로컬 점검이나 SSH 터널 용도로만 본다.
앱 런타임 최소 계약은 아래 세 값이다.
DATABASE_URLAPP_BASE_URLAPP_CRYPTO_CURRENT_PROVIDERAPP_CRYPTO_PROVIDERS_ENV_KEK
현재 single-host 번들은 ENV 단일 KEK 운영만 문서화한다.
DB_PASSWORD는 bundled PostgreSQL 컨테이너 설정값이며, 앱은 split datasource env를 읽지 않는다.
프로필:
db: PostgreSQLapp: 선택한SERVICE이미지caddy: HTTPS reverse proxymonitoring:loki,promtail,prometheus,grafana
주의:
- 이 compose는 모든 서비스가 profile에 묶여 있다.
- 그래서 profile 없이
docker compose up -d를 실행하면no service selected가 나온다. app만 서비스 이름으로 지정해도dbprofile은 자동으로 켜지지 않는다.- 즉
app을 띄우려면 최소--profile db --profile app을 같이 줘야 한다.
권장 조합:
- 앱 개발 확인:
db + app - HTTPS 포함 확인:
db + app + caddy - 전체 운영 스택:
db + app + caddy + monitoring
전체 스택:
docker compose -f docker-compose.dev.yaml --env-file .env \
--profile db --profile app --profile caddy --profile monitoring up -d프로덕션:
docker compose -f docker-compose.prod.yaml --env-file .env \
--profile db --profile app --profile caddy --profile monitoring up -d앱만:
docker compose -f docker-compose.dev.yaml --env-file .env \
--profile db --profile app up -d서비스 이름 직접 지정 예시:
docker compose -f docker-compose.dev.yaml --env-file .env \
--profile db --profile app up -d app앱 + HTTPS:
docker compose -f docker-compose.dev.yaml --env-file .env \
--profile db --profile app --profile caddy up -d모니터링 포함:
docker compose -f docker-compose.dev.yaml --env-file .env \
--profile db --profile app --profile monitoring up -d로컬 이미지 빌드 예시:
docker build --build-arg SERVICE=app -t deck-single-host-app:test ../..docker build --build-arg SERVICE=linkpie -t deck-single-host-linkpie:test ../..- 앱:
${APP_BASE_URL} - Grafana:
${APP_BASE_URL}/grafana/
Grafana는 익명 접근을 허용하지 않으며 GRAFANA_ADMIN_PASSWORD가 반드시 필요하다.
Grafana에는 Deck Overview 대시보드가 함께 프로비저닝된다.
- CI는 반드시
docker compose ... config검증을 먼저 수행해.env.example과 compose/template의 계약이 깨지지 않는지 확인해야 한다. deploy/single-host검증은 이 폴더만 복사한 임시 디렉토리에서 실행해야 한다. 바깥 경로를 참조한 채 통과하면 SSOT가 깨진다.- 운영 배포 job은
APP_IMAGE를 빌드/푸시한 뒤 compose 배포 단계로 넘겨야 한다.SERVICE와 다른 이미지를 넣으면 잘못된 서비스가 배포된다. prod배포에서는DATABASE_URL,DB_PASSWORD,GRAFANA_ADMIN_PASSWORD,APP_CRYPTO_PROVIDERS_ENV_KEK,APP_BASE_URL,ACME_EMAIL누락을 즉시 실패시켜야 한다.Let's Encrypt자동 발급은 공개 DNS와80/443개방이 전제다. CI 환경에서는 실제 발급을 검증하지 말고 compose/config와 local TLS wiring만 검증한다.- review automation이 제안하는 개선이라도
single-host외부 의존을 다시 만드는 방식이면 받지 않는다. SSOT와 self-contained 특성이 우선이다.