-
Notifications
You must be signed in to change notification settings - Fork 309
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
299 lines (285 loc) · 11.5 KB
/
Copy pathdocker-compose.yml
File metadata and controls
299 lines (285 loc) · 11.5 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
version: '3.8'
# ─────────────────────────────────────────────────────────────────────────────
# Fluxora Backend – Docker Compose
#
# Default profile : postgres + redis + app-blue + app-green.
# Both application slots share the same Postgres database and
# the same Redis instance, enabling blue/green zero-downtime
# deployments with fully shared state.
#
# chaos profile : adds Toxiproxy sitting in front of a dedicated Postgres
# and Redis so automated chaos tests can inject network faults
# (latency, bandwidth throttle, connection reset) via the
# Toxiproxy HTTP management API on port 8474.
#
# Blue/Green topology (default profile):
#
# load balancer
# │
# ├─► app-blue :3000 ──┐
# └─► app-green :3001 ──┤──► postgres :5432
# └──► redis :6379
#
# Each slot emits `X-Fluxora-Deployment-Slot: blue|green` on every response
# so a front-side load balancer or the e2e suite can verify which slot
# answered a request during a cutover. See docs/deployment.md for the full
# cutover and rollback procedure.
#
# Chaos topology (--profile chaos):
#
# backend / tests
# │
# ├─► toxi-postgres :5433 ──► chaos-postgres :5432
# └─► toxi-redis :6380 ──► chaos-redis :6379
# │
# └─► toxiproxy mgmt API :8474 (configure toxics at runtime)
#
# Security notes:
# - Toxiproxy management port (8474) is bound only to 127.0.0.1 in
# production-like deployments; the compose file exposes it only on
# localhost so CI runners do not expose it externally.
# - All service passwords are test-only values that never appear in
# application code or secrets stores.
# - The chaos profile containers are isolated in the "chaos" network and
# are never started unless "--profile chaos" is explicitly passed.
# - The shared Redis password (fluxora_redis_password) is a local-dev-only
# value; in production replace it with a secret manager reference.
# ─────────────────────────────────────────────────────────────────────────────
services:
# ── Default services (always started) ──────────────────────────────────────
postgres:
image: postgres:15-alpine
container_name: indexer-postgres
environment:
POSTGRES_DB: indexer_db
POSTGRES_USER: indexer_user
POSTGRES_PASSWORD: indexer_password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U indexer_user -d indexer_db"]
interval: 10s
timeout: 5s
retries: 5
# Shared Redis instance used by both app-blue and app-green.
#
# Both slots use this single Redis for:
# - Idempotency key storage (POST /api/streams cross-instance dedup)
# - Stream-event dedup cache (HybridDedupCache primary tier)
# - Webhook circuit-breaker state (shared across slots)
# - Rate-limiting sliding-window counters (consistent limits during cutover)
# - Admin-state distributed locks
# - Indexer leader-election lease (only one slot runs replay at a time)
#
# Security: requirepass is set so connections without the password are
# rejected. The password is a local-dev-only value; rotate it for any
# non-local environment.
redis:
image: redis:7-alpine
container_name: fluxora-redis
# --save "" disables RDB snapshots for dev; enable persistence in prod.
command: >
redis-server
--requirepass fluxora_redis_password
--loglevel warning
--save ""
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "-a", "fluxora_redis_password", "ping"]
interval: 5s
timeout: 3s
retries: 10
# ── Blue slot ───────────────────────────────────────────────────────────────
# Active production slot. Receives live traffic via port 3000.
# Emits X-Fluxora-Deployment-Slot: blue on every response.
#
# Migration safety: both slots share the same DATABASE_URL. node-pg-migrate
# holds a pg_advisory_lock during migrations so concurrent runs are safe and
# idempotent. Run `docker-compose exec app-blue pnpm run migrate` before
# promoting the green slot.
app-blue:
build:
context: .
dockerfile: Dockerfile
container_name: fluxora-blue
environment:
DATABASE_URL: postgresql://indexer_user:indexer_password@postgres:5432/indexer_db
# Shared Redis — both slots must point at the same instance.
REDIS_URL: redis://:fluxora_redis_password@redis:6379
REDIS_ENABLED: "true"
REPLAY_BATCH_SIZE: 1000
PORT: 3000
# Identifies this slot in X-Fluxora-Deployment-Slot response header.
DEPLOYMENT_SLOT: blue
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
# ── Green slot ──────────────────────────────────────────────────────────────
# Idle / staging slot. New releases are deployed here first.
# Emits X-Fluxora-Deployment-Slot: green on every response.
#
# To promote green to active, update the upstream in your load balancer from
# port 3000 → 3001. See docs/deployment.md for the full cutover procedure.
app-green:
build:
context: .
dockerfile: Dockerfile
container_name: fluxora-green
environment:
DATABASE_URL: postgresql://indexer_user:indexer_password@postgres:5432/indexer_db
# Shared Redis — must be identical to app-blue so state is consistent.
REDIS_URL: redis://:fluxora_redis_password@redis:6379
REDIS_ENABLED: "true"
REPLAY_BATCH_SIZE: 1000
PORT: 3000
# Identifies this slot in X-Fluxora-Deployment-Slot response header.
DEPLOYMENT_SLOT: green
ports:
- "3001:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
# ── Chaos profile services ──────────────────────────────────────────────────
# Start with: docker compose --profile chaos up -d
# Dedicated Postgres instance used only during chaos tests so the default
# indexer-postgres is not disrupted.
chaos-postgres:
profiles: [chaos]
image: postgres:15-alpine
container_name: chaos-postgres
environment:
POSTGRES_DB: chaos_db
POSTGRES_USER: chaos_user
# test-only password; never used in production
POSTGRES_PASSWORD: chaos_password
ports:
- "5434:5432"
networks:
- chaos
healthcheck:
test: ["CMD-SHELL", "pg_isready -U chaos_user -d chaos_db"]
interval: 5s
timeout: 3s
retries: 10
# Redis instance used only during chaos tests.
chaos-redis:
profiles: [chaos]
image: redis:7-alpine
container_name: chaos-redis
command: redis-server --requirepass chaos_password --loglevel warning
ports:
- "6381:6379"
networks:
- chaos
healthcheck:
test: ["CMD", "redis-cli", "-a", "chaos_password", "ping"]
interval: 5s
timeout: 3s
retries: 10
# Toxiproxy sits between the backend / tests and both Postgres and Redis.
# The management API (8474) is used by toxiproxy.chaos.test.ts to configure
# and tear down toxics programmatically.
#
# Proxy configuration (provisioned at startup via config volume):
# "pg_proxy" listens on :5433 → forwards to chaos-postgres:5432
# "redis_proxy" listens on :6380 → forwards to chaos-redis:6379
toxiproxy:
profiles: [chaos]
image: ghcr.io/shopify/toxiproxy:2.9.0
container_name: chaos-toxiproxy
# Seed proxies at startup so tests can immediately address the proxied ports.
# The JSON file is mounted read-only; toxics are added at runtime via the API.
command: [
"-host", "0.0.0.0",
"-config", "/etc/toxiproxy/config.json",
]
volumes:
- ./toxiproxy.config.json:/etc/toxiproxy/config.json:ro
ports:
# Toxiproxy management API — bound to localhost only so it is not
# reachable from outside the CI runner or developer machine.
- "127.0.0.1:8474:8474"
# Proxied Postgres endpoint (tests connect here instead of :5434)
- "127.0.0.1:5433:5433"
# Proxied Redis endpoint (tests connect here instead of :6381)
- "127.0.0.1:6380:6380"
networks:
- chaos
depends_on:
chaos-postgres:
condition: service_healthy
chaos-redis:
condition: service_healthy
# ── pgbouncer profile services ──────────────────────────────────────────────
# Start with: docker compose --profile pgbouncer up -d
#
# This profile provisions a minimal PgBouncer in transaction-pooling mode
# in front of the default Postgres instance for local verification of the
# POOL_MODE=transaction guard.
#
# Topology:
#
# backend / tests
# │
# └─► pgbouncer :6432 (transaction pooler) ──► postgres :5432
#
# Connect using:
# DATABASE_URL=postgresql://indexer_user:indexer_password@localhost:6432/indexer_db
# POOL_MODE=transaction
#
# Security notes:
# - The PgBouncer admin console password is a test-only value.
# - The :6432 port is only bound to localhost; production deployments
# should configure TLS and network-level access controls.
# - admin_users and stats_users are restricted to 'pgbouncer_admin'.
pgbouncer:
profiles: [pgbouncer]
image: bitnami/pgbouncer:1.22.0
container_name: indexer-pgbouncer
environment:
# Target Postgres instance
POSTGRESQL_HOST: postgres
POSTGRESQL_PORT: 5432
POSTGRESQL_DATABASE: indexer_db
POSTGRESQL_USERNAME: indexer_user
POSTGRESQL_PASSWORD: indexer_password
# PgBouncer pool settings
PGBOUNCER_DATABASE: indexer_db
PGBOUNCER_POOL_MODE: transaction
PGBOUNCER_MAX_CLIENT_CONN: 100
PGBOUNCER_DEFAULT_POOL_SIZE: 20
# Auth settings
PGBOUNCER_AUTH_TYPE: md5
PGBOUNCER_ADMIN_USERS: pgbouncer_admin
PGBOUNCER_STATS_USERS: pgbouncer_admin
# test-only password; never used in production
PGBOUNCER_AUTH_USER: indexer_user
PGBOUNCER_AUTH_QUERY: "SELECT p_user, p_password FROM pgbouncer.get_auth($1)"
ports:
# Expose PgBouncer on localhost only — not accessible outside CI runner
- "127.0.0.1:6432:5432"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U indexer_user -d indexer_db || exit 1"]
interval: 10s
timeout: 5s
retries: 5
networks:
chaos:
driver: bridge
volumes:
postgres_data: