Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
150 lines (145 loc) · 5.87 KB
/
Copy pathdocker-compose.yml
File metadata and controls
150 lines (145 loc) · 5.87 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
# The control plane without Cloudflare: the Node host, an S3-compatible
# object store for media and backups, and a Litestream sidecar replicating
# the global store. This is the stack the AWS instance runs and the local
# stand-in for it. The web app is not part of it (it stays on Vercel; run
# `next dev` locally).
#
# cp .env.example .env # then fill it in
# docker compose up --build
#
# Only the app reads .env; the sidecars get the few variables they need by
# name. The app's port is published on APP_BIND_ADDRESS (loopback unless
# .env says otherwise) and the object store's on loopback only, so nothing
# here listens on a public interface by default. See
# docs/CONTROL_PLANE_CONTAINER.md.
services:
app:
build:
context: .
dockerfile: packages/control-plane/Dockerfile
image: open-inspect-control-plane:local
env_file: .env
# Pinned here so a .env edit cannot move the host off its volume, its
# published port, or its drain budget; PORT in .env is the host-side port
# below. The stop grace period must outlast SHUTDOWN_TIMEOUT_MS plus the
# 5 s the host keeps before forcing its own exit, so both live here.
environment:
HOST: 0.0.0.0
PORT: "8787"
DATA_DIR: /data
SHUTDOWN_TIMEOUT_MS: "30000"
stop_grace_period: 40s
ports:
- "${APP_BIND_ADDRESS:-127.0.0.1}:${PORT:-8787}:8787"
volumes:
- control-plane-data:/data
depends_on:
object-store:
condition: service_healthy
restart: unless-stopped
# SeaweedFS as the S3-compatible store for media and backups, pinned by
# digest to the multi-arch index so amd64 CI and arm64 laptops run the same
# release. Credentials are required on every S3 request.
#
# `weed server` runs every component in this one container. Only the S3
# gateway listens beyond it; the master, volume server and filer bind to
# loopback, since nothing outside the container talks to them. Each start
# generates the filer signing key the components share. The buckets named
# in S3_BUCKET are created once S3 answers.
#
# The shell stays as PID 1 to create the buckets, and forwards SIGTERM to
# the server. The filer can take 10 s to close its streams, so the stop
# grace period outlasts it.
object-store:
image: chrislusf/seaweedfs:4.47@sha256:ce9e796f1fe6f06968f4c04bdaf8f678dad9c8acdfef3d244133d71bfa6bf882
entrypoint: ["/bin/sh", "-c"]
command:
- |
WEED_JWT_FILER_SIGNING_KEY="$$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
export WEED_JWT_FILER_SIGNING_KEY
/entrypoint.sh server -ip=127.0.0.1 -ip.bind=127.0.0.1 -s3.ip.bind=0.0.0.0 \
-master.peers=none -master.telemetry=false -volume.preStopSeconds=1 \
-filer -s3 -s3.port=9000 -s3.port.iceberg=0 -s3.port.lance=0 &
server=$$!
trap 'kill -TERM "$$server"' TERM INT
until curl -sf -o /dev/null http://localhost:9000/healthz; do
kill -0 "$$server" 2>/dev/null || exit 1
sleep 1
done
IFS=,
for bucket in $$S3_BUCKET; do
echo "s3.bucket.create -name $$bucket" | weed shell -master=127.0.0.1:9333 >/dev/null
done
wait "$$server" || wait "$$server"
stop_grace_period: 20s
environment:
AWS_ACCESS_KEY_ID: ${OBJECT_STORE_ROOT_USER:-objectstoreadmin}
AWS_SECRET_ACCESS_KEY: ${OBJECT_STORE_ROOT_PASSWORD:?OBJECT_STORE_ROOT_PASSWORD must be set; generate one with openssl rand -hex 16}
S3_BUCKET: ${OBJECT_STORE_BUCKET:?OBJECT_STORE_BUCKET must name the media bucket},${LITESTREAM_BUCKET:?LITESTREAM_BUCKET must name the backup bucket}
# Loopback only: the app and the sidecar reach it over the compose network.
ports:
- "127.0.0.1:9000:9000"
volumes:
- object-store-data:/data
# Healthy once S3 answers and every bucket exists, so the app and
# Litestream never start against a store without its buckets. The filer is
# asked, over loopback, because S3 answers an unsigned request 403 whether
# or not the bucket exists. Checks run every 2 s until the first passes,
# then every 30 s; an engine older than Docker 25 skips the 2 s phase.
healthcheck:
test:
- CMD-SHELL
- >-
curl -sf -o /dev/null http://localhost:9000/healthz &&
IFS=, && for bucket in $$S3_BUCKET; do
curl -sf -o /dev/null "http://localhost:8888/buckets/$$bucket/" || exit 1;
done
interval: 30s
timeout: 3s
retries: 3
start_period: 60s
start_interval: 2s
restart: unless-stopped
litestream:
image: litestream/litestream:0.3.13
# The app's user, so the shadow WAL it writes beside the database is
# owned like the database.
user: "1000:1000"
command: replicate -config /etc/litestream.yml
environment:
LITESTREAM_BUCKET: ${LITESTREAM_BUCKET:?LITESTREAM_BUCKET must name the backup bucket}
LITESTREAM_ENDPOINT: ${LITESTREAM_ENDPOINT:-}
LITESTREAM_ACCESS_KEY_ID: ${LITESTREAM_ACCESS_KEY_ID:-}
LITESTREAM_SECRET_ACCESS_KEY: ${LITESTREAM_SECRET_ACCESS_KEY:-}
volumes:
- control-plane-data:/data
- ./packages/control-plane/docker/litestream.yml:/etc/litestream.yml:ro
depends_on:
app:
condition: service_healthy
object-store:
condition: service_healthy
restart: unless-stopped
# Optional TLS termination: `docker compose --profile tls up` with
# CADDY_DOMAIN set and ports 80/443 reachable from the internet.
caddy:
image: caddy:2-alpine
profiles: ["tls"]
environment:
CADDY_DOMAIN: ${CADDY_DOMAIN:-}
ports:
- "80:80"
- "443:443"
volumes:
- ./packages/control-plane/docker/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
app:
condition: service_healthy
restart: unless-stopped
volumes:
control-plane-data:
object-store-data:
caddy-data:
caddy-config: