forked from rmyndharis/OpenWA
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
288 lines (280 loc) · 12 KB
/
Copy pathdocker-compose.yml
File metadata and controls
288 lines (280 loc) · 12 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
# OpenWA - Docker Compose Configuration
# Smart Orchestration with Profiles
services:
# ===== CORE: Docker Socket Proxy (sole container with /var/run/docker.sock access) =====
docker-proxy:
image: tecnativa/docker-socket-proxy
container_name: openwa-docker-proxy
restart: unless-stopped
# Only on the isolated internal network — reachable solely by openwa-api, NOT the
# dashboard or any other peer. `internal: true` also denies the proxy
# outbound access; it only needs the locally-mounted docker socket.
networks:
- internal-docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CONTAINERS: 1
IMAGES: 1
VOLUMES: 1
INFO: 1
PING: 1
POST: 1
DELETE: 1
# Everything else is denied by default (AUTH, SECRETS, NETWORKS, PLUGINS, SWARM, TASKS, SERVICES, CONFIGS, NODES, DISTRIBUTIONS)
labels:
- 'com.openwa.service=docker-proxy'
- 'com.openwa.core=true'
# ===== CORE: OpenWA Backend API =====
openwa-api:
build:
context: .
dockerfile: Dockerfile
container_name: openwa-api
restart: unless-stopped
# App network for datastores + the isolated network to reach
# docker-proxy (DOCKER_HOST). createService() in docker.service.ts attaches
# orchestrated containers to the literal `openwa-network`, so its name is fixed.
networks:
- openwa-network
- internal-docker
# Container hardening: Chromium runs with --no-sandbox, so the
# container itself is the confinement boundary. cap_drop ALL + a minimal re-add
# ONLY for the root entrypoint's chown + gosu privilege-drop; once gosu setuids to
# the openwa user the node/Chromium process keeps NO effective capabilities.
# read_only rootfs (Chromium's profile lives on the writable /app/data volume;
# HOME=/tmp + tmpfs absorb stray writes). no-new-privileges blocks setuid escalation.
# NOTE: requires a live single-session smoke (Chromium must launch) before merge.
security_opt:
- 'no-new-privileges:true'
cap_drop:
- ALL
cap_add:
- CHOWN # entrypoint: chown -R openwa /app/data on the named volume
- DAC_OVERRIDE # entrypoint: chown across pre-existing files
- FOWNER
- SETGID # gosu: drop to the openwa group
- SETUID # gosu: drop to the openwa user
read_only: true
tmpfs:
- /tmp
pids_limit: 512
mem_limit: ${OPENWA_MEM_LIMIT:-2g} # tune up for many concurrent sessions
ports:
- '127.0.0.1:${API_PORT:-2785}:2785'
expose:
- '2785'
environment:
# Core
- NODE_ENV=${NODE_ENV:-production}
# Writable HOME on tmpfs so Chromium's HOME-relative writes don't hit the read_only rootfs
- HOME=/tmp
# Chromium resolves its home from the passwd entry (no /home/openwa), ignoring $HOME, so without
# writable, existing config/cache dirs it hard-crashes at launch on the read_only rootfs. Pin XDG
# to the tmpfs; the entrypoint pre-creates these owned by openwa. (#254)
- XDG_CONFIG_HOME=/tmp/.config
- XDG_CACHE_HOME=/tmp/.cache
- PORT=2785
- LOG_LEVEL=${LOG_LEVEL:-info}
# Database. Forwarded blank by default (`${VAR:-}`) so a dashboard switch saved to
# data/.env.generated actually applies at runtime — main.ts clears the blank (BLANK_SHADOWED_ENV_KEYS)
# and the file wins. Set any of these in your .env/host to pin it (a real value keeps top precedence
# and the dashboard control then shows "managed by environment"). First-run defaults (sqlite) are
# written to data/.env.generated by the app, so a blank stack still boots SQLite.
# NOTE: if you run the built-in Postgres MANUALLY (`docker compose --profile postgres up`) instead
# of via the dashboard, set DATABASE_TYPE=postgres, DATABASE_HOST=postgres, DATABASE_PORT=5432,
# DATABASE_USERNAME=openwa and DATABASE_PASSWORD in your .env/host — these are no longer defaulted here.
- DATABASE_TYPE=${DATABASE_TYPE:-}
- DATABASE_NAME=${DATABASE_NAME:-}
- DATABASE_HOST=${DATABASE_HOST:-}
- DATABASE_PORT=${DATABASE_PORT:-}
- DATABASE_USERNAME=${DATABASE_USERNAME:-}
# No committed default secret; blank unless the operator sets it (built-in Postgres is provisioned
# with its own credentials by the orchestrator, not via this line).
- DATABASE_PASSWORD=${DATABASE_PASSWORD:-}
- DATABASE_SYNCHRONIZE=${DATABASE_SYNCHRONIZE:-false}
# Engine. Forwarded empty by default so the dashboard (Infrastructure > Engine) selects the
# active engine via data/.env.generated (defaults to whatsapp-web.js); main.ts treats a blank
# ENGINE_TYPE as unset, so .env.generated wins. Set ENGINE_TYPE in your .env/host to pin an
# engine (e.g. baileys) — a real value flows through here and keeps top precedence.
- ENGINE_TYPE=${ENGINE_TYPE:-}
- SESSION_DATA_PATH=${SESSION_DATA_PATH:-/app/data/sessions}
- PUPPETEER_HEADLESS=${PUPPETEER_HEADLESS:-true}
- PUPPETEER_ARGS=${PUPPETEER_ARGS:---no-sandbox,--disable-setuid-sandbox,--disable-dev-shm-usage,--disable-gpu}
# Optional WhatsApp Web version override. Leave empty for whatsapp-web.js auto-selection.
# If a session hangs at "authenticating", set WWEBJS_WEB_VERSION to a known-good cached build
# from wppconnect-team/wa-version; latest|auto|off also forces auto-selection.
- WWEBJS_WEB_VERSION=${WWEBJS_WEB_VERSION:-}
- WWEBJS_WEB_VERSION_REMOTE_PATH=${WWEBJS_WEB_VERSION_REMOTE_PATH:-}
# Raise whatsapp-web.js's first-boot init wait (default 30000ms) on slow boots — e.g. WSL2 or
# low-resource hosts where the QR can time out before WA Web loads. Empty = default; see
# docs/12-troubleshooting-faq.md. (Without this line the var in .env never reaches the container.)
- WWEBJS_AUTH_TIMEOUT_MS=${WWEBJS_AUTH_TIMEOUT_MS:-}
# Storage. Blank-forwarded (see Database note) so a dashboard local↔S3 switch applies; a real
# host value pins. Credentials use the canonical S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY names the
# app and dashboard write; the legacy S3_ACCESS_KEY / S3_SECRET_KEY are ALSO forwarded (and read
# as a fallback by the storage layer) so existing .env files keep working unchanged. S3_REGION is
# forwarded so external-S3 works.
- STORAGE_TYPE=${STORAGE_TYPE:-}
- STORAGE_LOCAL_PATH=${STORAGE_LOCAL_PATH:-}
- S3_ENDPOINT=${S3_ENDPOINT:-}
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID:-}
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY:-}
- S3_ACCESS_KEY=${S3_ACCESS_KEY:-}
- S3_SECRET_KEY=${S3_SECRET_KEY:-}
- S3_REGION=${S3_REGION:-}
- S3_BUCKET=${S3_BUCKET:-}
# Redis. Blank-forwarded (see Database note) so a dashboard enable/disable applies; a real host
# value pins.
- REDIS_ENABLED=${REDIS_ENABLED:-}
- REDIS_HOST=${REDIS_HOST:-}
- REDIS_PORT=${REDIS_PORT:-}
# Webhook
- WEBHOOK_TIMEOUT=${WEBHOOK_TIMEOUT:-10000}
- WEBHOOK_MAX_RETRIES=${WEBHOOK_MAX_RETRIES:-3}
- WEBHOOK_RETRY_DELAY=${WEBHOOK_RETRY_DELAY:-5000}
# Rate Limit
- RATE_LIMIT_TTL=${RATE_LIMIT_TTL:-60}
- RATE_LIMIT_MAX=${RATE_LIMIT_MAX:-100}
# Plugins
- PLUGINS_ENABLED=${PLUGINS_ENABLED:-true}
- PLUGINS_DIR=${PLUGINS_DIR:-/app/data/plugins}
# Security
- API_MASTER_KEY=${API_MASTER_KEY:-}
# Docker socket proxy (openwa-api never touches /var/run/docker.sock directly)
- DOCKER_HOST=tcp://docker-proxy:2375
volumes:
- openwa-data:/app/data
- ./docker-compose.yml:/app/docker-compose.yml:ro
depends_on:
docker-proxy:
condition: service_started
postgres:
condition: service_healthy
required: false
redis:
condition: service_healthy
required: false
healthcheck:
test:
[
'CMD',
'node',
'-e',
"require('http').get('http://localhost:2785/api/health/ready', (r) => process.exit(r.statusCode === 200 ? 0 : 1))",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
labels:
- 'com.openwa.service=api'
- 'com.openwa.core=true'
# The dashboard SPA is now bundled into the openwa-api image and served by NestJS on the
# same port (2785) — there is no separate dashboard container. Reach it at the openwa-api
# port directly; put your own TLS reverse proxy (nginx/Caddy/cloud LB) in front if needed.
# ===== OPTIONAL: Built-in PostgreSQL =====
postgres:
image: postgres:16-alpine
container_name: openwa-postgres
profiles: ['postgres', 'full']
restart: unless-stopped
networks:
- openwa-network
security_opt:
- 'no-new-privileges:true'
environment:
POSTGRES_USER: ${DATABASE_USERNAME:-openwa}
# M16: no committed default secret. Empty unless the operator sets it; the postgres
# image refuses to initialize with an empty password (clear fail-fast), and the app's
# production boot guard rejects empty/placeholder secrets before startup.
POSTGRES_PASSWORD: ${DATABASE_PASSWORD:-}
POSTGRES_DB: ${DATABASE_NAME:-openwa}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${DATABASE_USERNAME:-openwa}']
interval: 5s
timeout: 3s
retries: 5
labels:
- 'com.openwa.service=database'
- 'com.openwa.builtin=true'
# ===== OPTIONAL: Built-in Redis =====
redis:
image: redis:7-alpine
container_name: openwa-redis
profiles: ['redis', 'full']
restart: unless-stopped
networks:
- openwa-network
security_opt:
- 'no-new-privileges:true'
command: redis-server --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 5
labels:
- 'com.openwa.service=cache'
- 'com.openwa.builtin=true'
# ===== OPTIONAL: Built-in MinIO (S3-compatible) =====
minio:
image: minio/minio
container_name: openwa-minio
profiles: ['minio', 'full']
restart: unless-stopped
networks:
- openwa-network
security_opt:
- 'no-new-privileges:true'
command: server /data --console-address ":9001"
environment:
# M16: no committed default creds. MinIO refuses to start with empty root creds
# (clear fail-fast); the app's production boot guard rejects empty/placeholder secrets.
# Canonical names, matching what the app/dashboard read (legacy S3_ACCESS_KEY/S3_SECRET_KEY
# still accepted as a fallback so older .env files keep working).
MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:-${S3_ACCESS_KEY:-}}
MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:-${S3_SECRET_KEY:-}}
volumes:
- minio-data:/data
ports:
- '127.0.0.1:9000:9000'
- '127.0.0.1:9001:9001'
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 10s
timeout: 5s
retries: 3
labels:
- 'com.openwa.service=storage'
- 'com.openwa.builtin=true'
volumes:
# Pin explicit volume names so the compose path and the Docker-API orchestration path (which uses
# the literal `openwa_<svc>-data` names in docker.service.ts) bind the SAME volume regardless of the
# compose project name — otherwise re-enabling a built-in service could mount a fresh empty volume.
openwa-data:
name: openwa_openwa-data
driver: local
postgres-data:
name: openwa_postgres-data
driver: local
redis-data:
name: openwa_redis-data
driver: local
minio-data:
name: openwa_minio-data
driver: local
networks:
# Application network — datastores and openwa-api. Keep the name `openwa-network`:
# docker.service.ts attaches orchestrated containers to it by literal name.
openwa-network:
name: openwa-network
# Isolated network for the Docker socket proxy. `internal: true` means no external
# connectivity; only openwa-api joins it, so nothing else can reach docker-proxy:2375.
internal-docker:
name: openwa-internal-docker
internal: true