-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
165 lines (162 loc) · 8.34 KB
/
Copy pathdocker-compose.yml
File metadata and controls
165 lines (162 loc) · 8.34 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
# ── Shared base: everything staging and production have in common ────────────
# This file alone is NOT runnable — it deliberately carries no RETINA_ENV, no
# hostnames and no CORS origins, so booting it bare fails fast in start.sh
# rather than silently serving an unconfigured stack. Always combine it with
# exactly one environment overlay:
#
# docker compose -f docker-compose.yml -f docker-compose.staging.yml up -d
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
#
# In practice neither the deploy workflow nor a human types those flags: each
# host has a gitignored ./.env holding COMPOSE_FILE (see deploy/env.*.example),
# so plain `docker compose up -d --build` / `logs` / `ps` resolve to the right
# pair on that host and every command is identical across environments.
#
# The rule for this file: if a setting is not *inherently* per-environment
# (hostname, fleet size, droplet capacity), it belongs here — not in an
# overlay. deploy/check-env-parity.sh enforces that in CI.
services:
server:
build:
context: .
args:
# The CARTO basemap key, public by design — it ends up in the bundle,
# because the tile requests are the browser's. Resolved from ./.env,
# which every deploy rewrites from deploy/env.<env>.example and then
# tops up from the host's own /root/.secrets/carto.env. A host without
# that file interpolates to empty, builds unkeyed URLs, and so gets
# CARTO's watermarked tiles.
# Declared in the base file so every environment builds the same way.
VITE_CARTO_API_KEY: ${CARTO_API_KEY:-}
# Pin an explicit image name so it is stable and independent of the Compose
# project (directory) name. deploy/pre-deploy.sh + deploy/rollback.sh key the
# rollback on `retina-server:latest`; without this, Compose builds an
# auto-named `<dir>-server` image and the rollback tag is never the one
# Compose actually runs, so `rollback.sh` silently restarts the bad build.
# In the base so staging is rollback-capable on the same terms as prod.
image: retina-server:latest
ports:
- "80:80"
- "443:443"
- "3012:3012"
volumes:
- /etc/ssl/cloudflare:/etc/ssl/cloudflare:ro
# users.db and the Parquet detection archive. Without these the data lives
# in the container's writable layer and every `up -d --build` destroys it —
# which is exactly how staging's Data Explorer (dash /data) came to be
# permanently empty: the archive never survived to its first hourly flush.
- backend-data:/app/backend/data
- backend-coverage-data:/app/backend/coverage_data
env_file:
- backend/.env
environment:
# Bind uvicorn on 0.0.0.0 so the fleet container (below) can POST live
# ADS-B / ground-truth to the API over the compose network. Port 8000 is
# NOT in `ports:`, so it stays off-host — only reachable inside compose.
# Without this uvicorn binds 127.0.0.1 and the fleet's pushes are refused.
# (Detections themselves flow over the published TCP port 3012.)
- UVICORN_HOST=0.0.0.0
- RADAR_TCP_PORT=3012
# Frame pipeline sizing. Set here rather than left to each host's
# backend/.env so the two environments cannot silently drift apart on the
# knob that most directly shapes throughput behaviour.
- FRAME_WORKERS=6
- FRAME_QUEUE_SIZE=10000
# Simulation scene mix, read at boot by core/state.py. Lives here (not in
# an overlay) because both environments are meant to run the same mix —
# fleet *scale* is what legitimately differs, and that is already
# per-overlay via FLEET_MIN/MAX_AIRCRAFT.
#
# These exist so a rebuild boots into the intended scene: before them the
# dict was hardcoded-defaults-only, so `up -d --build` silently reverted
# whatever the Physics tab had applied — and because _updated_at is
# stamped at import, the fleet's poll pushed those defaults into the
# running world within 5 s. A runtime PUT still outranks these until the
# values below themselves change — see
# services/state_snapshot.py:_restore_simulation_config.
- SIM_FRAC_ANOMALOUS=0.0
- SIM_FRAC_DRONE=0.0
- SIM_FRAC_DARK=0.15
# The Cloudflare Zero Trust team whose assertions the origin will believe.
# One team for the whole org, so it belongs here rather than in an overlay:
# a per-environment copy could drift to another team and the audience check
# alone would not catch it. The per-application audience does differ, and
# is CF_ACCESS_AUD in each overlay. Not a secret — it appears in the login
# URL of every Access redirect.
- CF_ACCESS_TEAM_DOMAIN=offworldlab.cloudflareaccess.com
# bash, not sh: start.sh's supervisor uses `wait -n`, a bash builtin that
# dash rejects ("Illegal option -n"), which crash-loops the server. The
# script's shebang already says bash; naming it here keeps that true even if
# the Dockerfile CMD changes.
command: ["/bin/bash", "/app/deploy/start.sh"]
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')"]
interval: 30s
timeout: 5s
retries: 3
# Grace window for a cold boot (state/coverage warmup): failing checks
# here don't count toward `retries` or flip the container `unhealthy`.
# This gates the fleet's `depends_on: service_healthy` on a plain
# `docker compose up` (local/manual bring-up). The CI deploy starts the
# fleet with --no-deps and reboots ignore depends_on, so this doesn't
# affect those paths — it just prevents a slow-but-fine boot from being
# branded `unhealthy` and aborting a plain `up`. Sits above start.sh's
# fast-failure supervisor threshold (60s), so a slow-but-running boot is
# never counted as a crash.
start_period: 90s
# ── Fleet simulator: synthetic radar nodes + ground-truth aircraft ─────────
# This is what makes testmap.retina.fm differ from map.retina.fm. It was
# previously started out-of-band by deploy/restart-fleet-prod.sh via a
# transient `systemd-run` unit that did NOT survive reboots — that is how the
# fleet silently vanished after the RAM-expansion reboot. As a Compose service
# it now restarts exactly like the app: on boot (docker is enabled) and via
# the CI `docker compose up -d --build` step.
#
# Fleet *scale* is the one thing staging is meant to differ on, so every
# FLEET_* sizing value lives in the overlays.
fleet:
build:
context: .
dockerfile: Dockerfile.fleet
# Pin an explicit image name (mirrors the app's `image: retina-server:latest`)
# so pre-deploy/rollback can save + retag a stable fleet image. Without this
# Compose auto-names it `<dir>-fleet` and a rollback would leave the fleet on
# the bad deploy's build while only the app reverted.
image: retina-server-fleet:latest
depends_on:
server:
condition: service_healthy
# RADAR_API_KEY comes from backend/.env (the same file the app loads). The
# backend enforces X-API-Key on its ingest whenever it has the key set
# (routes/radar.py), so the fleet MUST present the matching key or its
# ground-truth / ADS-B pushes 401. This replaces the `-E RADAR_API_KEY` that
# the old restart-fleet-prod.sh passed to the transient unit. In the base
# because staging previously omitted it and so never exercised the
# authenticated ingest path production runs. FLEET_* in the overlays
# override anything in .env (Compose: `environment` wins over `env_file`).
env_file:
- backend/.env
environment:
- FLEET_HOST=server
- FLEET_PORT=3012
- FLEET_VALIDATE=false
# HTTP base URL for live ADS-B / ground-truth pushes (the moving "truth"
# tracks). Must point at the server over the compose network, not the
# fleet's own localhost. Reaches uvicorn directly (UVICORN_HOST=0.0.0.0).
- FLEET_VALIDATION_URL=http://server:8000
- FLEET_SEED=42
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
volumes:
backend-data:
backend-coverage-data: