Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
213 lines (206 loc) · 9.6 KB
/
Copy pathdocker-compose.yml
File metadata and controls
213 lines (206 loc) · 9.6 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
# Full local dev stack for the GroundBolt/ThunderCloud system.
#
# This file lives in the workspace metarepo because it spans component repos.
# Run compose commands from the workspace root.
#
# Images: the webapp, SymmetricDS and the meter-driver-emulator name the
# `latest` image that each component's CI publishes to GHCR from every build
# of its `main` branch, and each keeps a `build` section. No `pull_policy` is
# set, so Compose uses its default, "missing": `docker compose up` runs
# whatever image of that name exists locally and pulls only when none does.
# - On the first run `up` pulls the published `latest`, so it needs only this
# metarepo checked out.
# - `docker compose up --build` builds the images from ./thundercloud,
# ./symmetricds and ./meter-driver-emulator, the sibling checkouts that
# ./clone.sh provides. The local build carries the same image name, so
# later plain `up` runs keep using it.
# - `up` never re-pulls `latest` on its own. `docker compose pull` fetches the
# newest published images and replaces a local build of the same name.
# - A service whose image is not published yet fails to pull and is built
# from its checkout, so run ./clone.sh first in that case.
# - To build one component and pull the rest:
# `docker compose up -d --build ground` (or `docker compose build ground`).
# `develop.watch` rebuilds also need that component's checkout.
#
# Meter drivers: choose one with `--profile driver-emulator` (meter-driver-emulator)
# or `--profile driver-sparknet` (sparknet-http), then register it in the ground
# webapp under Global Settings > Meter Drivers at
# http://meter-driver-emulator:18080 or http://sparknet-http:8080. With
# neither profile the stack runs without a meter driver; the webapp boots
# normally with none registered.
#
# Webapp settings: `ground` and `cloud` take their development defaults from
# the `x-webapp-environment` block below. A `.env` at the workspace root is
# optional. Precedence for each variable: a value exported in the shell that
# runs compose wins, then the project `.env`, then the default here.
#
# The self-contained test harness (postgres-test + test runner) stays in
# thundercloud/docker-compose.test.yml — thundercloud's CI runs it with no
# sibling checkouts. Run tests from ./thundercloud.
# Development defaults for the webapp, shared by `ground` and `cloud`. Each
# value is `${VAR:-default}`: Compose substitutes VAR from the calling shell
# when it is exported there, else from the project `.env` when it is set
# there, else the default. A literal value here would
# hide the `.env` value, because `environment` takes precedence over
# `env_file`.
x-webapp-environment: &webapp-environment
SM_CURRENCY: "${SM_CURRENCY:-USD}"
SM_DEFAULT_PHONE_COUNTRY_CODE: "${SM_DEFAULT_PHONE_COUNTRY_CODE:-1}"
SM_SERIAL: "${SM_SERIAL:-1234}"
SM_SPARKCLOUD_API_KEY: "${SM_SPARKCLOUD_API_KEY:-test}"
SM_HEROKU: "${SM_HEROKU:-false}"
SM_HEARTBEAT_PERIOD: "${SM_HEARTBEAT_PERIOD:-15}"
INIT_CREATE_DEFAULTS: "${INIT_CREATE_DEFAULTS:-true}"
SM_INIT_ADMIN_USERNAME: "${SM_INIT_ADMIN_USERNAME:-admin}"
INIT_ADMIN_USERNAME: "${INIT_ADMIN_USERNAME:-admin}"
INIT_ADMIN_PASSWORD: "${INIT_ADMIN_PASSWORD:-password}"
INIT_ADMIN_EMAIL: "${INIT_ADMIN_EMAIL:-admin@sparkmeter.io}"
INIT_GROUND_NAME: "${INIT_GROUND_NAME:-Ground}"
INIT_GROUND_SERIAL: "${INIT_GROUND_SERIAL:-1234}"
SM_SECURITY_PASSWORD_SALT: "${SM_SECURITY_PASSWORD_SALT:-dev-salt-not-secret}"
SM_SECRET_KEY: "${SM_SECRET_KEY:-dev-not-secret}"
services:
ground:
container_name: "ground"
build: &webapp-build
context: './thundercloud'
dockerfile: deploy/Dockerfile
target: production
image: ghcr.io/earthspark/thundercloud:latest
command: /app/run/webapp
ports:
- "8765:5000"
env_file:
- path: .env
required: false
environment:
<<: *webapp-environment
DATABASE_URL: postgresql://spark:spark@postgres-ground/ground
# API base URL shown under API Credentials on a user's page. It follows
# the host port above, so it is fixed here rather than read from `.env`.
SM_API_ENDPOINT: http://localhost:8765/api/v0
# The ground row created on first boot derives its UUID from `serial`
# via `as_uuid(serial)`. INIT_GROUND_SERIAL and INIT_GROUND_NAME come
# from `x-webapp-environment` (defaults `1234` and `Ground`), and
# SERIAL, which drives the default-ground lookup, defaults to the same
# serial. To make this local deployment **be** a specific site (so
# meters/customers/etc. created here are anchored to the same ground
# row the cloud has), override these in your `.env` alongside the
# SYMDS_* values:
# INIT_GROUND_SERIAL=<site-serial>
# INIT_GROUND_NAME=<site-name>
# GROUND_SERIAL=<site-serial> # matches INIT_GROUND_SERIAL; drives default-ground lookup
# Without GROUND_SERIAL, SERIAL follows INIT_GROUND_SERIAL.
SERIAL: ${GROUND_SERIAL:-${INIT_GROUND_SERIAL:-1234}}
depends_on:
postgres-ground:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
develop:
watch:
- path: ./thundercloud
action: rebuild
# Metering provider: the SparkNet controller core with an HTTP+SSE API.
# Register it in the ground webapp under Global Settings > Meter Drivers
# with the base URL http://sparknet-http:8080. Pulled as the
# published image — ./sparknet-http is a distribution repo whose Dockerfile
# wraps prebuilt release binaries, so there is nothing to build from source
# here. Same service name as the production (ansible) deployment.
sparknet-http:
container_name: "sparknet-http"
image: ghcr.io/earthspark/sparknet-http:latest
# The binary serves a legacy line protocol on stdin and exits with status 1
# when stdin reaches end-of-file, which is what a non-interactive container
# gets. Keeping stdin open holds it running.
stdin_open: true
environment:
# Truthy SPARKNET_HTTP_SIMULATE_GATEWAY makes the provider run its
# in-memory gateway simulator; unset it to talk to a real gateway over
# a serial device (set SPARKNET_HTTP_DEVICE and pass the device into
# the container).
SPARKNET_HTTP_SIMULATE_GATEWAY: "1"
ports:
- "8080:8080"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/v1/healthz >/dev/null 2>&1 || exit 1"]
interval: 5s
timeout: 5s
retries: 5
start_period: 5s
profiles: ["driver-sparknet"]
# Meter driver: an emulator of the meter-driver HTTP+SSE contract, started
# with `--profile driver-emulator`. Register
# it in the ground webapp under Global Settings > Meter Drivers > Register
# driver with the base URL http://meter-driver-emulator:18080. Its image
# carries its own HEALTHCHECK on /v1/healthz. Building it needs the
# meter-driver-spec submodule, which ./clone.sh checks out.
meter-driver-emulator:
container_name: "meter-driver-emulator"
build: './meter-driver-emulator'
image: ghcr.io/earthspark/meter-driver-emulator:latest
ports:
- "18080:18080"
profiles: ["driver-emulator"]
# POSSIBLE IMPROVEMENTS:
# - Healthcheck uses psql which requires a full DB connection; could use
# pg_isready -U spark which is lighter weight
# - Could use POSTGRES_HOST_AUTH_METHOD=trust for local dev (no password needed)
# - Environment uses list format (- POSTGRES_USER=spark); could use map format
# (POSTGRES_USER: spark) for consistency with other services
postgres-ground:
container_name: "postgres-ground"
image: library/postgres:18
environment:
- POSTGRES_USER=spark
- POSTGRES_PASSWORD=spark
- POSTGRES_DB=ground
ports:
- "5440:5432"
healthcheck:
test: ["CMD", "psql", "-U", "spark", "-d", "ground", "-c", "SELECT 1;"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# SymmetricDS node on the ground side, syncing the ground database
# against symds-cloud. Without docker-compose.cloud.yml, sync attempts
# retry until that file brings up symds-cloud.
symds-ground:
container_name: "symds-ground"
build:
context: './symmetricds'
image: ghcr.io/earthspark/symmetricds:latest
environment:
DATABASE_URL: postgresql://spark:spark@postgres-ground/ground
ENGINE_NAME: ground-engine
# GROUP_ID must match `sparkmeter.database.sync.SYNC_GROUP_GROUND`
# (= "ground-group"), which is the source side of the configured
# ground-group → cloud-group node_group_link. Without this, the
# cloud SymmetricDS refuses registration with "Registration was
# not open" because no matching link exists.
GROUP_ID: ground-group
# Local-only defaults assume docker-compose.cloud.yml is running
# side-by-side (symds-cloud + postgres-cloud + cloud webapp). To point
# at a real cloud endpoint, set these in your `.env`:
# SYMDS_PROTOCOL=https
# SYMDS_REGISTRATION_URL=https://<serial>-sync.<rootDomain>/sync/cloud
# SYMDS_EXTERNAL_ID=<serial>
# SYMDS_HTTP_USERNAME=symds
# SYMDS_HTTP_PASSWORD=<...>
EXTERNAL_ID: ${SYMDS_EXTERNAL_ID:-ground}
PROTOCOL: ${SYMDS_PROTOCOL:-http}
REGISTRATION_URL: ${SYMDS_REGISTRATION_URL:-http://symds-cloud:31415/sync/cloud-engine}
SYNC_URL: ${SYMDS_SYNC_URL:-http://symds-ground:31415/sync/ground-engine}
HTTP_USERNAME: ${SYMDS_HTTP_USERNAME:-}
HTTP_PASSWORD: ${SYMDS_HTTP_PASSWORD:-}
depends_on:
postgres-ground:
condition: service_started
networks:
default:
name: sparkapp