Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
248 lines (239 loc) · 10.3 KB
/
Copy pathdocker-compose.yml
File metadata and controls
248 lines (239 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# 호스트에 공개하는 포트는 **루프백에만** 묶는다 (BIND_ADDR, 기본 127.0.0.1).
#
# 2026-09-28 점검에서 스택 포트 8개가 전부 0.0.0.0 에 묶여 있었다 — Core·RealTime·AI 뿐
# 아니라 PostgreSQL(38040)·RabbitMQ(38050/38051)·MinIO(38060/38061)까지. 확인한 사실:
# · 서버가 **자기 공인 IP** 로 http://<public>:38010/api/system/health 를 부르면 200 이다
# → 호스트 방화벽이 막지 않는다. ufw 가 있어도 도커는 제 iptables 체인을 앞에 끼워 넣어
# publish 한 포트를 그냥 통과시킨다(잘 알려진 함정).
# · 외부에서는 TCP 핸드셰이크만 간헐적으로 성립하고 HTTP 응답은 오지 않는다
# → 상류(학교망) 어딘가가 걸러 주고 있는 것으로 보인다. **그건 우리 것이 아니다.**
#
# 필요한 곳이 하나도 없다: nginx 는 127.0.0.1:38010 / 127.0.0.1:38020 으로만 프록시하고,
# 서비스끼리는 도커 네트워크 이름(backend:38010, minio:38060, rabbitmq:38050)으로 통신한다.
# 38051/38061 콘솔은 SSH 터널로 계속 볼 수 있다(-L 38061:127.0.0.1:38061).
#
# LAN 에 열어야 하면 BIND_ADDR=0.0.0.0 으로 명시적으로 켠다 — 기본값이 아니라 선택이어야 한다.
name: stackup
services:
postgres:
build: ./infra/postgres
container_name: stackup-postgres
command: ["postgres", "-c", "port=38040"]
ports:
- "${BIND_ADDR:-127.0.0.1}:${POSTGRES_PORT:-38040}:38040"
environment:
POSTGRES_DB: ${POSTGRES_DB:-stackup}
POSTGRES_USER: ${POSTGRES_USER:-stackup}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-stackup}
PGPORT: "38040"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -p 38040 -U ${POSTGRES_USER:-stackup}"]
interval: 5s
timeout: 3s
retries: 5
rabbitmq:
build: ./infra/rabbitmq
container_name: stackup-rabbitmq
ports:
- "${BIND_ADDR:-127.0.0.1}:${RABBITMQ_PORT:-38050}:38050"
- "${BIND_ADDR:-127.0.0.1}:${RABBITMQ_MANAGEMENT_PORT:-38051}:38051"
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-stackup}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:-stackup}
volumes:
- rabbitmq_data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 5
minio:
build: ./infra/minio
container_name: stackup-minio
ports:
- "${BIND_ADDR:-127.0.0.1}:${MINIO_API_PORT:-38060}:38060"
- "${BIND_ADDR:-127.0.0.1}:${MINIO_CONSOLE_PORT:-38061}:38061"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
volumes:
- minio_data:/data
healthcheck:
test:
- "CMD-SHELL"
- "mc alias set hc http://localhost:38060 \"$$MINIO_ROOT_USER\" \"$$MINIO_ROOT_PASSWORD\" >/dev/null 2>&1 && mc ready hc"
interval: 10s
timeout: 5s
retries: 5
minio-init:
image: minio/mc:latest
container_name: stackup-minio-init
depends_on:
minio:
condition: service_healthy
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
MINIO_BUCKET: ${MINIO_BUCKET:-stackup}
volumes:
- ./infra/minio/init.sh:/init.sh:ro
entrypoint: ["/bin/sh", "/init.sh"]
ai:
build: ./ai
container_name: stackup-ai
ports:
- "${BIND_ADDR:-127.0.0.1}:${AI_PORT:-38030}:38030"
# 값이 붙은 항목만 compose 가 소유한다 — 컨테이너 토폴로지(서비스 호스트명)와
# MINIO_* → S3_* 이름 변환처럼 settings.py 가 알 수 없는 것들.
#
# 이름만 적은 항목은 **전달만** 한다: .env 에 있으면 그 값이, 없으면 아무것도 넘어가지
# 않아 settings.py 의 기본값이 그대로 산다. 여기에 `${VAR:-기본값}` 으로 기본값을 또
# 적으면 settings.py 를 고쳐도 운영에 영원히 도달하지 못한다 —
# 실제로 LLM_PRO_TIMEOUT_SEC 를 30→60 으로 올린 변경이 이 줄에 막혀 무효가 됐고,
# 웹 이력서 분석이 계속 타임아웃으로 죽었다(실제 소요 37.4초).
# `${VAR:-}` 도 금지다 — 미설정이 빈 문자열로 주입돼 역시 코드 기본값을 덮는다.
# 중복 방지는 ai/tests/test_compose_defaults.py 가 CI 에서 강제한다.
environment:
- RABBITMQ_URL=amqp://${RABBITMQ_USER:-stackup}:${RABBITMQ_PASSWORD:-stackup}@rabbitmq:38050/
- S3_ENDPOINT_URL=http://minio:38060
- S3_ACCESS_KEY=${MINIO_ROOT_USER:-minioadmin}
- S3_SECRET_KEY=${MINIO_ROOT_PASSWORD:-minioadmin}
- S3_BUCKET_NAME=${MINIO_BUCKET:-stackup}
- STORAGE_LOCAL_ROOT=${STORAGE_LOCAL_ROOT:-/var/lib/stackup-ai/storage}
# AI → Core 내부 API. 같은 compose 네트워크의 backend 서비스로 직접 라우팅.
- CORE_INTERNAL_BASE_URL=${CORE_INTERNAL_BASE_URL:-http://backend:38010}
- CORE_INTERNAL_API_KEY=${CORE_INTERNAL_API_KEY:-local-development-internal-api-key}
# ── 아래는 전달만. 기본값의 주인은 ai/src/ai_server/config/settings.py ──
- S3_REGION
- STORAGE_BACKEND
- LLM_API_KEY
- LLM_BASE_URL
- LLM_PRO_MODEL
- LLM_FLASH_MODEL
- LLM_FLASH_TEMPERATURE
- LLM_FLASH_MAX_TOKENS
- LLM_PRO_TIMEOUT_SEC
- LLM_PRO_BASE_URL
- LLM_PRO_API_KEY
- LLM_FLASH_BASE_URL
- LLM_FLASH_API_KEY
- LLM_FLASH_TIMEOUT_SEC
- LLM_MAX_RETRIES
- DOCUMENT_ANALYSIS_MAX_ATTEMPTS
- DOCUMENT_ANALYSIS_RETRY_INITIAL_SEC
- QUESTIONS_RAG_TIMEOUT_SEC
- STT_PROVIDER
- STT_MAX_ATTEMPTS
- STT_RETRY_BACKOFF_SEC
- DEEPGRAM_API_KEY
- DEEPGRAM_BASE_URL
- DEEPGRAM_MODEL
- DEEPGRAM_LANGUAGE
- DEEPGRAM_TIMEOUT_SEC
- DEEPGRAM_CONNECT_TIMEOUT_SEC
- EMBEDDING_PROVIDER
- EMBEDDING_MODEL
- EMBEDDING_DIM
- GEMINI_API_KEY
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
rabbitmq:
condition: service_healthy
minio:
condition: service_healthy
minio-init:
condition: service_completed_successfully
backend:
# Spring RabbitMqConfig 가 ai.generate.*/ai.analyze.* 큐를 declare 한 뒤에야
# AI runner.py(passive=true 큐 검색)가 부팅 가능. backend healthy 이후 시작.
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:38030/health')"]
interval: 10s
timeout: 5s
retries: 5
realtime:
build: ./realtime
container_name: stackup-realtime
ports:
- "${BIND_ADDR:-127.0.0.1}:${REALTIME_PORT:-38020}:38020"
environment:
REALTIME_LISTEN_ADDR: :38020
REALTIME_RABBITMQ_URL: amqp://${RABBITMQ_USER:-stackup}:${RABBITMQ_PASSWORD:-stackup}@rabbitmq:38050/
REALTIME_LOG_LEVEL: ${REALTIME_LOG_LEVEL:-info}
REALTIME_JWT_SECRET: ${JWT_SECRET:-}
REALTIME_CORE_BASE_URL: ${CORE_INTERNAL_BASE_URL:-http://backend:38010}
REALTIME_INTERNAL_API_KEY: ${CORE_INTERNAL_API_KEY:-local-development-internal-api-key}
REALTIME_WS_WRITE_TIMEOUT: ${REALTIME_WS_WRITE_TIMEOUT:-10s}
REALTIME_SSE_WRITE_TIMEOUT: ${REALTIME_SSE_WRITE_TIMEOUT:-10s}
depends_on:
rabbitmq:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:38020/health"]
interval: 10s
timeout: 5s
retries: 5
backend:
build:
context: ./backend
container_name: stackup-backend
ports:
- "${BIND_ADDR:-127.0.0.1}:${BACKEND_PORT:-38010}:38010"
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-local}
SERVER_PORT: "38010"
POSTGRES_HOST: postgres
POSTGRES_PORT: "38040"
POSTGRES_DB: ${POSTGRES_DB:-stackup}
POSTGRES_USER: ${POSTGRES_USER:-stackup}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-stackup}
RABBITMQ_HOST: rabbitmq
RABBITMQ_PORT: "38050"
RABBITMQ_USER: ${RABBITMQ_USER:-stackup}
RABBITMQ_PASSWORD: ${RABBITMQ_PASSWORD:-stackup}
# 비밀값은 .env 또는 GitHub Secrets 로 주입. 값이 비면 application-local.yml 의 dev default 적용.
JWT_SECRET: ${JWT_SECRET:-}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-}
# 액세스 토큰 수명. application.yml 기본 900s(15분)은 면접 진행 중 만료로 이어져
# 배포 기본을 3600s(1시간)로 상향. 필요 시 .env(JWT_ACCESS_TTL_SECONDS)로 override.
JWT_ACCESS_TTL_SECONDS: ${JWT_ACCESS_TTL_SECONDS:-3600}
CORE_INTERNAL_API_KEY: ${CORE_INTERNAL_API_KEY:-local-development-internal-api-key}
GITHUB_OAUTH_CLIENT_ID: ${GITHUB_OAUTH_CLIENT_ID:-}
GITHUB_OAUTH_CLIENT_SECRET: ${GITHUB_OAUTH_CLIENT_SECRET:-}
GITHUB_OAUTH_REDIRECT_URI: ${GITHUB_OAUTH_REDIRECT_URI:-http://localhost:5173/auth/callback}
GOOGLE_OAUTH_CLIENT_ID: ${GOOGLE_OAUTH_CLIENT_ID:-}
GOOGLE_OAUTH_CLIENT_SECRET: ${GOOGLE_OAUTH_CLIENT_SECRET:-}
GOOGLE_OAUTH_REDIRECT_URI: ${GOOGLE_OAUTH_REDIRECT_URI:-http://localhost:5173/auth/google/callback}
S3_ENDPOINT: http://minio:38060
S3_ACCESS_KEY: ${MINIO_ROOT_USER:-minioadmin}
S3_SECRET_KEY: ${MINIO_ROOT_PASSWORD:-minioadmin}
S3_BUCKET: ${MINIO_BUCKET:-stackup}
S3_REGION: ${S3_REGION:-us-east-1}
S3_PATH_STYLE: "true"
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-*}
volumes:
# 백업 신선도 확인용(BackupHealthIndicator). 호스트 경로라 compose 가 소유한다.
# 읽기 전용 — 앱이 백업 파일을 건드릴 일은 없다.
- ${BACKUP_DIR:-/home/stackup/backups}:/var/backups/stackup:ro
depends_on:
postgres:
condition: service_healthy
rabbitmq:
condition: service_healthy
minio:
condition: service_healthy
healthcheck:
# 종합(/actuator/health)이 아니라 readiness 그룹을 본다 — s3/aiServer 장애로
# 백엔드 컨테이너가 unhealthy 가 되면 정작 멀쩡한 로그인·히스토리까지 끊긴다.
test: ["CMD-SHELL", "curl -sf http://localhost:38010/actuator/health/readiness >/dev/null || exit 1"]
interval: 10s
timeout: 5s
retries: 15
volumes:
postgres_data:
rabbitmq_data:
minio_data: