-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
194 lines (186 loc) · 7.41 KB
/
Copy pathdocker-compose.yml
File metadata and controls
194 lines (186 loc) · 7.41 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
# Local dev stack (FR-009). Images are pinned by sha256 digest for reproducible,
# air-gap-friendly installs (ops-envelope §9). See docs/docker-offline.md for the
# offline save/load procedure.
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- '3000:3000'
volumes:
# Source volume mount → hot reload in dev. node_modules stays in-image.
- ./src:/app/src
# Write-ahead raw day-files. Deliberately a SEPARATE volume from the Redis
# AOF (redisdata) so a raw-file fsync cannot starve the AOF fsync — they
# must not contend on one disk (T-00.5, ops-envelope §8). The raw-file
# writer itself is later scope (008-cold-storage / Unit 4); the volume is
# provisioned now so the durability layout is correct from the start.
- rawdata:/app/raw
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
NODE_ENV: development
PORT: '3000'
REPORTING_OFFSET: '0'
DB_HOST: postgres
DB_PORT: '5432'
DB_USER: analytics
DB_PASSWORD: analytics
DB_NAME: analytics
REDIS_HOST: redis
REDIS_PORT: '6379'
MINIO_ENDPOINT: minio
MINIO_PORT: '9000'
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
MINIO_BUCKET: analytics-raw
# Raw day-file directory — the mount point of the dedicated `rawdata`
# volume (kept off the Redis AOF disk, T-00.5).
RAW_FILE_DIR: /app/raw
# Backpressure / rate-limit envelope (ops-envelope §3–§5). The byte cap
# MUST match the redis `--maxmemory` above so the door brakes before OOM.
REDIS_MAXMEMORY_BYTES: '3984588800' # 3800 MiB
MEMORY_WATERMARK_FRACTION: '0.8'
QUEUE_DEPTH_WATERMARK: '200000'
RETRY_AFTER_SECONDS: '5'
INGEST_EVENTS_PER_SEC_CAP: '200'
INGEST_RATE_BURST_EVENTS: '5000'
# Security posture (FR-029, ops-envelope §9). REQUIRE_TLS is FALSE in this
# dev compose (no proxy in the default `up`); enable it — and the caddy
# profile below — for a TLS-terminated deploy. SECRET_MASTER_KEY MUST be set
# from a Docker secret / env file in production (a DB dump then yields only
# ciphertext); the dev default is empty (envelope-encryption disabled).
REQUIRE_TLS: 'false'
SECRET_MASTER_KEY: '${SECRET_MASTER_KEY:-}'
ALLOWED_ORIGINS: '${ALLOWED_ORIGINS:-}'
postgres:
# postgres:16-alpine
image: postgres@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
# Host port mapping for local dev + host-run integration tests (the app
# reaches Postgres over the compose network regardless). Override the host
# side via ${DB_PORT:-5432} if 5432 is taken on the host.
ports:
- '${DB_PORT:-5432}:5432'
environment:
POSTGRES_USER: analytics
POSTGRES_PASSWORD: analytics
POSTGRES_DB: analytics
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U analytics']
interval: 5s
timeout: 5s
retries: 10
redis:
# redis:7-alpine
image: redis@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
# maxmemory byte cap (ops-envelope §3 ≈ 3.8 GB) is now SET, consistent with
# the door watermark (REDIS_MAXMEMORY_BYTES / MEMORY_WATERMARK_FRACTION on the
# app): the fast-ack door 503s at 80 % (~3 GB) BEFORE `noeviction` OOM, so the
# counting path stays alive while the door sheds (T-00.4 / T-00.66). A modest
# dev VPS can lower this; keep the app's REDIS_MAXMEMORY_BYTES in step with it.
# `no-appendfsync-on-rewrite yes` avoids the AOF fsync stalling during rewrite
# (ops-envelope §8, AOF fsync-stall mitigation; raw dir is a separate volume).
command: >-
redis-server
--appendonly yes
--appendfsync everysec
--maxmemory-policy noeviction
--maxmemory 3800mb
--no-appendfsync-on-rewrite yes
# Host port mapping for local dev + host-run integration tests (override via
# ${REDIS_PORT:-6379} if 6379 is taken on the host).
ports:
- '${REDIS_PORT:-6379}:6379'
volumes:
- redisdata:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 5s
retries: 10
minio:
# minio/minio:latest
image: minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
command: server /data --console-address ":9001"
ports:
- '9000:9000'
- '9001:9001'
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
volumes:
- miniodata:/data
# Reverse-proxy / TLS terminator (T-00.7 / T-00.85, ops-envelope §9). Behind
# the `proxy` profile so the DEFAULT `docker compose up` stays cert-free and
# single-command on a dev box; a TLS deploy runs
# `docker compose --profile proxy up` (and sets the app's REQUIRE_TLS=true).
# Caddy auto-provisions certs; see docs/reverse-proxy.md for the DNS-01 / long-
# lived-cert / cert-expiry-alert guidance for the sanctions/shutdown case.
caddy:
profiles: ['proxy']
# caddy:2-alpine
image: caddy@sha256:98eb57d882ccd5213d1688764db10c1ca2c58a1ca3a6717a3411ad798f7a423a
depends_on:
- app
ports:
- '80:80'
- '443:443'
volumes:
- ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddydata:/data
- caddyconfig:/config
# PITR backup sidecar (T-00.80, FR-028). Behind the `backup` profile so the
# default dev `up` is unchanged; a production deploy runs
# `docker compose --profile backup up -d` to get scheduled pg_basebackup + WAL
# archiving to MinIO. Backups are ENCRYPTED with SECRET_MASTER_KEY (held
# OUTSIDE Postgres, FR-029). See scripts/pitr-*.sh + docs/backup-restore.md.
pitr:
profiles: ['backup']
# postgres:16-alpine — reuse the pinned pg image (has pg_basebackup + psql).
image: postgres@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
depends_on:
postgres:
condition: service_healthy
minio:
condition: service_started
entrypoint: ['/bin/sh', '/scripts/pitr-backup.sh']
environment:
PGHOST: postgres
PGPORT: '5432'
PGUSER: analytics
PGPASSWORD: analytics
PGDATABASE: analytics
MINIO_ENDPOINT: minio
MINIO_PORT: '9000'
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
MINIO_BUCKET: analytics-backup
BACKUP_INTERVAL_SECONDS: '3600'
# Backup-encryption key — held outside Postgres (FR-029). Empty ⇒ the
# script refuses to run (fails loud rather than writing plaintext backups).
SECRET_MASTER_KEY: '${SECRET_MASTER_KEY:-}'
volumes:
- ./scripts:/scripts:ro
- backupstage:/stage
# DEPLOYMENT REQUIREMENT — slewing NTP (T-00.6, Foundation §4.2). Containers
# inherit the HOST clock; a stepped clock breaks skew-correction + the
# monotonicity alarm. The single-VPS posture is a HOST requirement (not a
# container): run `chronyd` on the host with a bounded `maxslewrate` and NEVER
# `ntpd -g` / step-on-start. See docs/deployment-requirements.md for the exact
# chrony.conf stanza. (Documented here rather than shipped as a privileged
# time-daemon container, which would need host-clock capabilities.)
volumes:
pgdata:
redisdata:
# Raw write-ahead day-files — separate spindle from redisdata (T-00.5).
rawdata:
miniodata:
caddydata:
caddyconfig:
backupstage: