-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
293 lines (284 loc) · 10.1 KB
/
docker-compose.yml
File metadata and controls
293 lines (284 loc) · 10.1 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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
---
# Self-hosted Puter — full stack.
#
# Brings up Puter + every external service it needs:
# - nginx : reverse proxy (mirrors prod ALB; handles TLS + Host fan-out)
# - valkey : redis-compatible cache / rate-limiter backend
# - mariadb : SQL database (Puter applies its schema on first boot)
# - dynamo : DynamoDB-local (KV store; Puter creates the table itself)
# - s3 : RustFS — S3-compatible object storage
# - s3-init : one-shot init container that creates the bucket
# - puter : the application
#
# Quick start:
# 1. Copy .env.example to .env (or set the variables in your shell).
# 2. Drop a config.json into ./puter/config/ — see selfhosted/full-stack.md
# for the example that pairs with this compose.
# 3. docker compose up -d
#
# Easiest path:
# curl -fsSL https://raw.githubusercontent.com/HeyPuter/puter/main/install.sh | sh
# grabs this file, generates secrets, writes .env + config.json, and runs
# the compose up for you.
#
# Production:
# - Always replace the default passwords / S3 keys / Puter secrets.
# - Front Puter with TLS-terminating reverse proxy (Caddy / nginx).
# - Move state-bearing volumes to a backed-up location.
services:
valkey:
image: valkey/valkey:8-alpine
container_name: puter-valkey
restart: unless-stopped
# Run as a single-node cluster so Puter's ioredis Cluster client
# (the only mode it speaks) can connect. On first boot we assign all
# 16384 slots to ourselves; subsequent boots find them already in
# nodes.conf and skip. `cluster-require-full-coverage no` keeps reads
# working if we ever land partial slots.
command:
- sh
- -c
- |
valkey-server \
--port 6379 \
--cluster-enabled yes \
--cluster-config-file /data/nodes.conf \
--cluster-node-timeout 5000 \
--cluster-require-full-coverage no \
--cluster-announce-ip valkey \
--cluster-announce-port 6379 \
--cluster-announce-bus-port 16379 \
--appendonly yes \
--save "60 1" &
SERVER_PID=$$!
until valkey-cli -p 6379 PING > /dev/null 2>&1; do sleep 0.5; done
if ! valkey-cli -p 6379 CLUSTER NODES | grep -q '0-16383'; then
valkey-cli -p 6379 CLUSTER ADDSLOTSRANGE 0 16383
fi
wait $$SERVER_PID
volumes:
# `:z` is an SELinux relabel hint for Fedora/RHEL hosts (no-op
# everywhere else) — without it those distros deny container
# access to the bind mount and the service loops on EACCES.
- ./puter/data/valkey:/data:z
healthcheck:
test:
["CMD-SHELL", "valkey-cli -p 6379 cluster info | grep -q cluster_state:ok"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
mariadb:
image: mariadb:11
container_name: puter-mariadb
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-root-change-me}
MARIADB_DATABASE: ${MARIADB_DATABASE:-puter}
MARIADB_USER: ${MARIADB_USER:-puter}
MARIADB_PASSWORD: ${MARIADB_PASSWORD:-puter-change-me}
volumes:
- ./puter/data/mariadb:/var/lib/mysql:z
healthcheck:
# `healthcheck.sh` ships with the mariadb image; --connect verifies
# the server is accepting auth, not just listening on the socket.
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 20
start_period: 30s
dynamo:
# Puter creates the `store-kv-v1` table itself on startup
# (config.dynamo.bootstrapTables = true does the work).
image: amazon/dynamodb-local:latest
container_name: puter-dynamo
restart: unless-stopped
user: "1000:1000"
working_dir: /home/dynamodblocal
command:
- "-jar"
- "DynamoDBLocal.jar"
- "-sharedDb"
- "-dbPath"
- "/home/dynamodblocal/data"
volumes:
- ./puter/data/dynamo:/home/dynamodblocal/data:z
s3:
# RustFS — S3-compatible object storage. Drop-in alternative:
# MinIO (image: minio/minio, command: ["server", "/data", "--console-address", ":9001"]).
image: rustfs/rustfs:latest
container_name: puter-s3
restart: unless-stopped
environment:
RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY:-puter}
RUSTFS_SECRET_KEY: ${S3_SECRET_KEY:-puter-secret-change-me}
volumes:
- ./puter/data/s3:/data:z
# Internal-only — browsers reach RustFS via nginx (`s3.<domain>`),
# which preserves the Host header for S3 signature validation and
# rides the same TLS termination as Puter. Uncomment to also expose
# 9000 directly on the host for `aws-cli` / debugging.
# ports:
# - "9000:9000"
healthcheck:
# RustFS exposes /health on the S3 port. Use wget (curl is not in
# the slim image).
test:
[
"CMD-SHELL",
"wget -qO- --tries=1 --timeout=2 http://localhost:9000/health || exit 1",
]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
s3-init:
# One-shot container that creates the `puter-local` bucket on first
# boot. Exits 0 once the bucket exists; stays exited 0 thereafter.
image: amazon/aws-cli:latest
container_name: puter-s3-init
depends_on:
s3:
condition: service_healthy
environment:
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY:-puter}
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY:-puter-secret-change-me}
AWS_DEFAULT_REGION: us-east-1
entrypoint:
- /bin/sh
- -c
- |
set -e
endpoint=http://s3:9000
bucket=${S3_BUCKET:-puter-local}
if aws --endpoint-url "$$endpoint" s3api head-bucket --bucket "$$bucket" 2>/dev/null; then
echo "bucket $$bucket already exists"
else
echo "creating bucket $$bucket"
aws --endpoint-url "$$endpoint" s3 mb "s3://$$bucket"
fi
restart: "no"
# ── Optional: local LLM ───────────────────────────────────────────
# Behind the `ai` compose profile — only starts when explicitly opted
# into. Bring up with:
# docker compose --profile ai up -d
# When enabled, also set in your `puter/config/config.json`:
# "providers": { "ollama": { "apiBaseUrl": "http://ollama:11434" } }
# When NOT enabled, set:
# "providers": { "ollama": { "enabled": false } }
# otherwise Puter spams `ECONNREFUSED 127.0.0.1:11434` on startup.
ollama:
profiles: ["ai"]
# CPU-only out of the box; uncomment the GPU `deploy:` block below
# if you've got nvidia-docker for much faster inference. Disk + RAM
# scale with the model — `tinyllama` (1.1B, ~640 MB on disk, ~700
# MB RAM) is the cheapest sane default. Swap via OLLAMA_DEFAULT_MODEL.
image: ollama/ollama:latest
container_name: puter-ollama
restart: unless-stopped
volumes:
- ./puter/data/ollama:/root/.ollama:z
# Uncomment to expose Ollama directly on the host (`localhost:11434`)
# for `ollama` CLI / OpenAI-API compatible tools. Internal-only by default.
# ports:
# - "11434:11434"
healthcheck:
test:
["CMD-SHELL", "ollama list >/dev/null 2>&1 || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
# GPU passthrough (NVIDIA). Requires nvidia-container-toolkit on host.
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
ollama-init:
profiles: ["ai"]
# One-shot — ensures the default model is present. `ollama pull` is
# idempotent: present-and-up-to-date → fast no-op; missing → downloads.
image: ollama/ollama:latest
container_name: puter-ollama-init
depends_on:
ollama:
condition: service_healthy
environment:
OLLAMA_HOST: http://ollama:11434
OLLAMA_DEFAULT_MODEL: ${OLLAMA_DEFAULT_MODEL:-tinyllama}
entrypoint:
- /bin/sh
- -c
- |
set -e
echo "[ollama-init] ensuring $${OLLAMA_DEFAULT_MODEL}"
ollama pull "$${OLLAMA_DEFAULT_MODEL}"
echo "[ollama-init] done"
restart: "no"
puter:
image: ghcr.io/heyputer/puter:main
pull_policy: always
# Uncomment to build from this directory instead of pulling the published
# image. Also flip pull_policy to `never` so compose doesn't overwrite
# your local build by re-pulling :latest.
# build:
# context: .
# # buildx-only: cross-compile to both archs in a single push
# # platforms:
# # - linux/amd64
# # - linux/arm64
container_name: puter
restart: unless-stopped
depends_on:
valkey:
condition: service_healthy
mariadb:
condition: service_healthy
dynamo:
condition: service_started
s3-init:
condition: service_completed_successfully
# Internal-only: nginx reaches it on the compose network. Uncomment
# to also expose port 4100 directly on the host (useful for debugging).
# ports:
# - "4100:4100"
expose:
- "4100"
environment:
PUID: 1000
PGID: 1000
volumes:
# Drop your config.json here — see selfhosted/full-stack.md.
- ./puter/config:/etc/puter:z
# Persistent runtime data (anything your config points at /var/puter).
- ./puter/data/puter:/var/puter:z
healthcheck:
test: wget --no-verbose --tries=1 --spider http://puter.localhost:4100/test || exit 1
interval: 30s
timeout: 3s
retries: 3
start_period: 30s
nginx:
image: nginx:1.27-alpine
container_name: puter-nginx
restart: unless-stopped
depends_on:
puter:
condition: service_started
ports:
- "${HTTP_PORT:-80}:80"
# Uncomment when you enable TLS in nginx/nginx.conf:
# - "${HTTPS_PORT:-443}:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro,z
# TLS certs (fullchain.pem + privkey.pem). Read-only inside.
- ./puter/tls:/etc/nginx/tls:ro,z
healthcheck:
test: ["CMD-SHELL", "wget -qO- --tries=1 --timeout=2 http://localhost/ || exit 1"]
interval: 10s
timeout: 3s
retries: 5
start_period: 5s