Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
137 lines (130 loc) · 5.31 KB
/
Copy pathdocker-compose.yml
File metadata and controls
137 lines (130 loc) · 5.31 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
# Local development and test infrastructure.
#
# PostgreSQL, not SQLite, in every environment including tests: S0 §11 depends on CHECK constraint
# semantics, revoked grants, Row-Level Security, NUMERIC precision, FOR UPDATE SKIP LOCKED and
# advisory locks, none of which SQLite has. A suite that passed against SQLite would leave every
# one of those invariants unverified.
#
# Redis holds sessions (S0 §7.1) and rate-limit counters (§7.4) only. It never holds financial
# state, the idempotency store, or queued ledger work — that is Postgres's job (ADR 13).
services:
db:
image: postgres:16-alpine
container_name: trueup-db
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: trueup
# Host port 5433, not 5432: other projects on this machine run their own Postgres on the
# default port. A non-default host port makes it impossible to point Trueup at the wrong
# database by accident, or to clobber someone else's.
ports:
- "5433:5432"
volumes:
- trueup-db-data:/var/lib/postgresql/data
- ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/00-roles.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d trueup"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
container_name: trueup-redis
ports:
- "6380:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
# `backend`/`frontend` are gated behind the `app` profile so `make up` (documented as "start
# PostgreSQL and Redis") is unaffected -- `docker compose --profile app up --build` runs the
# full containerized stack, which is what CI's image-build/scan step and a local end-to-end
# smoke test both use. Internal hostnames (`db`, `redis`), not the host-mapped 5433/6380 ports,
# since these two talk to Postgres/Redis over the compose network directly.
backend:
profiles: ["app"]
build:
context: ./backend
container_name: trueup-backend
env_file: .env
environment:
DATABASE_URL: postgresql+psycopg://trueup_app:trueup_app@db:5432/trueup
DATABASE_URL_WORKER: postgresql+psycopg://trueup_worker:trueup_worker@db:5432/trueup
DATABASE_URL_OWNER: postgresql+psycopg://trueup_owner:trueup_owner@db:5432/trueup
DATABASE_URL_CHAT: postgresql+psycopg://trueup_chat_readonly:trueup_chat_readonly@db:5432/trueup
REDIS_URL: redis://redis:6379/0
# Host port 5050, not 5000: macOS's AirPlay Receiver (ControlCenter) already owns port 5000,
# and `make dev`'s bare-metal Flask server already documents 5000 for the non-container path
# -- picking a third, distinct port avoids both collisions rather than fighting either.
ports:
- "5050:8000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health/live')"]
interval: 10s
timeout: 5s
retries: 10
# The always-on `job_outbox` drain process (S0 §9) -- without it, nothing ever submits an
# order to the broker, charges a fee, or applies an Alpaca fill to the ledger (`app.jobs
# .outbox_worker_command`'s own docstring has the full task-routing rationale). Same image as
# `backend`, different entrypoint -- reuses the built image rather than a second Dockerfile.
worker:
profiles: ["app"]
build:
context: ./backend
container_name: trueup-worker
command: ["flask", "--app", "wsgi", "jobs", "outbox-worker"]
env_file: .env
environment:
DATABASE_URL: postgresql+psycopg://trueup_app:trueup_app@db:5432/trueup
DATABASE_URL_WORKER: postgresql+psycopg://trueup_worker:trueup_worker@db:5432/trueup
DATABASE_URL_OWNER: postgresql+psycopg://trueup_owner:trueup_owner@db:5432/trueup
DATABASE_URL_CHAT: postgresql+psycopg://trueup_chat_readonly:trueup_chat_readonly@db:5432/trueup
REDIS_URL: redis://redis:6379/0
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
# S13/ADR 24's MCP agent surface -- a separate ASGI process, same image as `backend`, different
# entrypoint (same "different process, same database" shape `worker` already establishes).
mcp-server:
profiles: ["app"]
build:
context: ./backend
container_name: trueup-mcp-server
command: ["python", "-m", "app.mcp.entrypoint"]
env_file: .env
environment:
DATABASE_URL: postgresql+psycopg://trueup_app:trueup_app@db:5432/trueup
DATABASE_URL_WORKER: postgresql+psycopg://trueup_worker:trueup_worker@db:5432/trueup
DATABASE_URL_OWNER: postgresql+psycopg://trueup_owner:trueup_owner@db:5432/trueup
DATABASE_URL_CHAT: postgresql+psycopg://trueup_chat_readonly:trueup_chat_readonly@db:5432/trueup
REDIS_URL: redis://redis:6379/0
MCP_SERVER_HOST: 0.0.0.0
MCP_SERVER_PORT: "8001"
ports:
- "8001:8001"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
frontend:
profiles: ["app"]
build:
context: ./frontend
container_name: trueup-frontend
ports:
- "8080:8080"
depends_on:
backend:
condition: service_healthy
volumes:
trueup-db-data: