Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
105 lines (99 loc) · 4.13 KB
/
Copy pathdocker-compose.yml
File metadata and controls
105 lines (99 loc) · 4.13 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
# The whole platform, locally: Postgres, Redis, the API and the client.
#
# cp .env.example .env
# docker compose up --build
# open http://localhost:3000
#
# This is also the configuration that exercises the code paths the test suite cannot reach on its
# own: the `pg` adapter over the wire protocol with a real connection pool, and the `ioredis`
# adapter against real Redis. The test suite runs the *same contract suites* against them when
# TEST_DATABASE_URL and TEST_REDIS_URL are set -- see `docs/operations.md`.
name: fullstack-typescript-platform
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: platform
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-platform-local-only}
POSTGRES_DB: platform
ports:
# Exposed so the contract suites can be pointed at it from the host.
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
# `pg_isready` with the database named: the default checks the server is listening, which it
# does before the named database exists on first start.
test: ["CMD-SHELL", "pg_isready -U platform -d platform"]
interval: 5s
timeout: 3s
retries: 20
redis:
image: redis:7-alpine
# `--appendonly yes` so rate-limit counters and the token denylist survive a restart. Losing
# them is not catastrophic, but a restart that silently resets every limiter is a window.
command: ["redis-server", "--appendonly", "yes", "--save", ""]
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 20
api:
build:
context: ./server
target: runtime
environment:
NODE_ENV: production
PORT: 4000
# Not a default in the code: a development fallback secret is how a production deployment
# ends up signing tokens with a value from a repository. Generate one with
# `openssl rand -base64 48`.
# Quoted, and it has to be: the `${VAR:?message}` error text contains ": ", which YAML
# reads as a nested mapping. Unquoted, this file failed to parse at all -- `docker compose`
# rejected it with "mapping values are not allowed in this context" before it built
# anything. The `docker compose config` step in CI now catches that class of mistake in
# seconds instead of after a full image build.
JWT_SECRET: "${JWT_SECRET:?set JWT_SECRET in .env - generate one with openssl rand -base64 48}"
DATABASE_URL: "postgres://platform:${POSTGRES_PASSWORD:-platform-local-only}@postgres:5432/platform"
REDIS_URL: redis://redis:6379
CORS_ORIGINS: http://localhost:3000
# Required, because NODE_ENV is production above and the origin is localhost. That
# combination is exactly what the production check rejects by default -- and rightly, since
# shipping a localhost allow list is a real mistake. Here it is deliberate: this stack runs
# the production build for a browser on the developer's own machine. Opting in explicitly is
# the point; the check still catches a deployment that simply forgot to change the default.
ALLOW_LOCALHOST_CORS: "true"
LOG_LEVEL: ${LOG_LEVEL:-info}
ports:
- "4000:4000"
depends_on:
# `service_healthy`, not `service_started`: with `started` the API races the database's
# initialisation and its first migration fails on a server that is not accepting connections
# yet.
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
web:
build:
context: ./web
target: runtime
environment:
# Read at runtime and injected into the page, so this image is not environment-specific.
# The browser resolves it, so it is localhost from the host's point of view -- not the
# compose network name, which a browser cannot resolve.
PLATFORM_API_URL: http://localhost:4000
PLATFORM_WS_URL: ws://localhost:4000/ws
ports:
- "3000:3000"
depends_on:
- api
restart: unless-stopped
volumes:
postgres-data:
redis-data: