Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
269 lines (262 loc) · 11.2 KB
/
Copy pathdocker-compose.yml
File metadata and controls
269 lines (262 loc) · 11.2 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
# The self-host stack. `docker compose up` runs the whole node: API, async
# worker, Redis and the web UI.
#
# Services carry both `image:` and `build:`, so one file serves two audiences —
# with both set, Compose pulls the published image and only builds when asked:
#
# docker compose up # self-host: pull published, tested images
# docker compose up --build # develop: build from source
#
# Pulling by default also avoids a trap: `frontend/docs-dist/` is tracked but
# empty, so a from-source frontend build succeeds while shipping an empty /docs.
#
# Tags are kept equal to pyproject by scripts/bump_version.py. Set OHM_VERSION to
# run another release (OHM_VERSION=0.10 floats on patches).
#
# Requires Compose >= 2.24 for `env_file: required:`.
services:
# Open Hardware Manager API Server
ohm-api:
image: touchthesun/openhardwaremanager:${OHM_VERSION:-0.14.1}
build:
context: .
dockerfile: Dockerfile
container_name: ohm-api
ports:
# Loopback by default: a bare "8001:8001" binds every interface, so a
# node on a laptop joining a cafe network would publish its raw API to
# that network (#523, the treatment #480 already gave the installer).
# The web container reverse-proxies /v1 over the compose network, so
# nothing needs this host port to reach the node. Set API_BIND=0.0.0.0
# to use this node as an API server from another machine — and read
# #344 and #478 before you do.
- "${API_BIND:-127.0.0.1}:${API_PORT:-8001}:8001"
# Optional so a clean clone starts with no setup. Still the only way secrets
# reach the container: API keys, storage credentials and LLM keys are not
# listed under `environment:`, so they arrive by passthrough from here.
env_file:
- path: .env
required: false
environment:
# Core API settings
# Note: PORT is handled by docker-entrypoint.sh which prioritizes PORT over API_PORT
# Cloud Run sets PORT, so we support both PORT and API_PORT for compatibility
# If PORT is set in host environment, it will be passed through automatically
# If PORT is not set, docker-entrypoint.sh will use API_PORT (defaults to 8001)
- API_HOST=${API_HOST:-0.0.0.0}
- API_PORT=${API_PORT:-8001}
# PORT: Pass through if set (e.g., from Cloud Run or host environment)
# If PORT is not set, docker-entrypoint.sh will use API_PORT (defaults to 8001)
# Note: Using ${PORT:-} means if PORT is unset, it becomes empty string,
# but entrypoint script handles empty PORT correctly by falling back to API_PORT
- PORT=${PORT:-}
# DEBUG defaults to False in settings.py (case-insensitive)
- DEBUG=${DEBUG:-False}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- ENVIRONMENT=${ENVIRONMENT:-development}
# CORS and security
# Note: CORS_ORIGINS defaults to "*" in development, empty in production (handled in settings.py)
- CORS_ORIGINS=${CORS_ORIGINS:-*}
# API_KEYS should come from .env file (sensitive value)
# Do not set default here - should be in .env file for security
- API_KEYS=${API_KEYS:-}
# Storage configuration
# Note: Storage config uses provider-specific env vars:
# - LOCAL_STORAGE_PATH for local provider (defaults to "storage" in code)
# - AWS_S3_BUCKET for AWS S3
# - AZURE_STORAGE_CONTAINER for Azure Blob
# - GCP_STORAGE_BUCKET for Google Cloud Storage
- STORAGE_PROVIDER=${STORAGE_PROVIDER:-local}
# For local storage, LOCAL_STORAGE_PATH defaults to "storage" in code
# Set it here only if you want a different path
- LOCAL_STORAGE_PATH=${LOCAL_STORAGE_PATH:-storage}
# LLM configuration. A kill switch, not an enable switch: with no provider
# configured this changes nothing, so it defaults to the schema's `true`
# and a key added through Settings works without further configuration.
# Flip it to stop LLM spend: LLM_ENABLED=false docker compose up
- LLM_ENABLED=${LLM_ENABLED:-true}
# Server configuration
- USE_GUNICORN=${USE_GUNICORN:-auto}
# Federation (must be set in container env — host shell export alone is not enough)
- OHM_FEDERATION_ENABLED=${OHM_FEDERATION_ENABLED:-false}
- OHM_FEDERATION_NODE_NAME=${OHM_FEDERATION_NODE_NAME:-OHM Node}
- OHM_FEDERATION_NODE_ROLE=${OHM_FEDERATION_NODE_ROLE:-peer}
- OHM_FEDERATION_DATA_DIR=${OHM_FEDERATION_DATA_DIR:-/app/storage/federation}
# Beside the object root, not in it: this compose mounts one volume at
# /app/storage and uses it as the object root, so the saved storage
# configuration (credentials for the *next* backend) must not be listed,
# migrated or backed up as an object. The storage layer refuses any key that
# names it; see src/core/storage/node_local.py.
- OHM_STORAGE_CONFIG_PATH=${OHM_STORAGE_CONFIG_PATH:-/app/storage/config/storage-config.json}
- OHM_FEDERATION_MANUAL_PEERS=${OHM_FEDERATION_MANUAL_PEERS:-}
- OHM_FEDERATION_SYNC_INTERVAL_SEC=${OHM_FEDERATION_SYNC_INTERVAL_SEC:-60}
# Cache (memory default; set CACHE_BACKEND=redis to share across replicas)
- CACHE_BACKEND=${CACHE_BACKEND:-memory}
- CACHE_REDIS_URL=${CACHE_REDIS_URL:-redis://redis:6379/0}
- CACHE_KEY_PREFIX=${CACHE_KEY_PREFIX:-ohm}
# Async jobs (Celery). A kill switch like LLM_ENABLED: the broker below is
# what makes jobs available, and this only turns them off.
# Stop consuming work without editing anything: JOBS_ENABLED=false docker compose up
- JOBS_ENABLED=${JOBS_ENABLED:-true}
- JOB_BROKER_URL=${JOB_BROKER_URL:-redis://redis:6379/1}
- JOB_RESULT_BACKEND=${JOB_RESULT_BACKEND:-redis://redis:6379/2}
volumes:
- ohm-storage:/app/storage
- ohm-logs:/app/logs
# Optional: Mount local directories for development (may require Docker Desktop permissions)
# - ./storage:/app/storage
# - ./logs:/app/logs
- ./test-data:/app/test-data
networks:
- ohm-network
depends_on:
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
# Note: Healthcheck uses hardcoded port 8001 (default)
# If PORT or API_PORT is changed, update this accordingly
# The Dockerfile healthcheck uses ${PORT:-8001} which is more flexible
test: ["CMD", "curl", "-f", "http://localhost:8001/health/readiness"]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
# Redis for cache + Celery broker/result backend.
#
# Deliberately NOT published to the host: the API and worker reach it over the
# compose network by name, and an unauthenticated Redis on a public interface
# is among the most reliably exploited misconfigurations there is.
# For a shell: docker compose exec redis redis-cli
redis:
image: redis:7-alpine
container_name: ohm-redis
networks:
- ohm-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
# Celery worker sidecar (same image as ohm-api, worker entrypoint mode).
ohm-worker:
image: touchthesun/openhardwaremanager:${OHM_VERSION:-0.14.1}
build:
context: .
dockerfile: Dockerfile
container_name: ohm-worker
command: ["worker"]
env_file:
- path: .env
required: false
environment:
- ENVIRONMENT=${ENVIRONMENT:-development}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- STORAGE_PROVIDER=${STORAGE_PROVIDER:-local}
- LOCAL_STORAGE_PATH=${LOCAL_STORAGE_PATH:-storage}
# Must match ohm-api: the worker runs the same generation code.
- LLM_ENABLED=${LLM_ENABLED:-true}
- JOBS_ENABLED=${JOBS_ENABLED:-true}
- JOB_BROKER_URL=${JOB_BROKER_URL:-redis://redis:6379/1}
- JOB_RESULT_BACKEND=${JOB_RESULT_BACKEND:-redis://redis:6379/2}
- CELERY_CONCURRENCY=${CELERY_CONCURRENCY:-1}
volumes:
- ohm-storage:/app/storage
- ohm-logs:/app/logs
networks:
- ohm-network
depends_on:
redis:
condition: service_healthy
# Ordering only, not readiness: start the API's mount of the shared
# ohm-storage volume before the worker's, rather than both at once.
#
# Added after a single observed first-run failure — `failed to mkdir
# /var/lib/docker/volumes/…_ohm-storage/_data/federation: file exists` on
# ohm-worker. It has not reproduced in 34 attempts since, including 12
# with this guard removed, so treat it as cheap insurance on a plausible
# mechanism rather than a diagnosed fix. Costs nothing: `service_started`
# adds no wait beyond container creation.
ohm-api:
condition: service_started
restart: unless-stopped
# No healthcheck override: the image's own check knows it is a worker and
# asks the broker instead of curling a port nothing is listening on. This
# override used to be the only thing hiding a default that could never pass.
# Web UI. Serves the SPA and reverse-proxies /v1 to the API, so the browser
# talks to one origin and there is no CORS configuration to get wrong.
ohm-frontend:
image: touchthesun/openhardwaremanager-frontend:${OHM_VERSION:-0.14.1}
build: frontend
container_name: ohm-frontend
ports:
- "8080:8080"
environment:
- API_UPSTREAM_URL=http://ohm-api:8001
networks:
- ohm-network
depends_on:
ohm-api:
condition: service_healthy
restart: unless-stopped
# Prometheus for metrics collection
prometheus:
image: prom/prometheus:latest
container_name: ohm-prometheus
ports:
# Loopback only: unauthenticated, and nothing off-box needs to reach it.
- "127.0.0.1:9090:9090"
volumes:
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
- '--web.enable-lifecycle'
networks:
- ohm-network
depends_on:
- ohm-api
restart: unless-stopped
profiles:
- monitoring
# Start with: docker-compose --profile monitoring up prometheus
# Open Hardware Manager CLI (for testing CLI commands)
ohm-cli:
build: .
container_name: ohm-cli
environment:
- SERVER_URL=http://ohm-api:8001
- LOG_LEVEL=INFO
- STORAGE_PROVIDER=local
# Note: For local storage, LOCAL_STORAGE_PATH defaults to "storage" in code
# Using shared volume, so bucket name doesn't matter for CLI service
- LLM_ENABLED=false
volumes:
- ohm-storage:/app/storage
- ohm-logs:/app/logs
# Optional: Mount local directories for development (may require Docker Desktop permissions)
# - ./storage:/app/storage
# - ./logs:/app/logs
# - ./test-data:/app/test-data
networks:
- ohm-network
depends_on:
- ohm-api
profiles:
- cli
# This service is only started when explicitly requested
# Use: docker-compose --profile cli up ohm-cli
networks:
ohm-network:
driver: bridge
volumes:
ohm-storage:
driver: local
ohm-logs:
driver: local
prometheus-data:
driver: local