-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
460 lines (431 loc) · 15.3 KB
/
docker-compose.yml
File metadata and controls
460 lines (431 loc) · 15.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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
x-baklib-common: &baklib-common
image: ${IMAGE_NAME}:${IMAGE_TAG}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
etcd01:
condition: service_healthy
etcd02:
condition: service_healthy
etcd03:
condition: service_healthy
etcd-init:
condition: service_completed_successfully
networks:
- baklib
environment: &baklib-env
# Rails 基础配置
RAILS_ENV: production
SHOW_VERIFICATION_CODE: ${SHOW_VERIFICATION_CODE}
INGRESS_PROTOCOL: ${INGRESS_PROTOCOL:-https}
INGRESS_PORT: ${INGRESS_PORT:-443}
# 数据库配置(容器内固定值,基于 Docker Compose 服务名称)
APP_DATABASE_HOST: baklib-db
APP_DATABASE_PORT: 5432
APP_DATABASE_USERNAME: postgres
APP_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
APP_DATABASE_NAME: baklib_production
# ETCD 配置(容器内固定值,基于 Docker Compose 服务名称)
ETCD_ENDPOINTS: http://etcd01:2379,http://etcd02:2379,http://etcd03:2379
ETCD_USER: root
ETCD_PASSWORD: ${ETCD_ROOT_PASSWORD}
# 应用核心配置(从 .env 读取)
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
MAIN_DOMAIN: ${MAIN_DOMAIN}
SAAS_DOMAIN_SUFFIX: ${SAAS_DOMAIN_SUFFIX}
FREE_DOMAIN_SUFFIX: ${FREE_DOMAIN_SUFFIX}
CNAME_DNS_SUFFIX: ${CNAME_DNS_SUFFIX}
EXTERNAL_IP: ${EXTERNAL_IP}
ALLOW_CREATE_ORGANIZATION: ${ALLOW_CREATE_ORGANIZATION}
RESERVED_ORGANIZATION_IDENTIFIERS: ${RESERVED_ORGANIZATION_IDENTIFIERS}
# 可选配置
SAAS_DOMAIN_TLD_LENGTH: ${SAAS_DOMAIN_TLD_LENGTH:-}
ASSET_CDN_HOST: ${ASSET_CDN_HOST:-}
RAILS_MAX_THREADS: ${RAILS_MAX_THREADS:-}
API_MAX_PAGE_SIZE: ${API_MAX_PAGE_SIZE:-}
PAGE_PREVIEW_SKIP_IP_VALIDATION: ${PAGE_PREVIEW_SKIP_IP_VALIDATION:-}
# 短信服务配置
TEXT_MESSAGE_ADAPTER: ${TEXT_MESSAGE_ADAPTER:-qiyewechat}
TEXT_MESSAGE_LOGIN_TEMPLATE: ${TEXT_MESSAGE_LOGIN_TEMPLATE:-'您的手机验证码为:%{code}'}
# UCloud 短信配置
TEXT_MESSAGE_UCLOUD_PUBLIC_KEY: ${TEXT_MESSAGE_UCLOUD_PUBLIC_KEY:-}
TEXT_MESSAGE_UCLOUD_PRIVATE_KEY: ${TEXT_MESSAGE_UCLOUD_PRIVATE_KEY:-}
TEXT_MESSAGE_UCLOUD_PROJECT_ID: ${TEXT_MESSAGE_UCLOUD_PROJECT_ID:-}
TEXT_MESSAGE_UCLOUD_SIG_CONTENT: ${TEXT_MESSAGE_UCLOUD_SIG_CONTENT:-}
TEXT_MESSAGE_UCLOUD_BAKLIB_LOGIN_TEMPLATE_ID: ${TEXT_MESSAGE_UCLOUD_BAKLIB_LOGIN_TEMPLATE_ID:-}
TEXT_MESSAGE_UCLOUD_BAKLIB_LOGIN_TEMPLATE: ${TEXT_MESSAGE_UCLOUD_BAKLIB_LOGIN_TEMPLATE:-}
# 阿里云短信配置
TEXT_MESSAGE_ALIYUN_ACCESS_KEY_ID: ${TEXT_MESSAGE_ALIYUN_ACCESS_KEY_ID:-}
TEXT_MESSAGE_ALIYUN_ACCESS_KEY_SECRET: ${TEXT_MESSAGE_ALIYUN_ACCESS_KEY_SECRET:-}
TEXT_MESSAGE_ALIYUN_SIG_CONTENT: ${TEXT_MESSAGE_ALIYUN_SIG_CONTENT:-}
TEXT_MESSAGE_ALIYUN_BAKLIB_LOGIN_TEMPLATE_ID: ${TEXT_MESSAGE_ALIYUN_BAKLIB_LOGIN_TEMPLATE_ID:-}
TEXT_MESSAGE_ALIYUN_BAKLIB_LOGIN_TEMPLATE: ${TEXT_MESSAGE_ALIYUN_BAKLIB_LOGIN_TEMPLATE:-}
# 企业微信短信配置
TEXT_MESSAGE_QIYEWECHAT_KEY: ${TEXT_MESSAGE_QIYEWECHAT_KEY:-}
# 存储服务配置
STORAGE_SAAS_DEFAULT_SERVICE: ${STORAGE_SAAS_DEFAULT_SERVICE:-local}
# 七牛云存储配置
STORAGE_QINIU_ACCESS_KEY: ${STORAGE_QINIU_ACCESS_KEY:-}
STORAGE_QINIU_SECRET_KEY: ${STORAGE_QINIU_SECRET_KEY:-}
STORAGE_QINIU_BUCKET: ${STORAGE_QINIU_BUCKET:-}
STORAGE_QINIU_PROTOCOL: ${STORAGE_QINIU_PROTOCOL:-}
STORAGE_QINIU_DOMAIN: ${STORAGE_QINIU_DOMAIN:-}
# 阿里云存储配置
STORAGE_ALIIYUN_ACCESS_KEY: ${STORAGE_ALIIYUN_ACCESS_KEY:-}
STORAGE_ALIIYUN_SECRET_KEY: ${STORAGE_ALIIYUN_SECRET_KEY:-}
STORAGE_ALIIYUN_BUCKET: ${STORAGE_ALIIYUN_BUCKET:-}
STORAGE_ALIIYUN_ENDPOINT: ${STORAGE_ALIIYUN_ENDPOINT:-}
STORAGE_ALIIYUN_CDN_HOST: ${STORAGE_ALIIYUN_CDN_HOST:-}
STORAGE_ALIIYUN_CDN_KEY: ${STORAGE_ALIIYUN_CDN_KEY:-}
STORAGE_ALIIYUN_PUBLIC: ${STORAGE_ALIIYUN_PUBLIC:-}
# AWS 存储配置
STORAGE_AWS_ACCESS_KEY: ${STORAGE_AWS_ACCESS_KEY:-}
STORAGE_AWS_SECRET_KEY: ${STORAGE_AWS_SECRET_KEY:-}
STORAGE_AWS_BUCKET: ${STORAGE_AWS_BUCKET:-}
STORAGE_AWS_REGION: ${STORAGE_AWS_REGION:-}
STORAGE_AWS_PUBLIC: ${STORAGE_AWS_PUBLIC:-}
STORAGE_AWS_EXPIRES_IN: ${STORAGE_AWS_EXPIRES_IN:-}
STORAGE_AWS_CDN_HOST: ${STORAGE_AWS_CDN_HOST:-}
STORAGE_AWS_CDN_PUBLIC_KEY_ID: ${STORAGE_AWS_CDN_PUBLIC_KEY_ID:-}
STORAGE_AWS_CDN_PRIVATE_KEY_BASE64: ${STORAGE_AWS_CDN_PRIVATE_KEY_BASE64:-}
# 邮件服务配置
MAILER_DELIVERY_METHOD: ${MAILER_DELIVERY_METHOD:-none}
MAILER_SEND_FROM: ${MAILER_SEND_FROM:-}
MAILER_SMTP_HOST: ${MAILER_SMTP_HOST:-}
MAILER_SMTP_PORT: ${MAILER_SMTP_PORT:-}
MAILER_SMTP_USERNAME: ${MAILER_SMTP_USERNAME:-}
MAILER_SMTP_PASSWORD: ${MAILER_SMTP_PASSWORD:-}
MAILER_SMTP_DOMAIN: ${MAILER_SMTP_DOMAIN:-}
MAILER_SMTP_AUTHENTICATION: ${MAILER_SMTP_AUTHENTICATION:-}
MAILER_SMTP_ENABLE_STARTTLS_AUTO: ${MAILER_SMTP_ENABLE_STARTTLS_AUTO:-}
MAILER_SMTP_SSL: ${MAILER_SMTP_SSL:-}
# Sentry 配置(可选)
SENTRY_DSN: ${SENTRY_DSN:-}
SENTRY_CURRENT_ENV: ${SENTRY_CURRENT_ENV:-}
GITHUB_PROXY_URL: ${GITHUB_PROXY_URL:-}
volumes:
- baklib-storage:/rails/storage
- baklib-theme-repositories:/rails/theme_repositories
- ${PWD}/traefik/etc:/etc/traefik/
# 使用 HOST_PROJECT_ROOT 以便在 CLI 容器内执行 run --rm web 时仍能挂载宿主机上的 product.pem(否则 PWD=/work 会导致宿主机无此路径、挂载成目录)
- ${HOST_PROJECT_ROOT:-${PWD}}/product.pem:/rails/config/product.pem:ro
x-etcd: &etcd-common
image: &etcd-image ${ETCD_IMAGE:-registry.devops.tanmer.com/library/etcd:v3.5.26}
restart: unless-stopped
networks:
- baklib
environment: &etcd-env
ETCD_NAME: etcd03
ETCD_ADVERTISE_CLIENT_URLS: http://etcd03:2379
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
ETCD_INITIAL_ADVERTISE_PEER_URLS: http://etcd03:2380
ETCD_INITIAL_CLUSTER_TOKEN: tanmer-com-token
ETCD_INITIAL_CLUSTER_STATE: new
ETCD_INITIAL_CLUSTER: etcd01=http://etcd01:2380,etcd02=http://etcd02:2380,etcd03=http://etcd03:2380
ETCD_ROOT_PASSWORD: ${ETCD_ROOT_PASSWORD}
labels:
traefik.enable: "false"
namespace: system
deploy:
resources:
limits:
cpus: "1"
memory: 512M
reservations:
cpus: "1"
memory: 512M
healthcheck:
test: ["CMD", "/usr/local/bin/etcdctl", "--endpoints=http://localhost:2379", "--user=root", "--password=${ETCD_ROOT_PASSWORD}", "endpoint", "health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 60s
services:
web:
<<: *baklib-common
container_name: baklib-web
command: bin/start-app
environment:
<<: *baklib-env
# Rails 基础配置(web 特有)
RAILS_SERVE_STATIC_FILES: y
# 服务名称
SAAS_DOCKER_SERVICE_NAME: "baklib-web@docker"
# 数据库配置(web 特有,覆盖 .env 中的值)
APP_DATABASE_POOL: ${APP_DATABASE_POOL:-6}
# Redis 配置(web 特有,覆盖 .env 中的值)
REDIS_URL: "redis://baklib-redis:6379/0?pool=${REDIS_POOL:-7}"
# 可选配置(web 特有)
WEB_CONCURRENCY: ${WEB_CONCURRENCY:-}
labels:
namespace: baklib
traefik.enable: "true"
traefik.http.routers.baklib-web.rule: "Host(`${MAIN_DOMAIN}`)"
traefik.http.routers.baklib-web.entryPoints: "http"
traefik.http.routers.baklib-web.middlewares: "baklib-default@file"
#traefik.http.routers.baklib-web.tls: "true"
traefik.http.routers.baklib-saas.rule: "HostRegexp(`[a-z0-9-]+${SAAS_DOMAIN_SUFFIX}`)"
traefik.http.routers.baklib-saas.entryPoints: "http"
traefik.http.routers.baklib-saas.middlewares: "baklib-default@file"
traefik.http.routers.baklib-api.rule: "Host(`open.${MAIN_DOMAIN}`)"
traefik.http.routers.baklib-api.entryPoints: "http"
traefik.http.routers.baklib-api.middlewares: "baklib-default@file"
traefik.http.routers.baklib-trial.rule: "HostRegexp(`[a-z0-9-]+${FREE_DOMAIN_SUFFIX}`)"
traefik.http.routers.baklib-trial.entryPoints: "http"
traefik.http.routers.baklib-trial.middlewares: "baklib-default@file"
deploy:
resources:
limits:
cpus: ${WEB_CONCURRENCY:-4}
memory: ${WEB_MEMORY:-4096M}
reservations:
cpus: ${WEB_CONCURRENCY:-4}
memory: ${WEB_MEMORY:-4096M}
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3000/_healthz || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 40s
job:
<<: *baklib-common
container_name: baklib-job
command: bin/jobs
environment:
<<: *baklib-env
# Redis 配置(job 特有,覆盖 .env 中的值)
REDIS_URL: "redis://baklib-redis:6379/0?pool=7"
# 数据库配置(job 特有,覆盖 .env 中的值)
APP_DATABASE_POOL: 10
# 任务配置(固定值)
SOLID_QUEUE_THREADS: 5
GIT_SYNC_WORKER_COUNT: 2
labels:
traefik.enable: "false"
namespace: baklib
deploy:
resources:
limits:
cpus: "4"
memory: 4096M
reservations:
cpus: "4"
memory: 4096M
db:
image: ${POSTGRES_IMAGE:-registry.devops.tanmer.com/library/postgres:17.7-trixie}
container_name: baklib-db
restart: unless-stopped
networks:
- baklib
volumes:
- baklib-postgres:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: baklib_production
labels:
traefik.enable: "false"
namespace: baklib
deploy:
resources:
limits:
cpus: "4"
memory: 8192M
reservations:
cpus: "4"
memory: 8192M
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: ${REDIS_IMAGE:-registry.devops.tanmer.com/library/redis:7.4.7}
container_name: baklib-redis
restart: unless-stopped
networks:
- baklib
volumes:
- baklib-redis:/data
labels:
traefik.enable: "false"
namespace: baklib
deploy:
resources:
limits:
cpus: "2"
memory: 4096M
reservations:
cpus: "2"
memory: 4096M
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
etcd01:
<<: *etcd-common
container_name: etcd01
environment:
<<: *etcd-env
ETCD_NAME: etcd01
ETCD_ADVERTISE_CLIENT_URLS: http://etcd01:2379
ETCD_INITIAL_ADVERTISE_PEER_URLS: http://etcd01:2380
etcd02:
<<: *etcd-common
container_name: etcd02
environment:
<<: *etcd-env
ETCD_NAME: etcd02
ETCD_ADVERTISE_CLIENT_URLS: http://etcd02:2379
ETCD_INITIAL_ADVERTISE_PEER_URLS: http://etcd02:2380
etcd03:
<<: *etcd-common
container_name: etcd03
environment:
<<: *etcd-env
ETCD_NAME: etcd03
ETCD_ADVERTISE_CLIENT_URLS: http://etcd03:2379
ETCD_INITIAL_ADVERTISE_PEER_URLS: http://etcd03:2380
etcd-init:
image: registry.devops.tanmer.com/library/docker:cli
container_name: etcd-init
restart: "on-failure"
depends_on:
etcd01:
condition: service_healthy
etcd02:
condition: service_healthy
etcd03:
condition: service_healthy
networks:
- baklib
environment:
ETCD_ENDPOINTS: http://etcd01:2379,http://etcd02:2379,http://etcd03:2379
ETCD_ROOT_PASSWORD: ${ETCD_ROOT_PASSWORD}
ETCD_IMAGE: *etcd-image
command: >
sh -c "
if ! command -v etcdctl >/dev/null 2>&1; then
TEMP_CONTAINER=etcd-temp-$$(date +%s);
docker create --name $$TEMP_CONTAINER $$ETCD_IMAGE >/dev/null 2>&1;
if docker cp $$TEMP_CONTAINER:/usr/local/bin/etcdctl /usr/local/bin/etcdctl 2>/dev/null; then
chmod +x /usr/local/bin/etcdctl;
elif docker cp $$TEMP_CONTAINER:/bin/etcdctl /usr/local/bin/etcdctl 2>/dev/null; then
chmod +x /usr/local/bin/etcdctl;
else
echo '❌ 无法从 etcd 镜像复制 etcdctl';
docker rm $$TEMP_CONTAINER >/dev/null 2>&1;
exit 1;
fi;
docker rm $$TEMP_CONTAINER >/dev/null 2>&1;
fi;
if etcdctl --endpoints=\"$$ETCD_ENDPOINTS\" auth status 2>/dev/null | grep -qiE '(Authentication Status: true|enabled)'; then
echo '✅ etcd 认证已启用,跳过初始化';
exit 0;
fi;
echo \"$$ETCD_ROOT_PASSWORD\" | etcdctl --endpoints=\"$$ETCD_ENDPOINTS\" user add root --interactive=false 2>/dev/null || true;
etcdctl --endpoints=\"$$ETCD_ENDPOINTS\" user grant-role root root 2>/dev/null || true;
etcdctl --endpoints=\"$$ETCD_ENDPOINTS\" auth enable;
echo '✅ etcd 认证已启用';
"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
traefik.enable: "false"
namespace: system
traefik:
image: ${TRAEFIK_IMAGE:-registry.devops.tanmer.com/library/traefik:v3.6.6}
container_name: traefik
restart: unless-stopped
depends_on:
etcd01:
condition: service_healthy
etcd02:
condition: service_healthy
etcd03:
condition: service_healthy
etcd-init:
condition: service_completed_successfully
networks:
- baklib
ports:
- "443:443"
- "80:80"
- "8081:8081" # Traefik Dashboard
volumes:
- ${PWD}/traefik/etc:/etc/traefik/:ro
- ${PWD}/logs/traefik:/var/log/
# 挂载 Docker socket 以便 Traefik 自动发现服务
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
# 阿里云 DNS 配置(用于 ACME DNS 挑战)
ALICLOUD_ACCESS_KEY: ${DNS_ALIYUN_ACCESS_KEY:-}
ALICLOUD_SECRET_KEY: ${DNS_ALIYUN_SECRET_KEY:-}
labels:
namespace: system
deploy:
resources:
limits:
cpus: "4"
memory: 2048M
reservations:
cpus: "4"
memory: 2048M
shell:
profiles:
- debug
image: registry.devops.tanmer.com/library/alpine:3.19
container_name: baklib-shell
command: sleep infinity
restart: unless-stopped
networks:
- baklib
volumes:
# 挂载项目目录以便调试
- ${PWD}:/workspace:rw
# 挂载存储目录(如果需要调试存储)
- baklib-storage:/rails/storage:ro
# 挂载主题仓库
- baklib-theme-repositories:/rails/theme_repositories:ro
environment:
# 数据库连接信息(用于 psql)
PGHOST: baklib-db
PGPORT: 5432
PGUSER: postgres
PGDATABASE: baklib_production
PGPASSWORD: ${POSTGRES_PASSWORD}
# Redis 连接信息
REDIS_HOST: baklib-redis
REDIS_PORT: 6379
# ETCD 连接信息
ETCD_ENDPOINTS: http://etcd01:2379,http://etcd02:2379,http://etcd03:2379
ETCD_USER: root
ETCD_PASSWORD: ${ETCD_ROOT_PASSWORD}
labels:
traefik.enable: "false"
namespace: baklib
networks:
baklib:
name: ${KAMAL_NETWORK_NAME:-baklib}
volumes:
baklib-postgres:
name: baklib-postgres
baklib-redis:
name: baklib-redis
etcd-data01:
name: etcd-data01
etcd-data02:
name: etcd-data02
etcd-data03:
name: etcd-data03
baklib-storage:
name: baklib-storage
baklib-theme-repositories:
name: baklib-theme-repositories