-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
274 lines (264 loc) · 11.4 KB
/
Copy pathdocker-compose.yml
File metadata and controls
274 lines (264 loc) · 11.4 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
# ISOLATED_LIVE one-click stack (productization Phase 1-C).
#
# Contract (enforced by tools/demo_console/one_click_startup.py and
# tests/isolated_live/test_one_click_containerization.py):
# - postgres uses the digest-pinned pgvector image; every other service
# is BUILT locally (pull_policy: never) — no unpinned remote tags.
# - all services share the internal-only bridge network "isolated".
# - ONLY demo-console publishes a port, bound to 127.0.0.1.
# postgres / controller / policy-gateway / preflight publish NOTHING.
# - dependency order: postgres (healthy) -> policy-gateway -> controller
# -> demo-console -> preflight.
# - preflight reaches PostgreSQL INSIDE the internal network
# (host=postgres) — no twin container, no host-process substitute.
# - no persistent volumes; the stack is one-shot.
#
# NOTE (dependency installs): the three Python service images and the
# preflight image require pip install at BUILD time. That install is NOT
# performed in Phase 1-C (code-only); it needs a separate, explicit
# authorization round. The Dockerfiles below are the declared build recipe.
name: mergepilot-isolated
services:
postgres:
image: pgvector/pgvector@sha256:a36250871de0833b8757561c72f2477ef1ddd1101afa4e617fb552e0de514c6b
pull_policy: never
env_file: postgres.env # created by the orchestrator (SecretFile)
environment:
PGDATA: /tmp/pgdata
networks:
- isolated
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mergepilot -d mergepilot_audit"]
interval: 5s
timeout: 3s
retries: 10
# ports: deliberately ABSENT — postgres is never published.
policy-gateway:
build:
# Retry v3: build context unified with the builder/orchestrator
# contract (root wrapper Dockerfile) — five-layer consistency; the
# canonical thin Dockerfile in tools/policy-gateway/ stays for
# standalone subdir builds.
context: .
dockerfile: Dockerfile.policy-gateway
pull_policy: never
depends_on:
postgres:
condition: service_healthy
networks:
- isolated
# ports: deliberately ABSENT.
# Retry v3 Fix 3: UPSTREAM_URL (non-secret) points at the in-container
# zero-tool stub so the gateway's real lifespan completes inside the
# isolated network (no github-mcp exists here by design). The
# healthcheck probes the gateway's own listen port: uvicorn binds only
# AFTER the upstream session is up, so healthy == fully started — an
# exited or still-retrying gateway never reports healthy (no standby).
environment:
UPSTREAM_URL: "http://127.0.0.1:8084/sse"
healthcheck:
test: ["CMD", "python", "/app/healthcheck.py"]
interval: 5s
timeout: 3s
retries: 10
controller:
build:
# Retry v3: build context unified with the builder/orchestrator
# contract (root wrapper Dockerfile) — five-layer consistency.
context: .
dockerfile: Dockerfile.controller
pull_policy: never
depends_on:
postgres:
condition: service_healthy
policy-gateway:
condition: service_healthy
networks:
- isolated
# ports: deliberately ABSENT.
# Retry v3 Fix 3: the secrets PG_PASS/ADMIN_PW (controller.py refuses
# to start without them) travel via the orchestrator-created secret
# env-file — never compose literals. The non-secret DB coordinates are
# explicit: the code default PG_HOST='audit-pg' does not exist in this
# stack. Healthcheck = TCP to the configured PostgreSQL from inside
# the container (the controller has no listen port); an exited
# controller never reports healthy.
env_file: controller.env # created by the orchestrator (secrets)
environment:
PG_HOST: postgres
PG_PORT: "5432"
PG_DATABASE: mergepilot_audit
PG_USER: mergepilot
healthcheck:
test: ["CMD", "python", "/app/healthcheck.py"]
interval: 5s
timeout: 3s
retries: 10
# gh-webhook (M8-GH-3): the GitHub webhook receiver + Checks reporter
# image. Publishes its receiver on the HOST loopback ONLY (8090) via the
# publication bridge (a non-internal network — Docker silently drops
# port publishing on internal networks; see the 1-G design notes above).
# Secrets (GITHUB_INGRESS_DSN with INSERT-only role + connect_timeout,
# GITHUB_WEBHOOK_SECRET) travel via the orchestrator-created secret
# env-file — never compose literals. Hardened runtime (M8-GH-2):
# non-root UID/GID 9090, digest-pinned base, read-only rootfs,
# no-new-privileges, all capabilities dropped, no Docker socket.
gh-webhook:
build:
context: .
dockerfile: Dockerfile.gh-webhook
pull_policy: never
depends_on:
postgres:
condition: service_healthy
networks:
- isolated
- console-publish
ports:
- "0.0.0.0:8090:8090" # distro-side backend of the Windows loopback publication edge (forwarder binds 127.0.0.1 on Windows).
env_file: gh_webhook.env # created by the orchestrator (secrets)
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs:
- /tmp
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8090/healthz',timeout=2)"]
interval: 5s
timeout: 3s
retries: 10
restart: "no"
demo-console:
build:
context: .
dockerfile: Dockerfile.demo-console
pull_policy: never
depends_on:
# Retry v3: healthy, not merely started — the full stack requires the
# controller (and transitively the policy-gateway) to pass their real
# healthchecks before the console starts.
controller:
condition: service_healthy
# M8-GH-3: the console also waits for the webhook receiver (transitively
# postgres-healthy) so a broken ingress surface blocks the stack loudly.
gh-webhook:
condition: service_healthy
networks:
- isolated
# Review-gap Fix 3: REAL demo-console readiness — loopback HTTP probe of
# /api/live/status (200 + JSON + POSTGRES_ISOLATED + source_read_only +
# startup snapshot). preflight waits for THIS, not for 'started'.
healthcheck:
test: ["CMD", "python", "/app/console_healthcheck.py"]
interval: 5s
timeout: 3s
retries: 10
environment:
# Entrypoint contract: ISOLATED_LIVE mode, postgres source, canonical
# reader role, and a CALLER-PROVIDED run_id. REPLAY is refused (no
# fallback). The run_id is NEVER hardcoded here; it must be injected
# via the environment when the stack is started.
MERGEPILOT_MODE: isolated_live
MERGEPILOT_SOURCE_KIND: postgres
MERGEPILOT_RUN_ID: ${MERGEPILOT_RUN_ID:?MERGEPILOT_RUN_ID is required}
MERGEPILOT_EXPECTED_ROLE: mergepilot_reader
# Bind context (retry v2 Fix 1): the CONTAINER listens on 0.0.0.0
# (required for Docker bridge routing). The HOST-side publish remains
# 127.0.0.1-only (see ports below). The context is EXPLICIT — it is
# never inferred from the host value; serve.py/preflight validate
# against it (host mode stays strictly loopback).
MERGEPILOT_BIND_CONTEXT: container
MERGEPILOT_HOST: "0.0.0.0"
MERGEPILOT_PORT: "8600"
# PostgreSQL expected identity (retry v2 Fix 2): all five are REQUIRED
# by the entrypoint and serve.py config preflight — no defaults, no
# inference. SERVER_ADDRESSES is the postgres container's MEASURED
# bridge IP (caller-injected after postgres is healthy; hardcoding is
# forbidden). The other four are canonical constants of this stack.
MERGEPILOT_PG_EXPECTED_DATABASE: mergepilot_audit
MERGEPILOT_PG_ENVIRONMENT_ID: mergepilot-test-ephemeral
MERGEPILOT_PG_EXPECTED_SERVER_ADDRESSES: ${MERGEPILOT_PG_EXPECTED_SERVER_ADDRESSES:?MERGEPILOT_PG_EXPECTED_SERVER_ADDRESSES is required (measured bridge IP)}
MERGEPILOT_PG_EXPECTED_SERVER_PORT: "5432"
MERGEPILOT_PG_EXPECTED_APPLICATION_NAME: mergepilot_isolated_live_reader
# 1-G stabilization sweep: the reader DSN (serve.py reads
# MERGEPILOT_PG_DSN) travels via this orchestrator-created secret
# env-file (ReaderDsnSecretFile) — never compose literals. Without
# it the console cannot open its read-only PostgreSQL source.
env_file: demo_console.env
# 1-G network design: Docker silently DROPS port publishing on
# internal networks (verified: Ports={"8600/tcp":null}), and the
# DSN-bearing console must never gain an external default route
# (dual-network egress verified as SECRET_CONTAINER_EGRESS_FAIL).
# The console therefore stays internal-only and UNPUBLISHED; the
# loopback publication moved to the secretless console-edge below.
# ports: deliberately ABSENT.
# console-edge: SECRETLESS loopback publication plumbing (1-G network
# design, experiment C). NOT a fifth application service; does NOT
# constitute application integration. It is the ONLY service on the
# publication bridge and the ONLY published port in the stack: the
# fixed upstream is http://demo-console:8600 (a code constant, never
# configurable), methods are GET-only on a strict path whitelist, and
# it refuses CONNECT/absolute-URI/arbitrary-Host — it cannot act as an
# open proxy. It holds no DSN/password/token/database coordinates
# (no env_file, no environment secrets) by design.
console-edge:
build:
context: .
dockerfile: Dockerfile.console-edge
pull_policy: never
depends_on:
demo-console:
condition: service_healthy
networks:
- isolated
- console-publish
ports:
- "0.0.0.0:8600:8600" # distro-side backend of the Windows loopback publication edge (forwarder binds 127.0.0.1 on Windows).
healthcheck:
test: ["CMD", "python", "/app/console_edge_healthcheck.py"]
interval: 5s
timeout: 3s
retries: 10
restart: "no"
preflight:
build:
context: .
dockerfile: Dockerfile.preflight
pull_policy: never
depends_on:
# Review-gap Fix 3: the gate matrix runs only after the demo-console
# is genuinely READY (the matrix's own fail-closed http_endpoint
# gate still re-verifies the endpoint in-network).
demo-console:
condition: service_healthy
# 1-G network design: full-stack readiness requires the publication
# edge healthy too (its healthcheck proves the fixed upstream
# chain answers 200/POSTGRES_ISOLATED/read-only).
console-edge:
condition: service_healthy
networks:
- isolated
# 1-G stabilization sweep: preflight's REAL DB gates connect with the
# reader DSN (preflight_entrypoint.py exits without MERGEPILOT_PG_DSN)
# — same orchestrator-created secret env-file as demo-console.
env_file: demo_console.env
environment:
# preflight reaches PostgreSQL INSIDE the internal network.
MERGEPILOT_PG_HOST: postgres
MERGEPILOT_PG_PORT: "5432"
MERGEPILOT_DEMO_CONSOLE_URL: http://demo-console:8600
restart: "no"
# ports: deliberately ABSENT.
networks:
isolated:
driver: bridge
internal: true
# 1-G network design: publication bridge — a NORMAL (non-internal)
# bridge so Docker actually wires the loopback port publish. Only the
# secretless console-edge attaches here; every secret-bearing service
# stays on the internal-only network above.
console-publish:
driver: bridge
volumes: {}