Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
155 lines (152 loc) · 9.11 KB
/
Copy pathdocker-compose.yml
File metadata and controls
155 lines (152 loc) · 9.11 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
# One-command bring-up: TimescaleDB + the smoked collector serving the dashboard.
# docker compose up --build -> http://localhost:8087/
#
# Federation (multi-vantage) has two independent parts, both opt-in:
# * the DASHBOARD goes public behind a bundled Caddy reverse proxy under the
# `federation` profile — it terminates TLS (automatic Let's Encrypt) and gates
# the dashboard with HTTP Basic Auth.
# * the AGENT API is smoked's own dedicated mutual-TLS listener, enabled by
# setting SMOKED_AGENT_ADDR/SMOKED_AGENT_HOSTNAME (see the `smoked` service
# below) and published directly — it is NOT proxied by Caddy.
# cp .env.example .env # then set DOMAIN + ACME_EMAIL + DASH_PASSWORD_HASH
# docker compose --profile federation up --build
# The default `docker compose up` above is unchanged and starts no proxy and no
# agent listener (federation stays dark).
services:
timescaledb:
# Pinned to a specific release AND digest, so a rebuild can't silently pull a different
# TimescaleDB (Renovate keeps the digest current — see renovate.json). CODE_REVIEW M4/L7.
image: timescale/timescaledb:2.29.1-pg16@sha256:252a443e2936039b83dd8da1373d01e59e932d1054fa6adf1bc061f1d56ae60a
environment:
POSTGRES_USER: smoke
POSTGRES_PASSWORD: smoke
POSTGRES_DB: smoke
volumes:
- tsdata:/var/lib/postgresql/data
healthcheck:
# Check TCP (-h 127.0.0.1), not the default local socket: during TimescaleDB's
# first-run init a temporary socket-only server is up (listen_addresses='') while
# the real TCP listener is not, so a socket pg_isready reports healthy before
# smoked can actually connect — depends_on:service_healthy would then release smoked
# into a refused connection. The TCP check stays unhealthy until 5432 is really up.
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U smoke -d smoke"]
interval: 5s
timeout: 3s
retries: 10
smoked:
build: .
# Runs as a non-root user (see Dockerfile); NET_RAW stays in the bounding set
# so the setcap'd fping can still send ICMP. The server binds 0.0.0.0:8087
# inside the container — put it behind a reverse proxy / VPN for real exposure
# (the proxy owns TLS + authentication; the bundled Caddy does NOT rate-limit —
# add a rate limiter there or upstream if you need one); the port below is
# published on localhost only.
cap_add:
- NET_RAW # fping ICMP (via fping's file capability, non-root)
sysctls:
# Lets the native Ping probe send ICMP via an unprivileged datagram socket
# (no capability needed) — the socket's GID must fall in this range. 10001 is
# the collector's GID (see Dockerfile: `adduser -u 10001`). FPing instead uses
# CAP_NET_RAW (above); the native Ping probe's raw fallback can't help here
# because the non-root process holds no effective capability, so this range is
# what makes `probe: Ping` work.
#
# Do NOT widen this to "0 2147483647": rootless Podman applies sysctls inside a
# user namespace that maps only the ~65k GIDs from /etc/subgid, so an out-of-range
# upper bound is rejected at container start with EINVAL (Docker rootful is fine).
net.ipv4.ping_group_range: "0 10001"
environment:
# Opt-in mTLS federation agent listener, entirely separate from the dashboard
# HTTP server above. Both empty (the default) means no listener at all — the
# single-host quickstart is unaffected. Set SMOKED_AGENT_ADDR (e.g. :8443) and
# SMOKED_AGENT_HOSTNAME (your public domain; becomes the SAN on smoked's
# self-issued server cert) in .env to enable it — needs SMOKED_DSN (set
# below) or smoked fatals at startup. See docs/federation.md.
SMOKED_AGENT_ADDR: ${SMOKED_AGENT_ADDR:-}
SMOKED_AGENT_HOSTNAME: ${SMOKED_AGENT_HOSTNAME:-}
# Optional: enable the password-gated admin vantage-management API + Vantages
# GUI panel. Empty (the default) leaves it fail-closed/disabled. Set it in
# .env to onboard vantages from the browser instead of the CLI.
SMOKED_ADMIN_PASSWORD: ${SMOKED_ADMIN_PASSWORD:-}
# Independent 32-byte (64 hex character) cookie-signing key. Generate once with
# `openssl rand -hex 32` and keep it stable for sessions to survive restarts.
# Empty is safe but intentionally uses a new key on every collector start.
SMOKED_ADMIN_SESSION_KEY: ${SMOKED_ADMIN_SESSION_KEY:-}
# How long an admin login stays valid (token expiry + cookie Max-Age). Any Go duration,
# e.g. 24h / 168h / 30m (minimum 1m). Empty uses the 12h default.
SMOKED_ADMIN_SESSION_TTL: ${SMOKED_ADMIN_SESSION_TTL:-}
# The image's default command runs `-serve -addr :8087 -webdir /web`; these env vars drive the
# rest (config / DSN / downsample), keeping the DB password out of the command list. Each
# mirrors a -flag on cmd/smoked, and the `vantage`/`import` subcommands already read SMOKED_DSN.
SMOKED_CONFIG: /etc/heliograph/config.yaml # baked demo config (multi-probe targets)
SMOKED_DSN: "postgres://smoke:smoke@timescaledb:5432/smoke?sslmode=disable"
SMOKED_DOWNSAMPLE: "1" # hourly continuous aggregate for the UI's Hourly mode
SMOKED_RESOLVE_IPS: "1" # show each target's IP in the graph title (pinned/literal/resolved)
SMOKED_ABSOLUTE_TIME: ${SMOKED_ABSOLUTE_TIME:-1} # graph x-axes: 1 (default) = absolute clock time; 0 = relative -3h/now (set in .env)
SMOKED_Y_FIT: ${SMOKED_Y_FIT:-peaks} # default graph y-axis fit: peaks | typical (SmokePing-like); viewers can override (set in .env)
ports:
# Published on localhost only: the API is unauthenticated and query-heavy, so it
# must not be reachable on all host interfaces. For access beyond this host, run a
# reverse proxy (TLS + auth; add rate limiting there if you need it) pointed at
# 127.0.0.1:8087 rather than changing this to 0.0.0.0.
- "127.0.0.1:8087:8087"
# smoked's own mTLS agent listener (federation). Only serves traffic once
# SMOKED_AGENT_ADDR is set above; otherwise nothing listens here and this is a
# harmless closed port. Published directly — NOT proxied by Caddy.
# NOTE: this port must match the port in SMOKED_AGENT_ADDR — if you change it from
# the default (e.g. SMOKED_AGENT_ADDR=:9443), update both sides or agents can't connect.
- "8443:8443"
depends_on:
timescaledb:
condition: service_healthy
# Bundled reverse proxy for the DASHBOARD ONLY (opt-in via the `federation`
# profile). Terminates TLS with an auto-issued, auto-renewed Let's Encrypt cert
# for $DOMAIN and gates the dashboard/read/admin API behind HTTP Basic Auth (see
# Caddyfile) — smoked itself never does TLS for the dashboard. Remote agents do
# NOT go through Caddy: they connect straight to smoked's own mTLS listener on
# :8443 (see the `smoked` service above) and authenticate with a CA-signed
# client certificate — no API key involved.
caddy:
profiles: ["federation"]
# Built from Caddy.Dockerfile so DNS-challenge provider plugins are compiled
# in — the stock image has none. Bundles cloudflare, route53, digitalocean,
# duckdns, namecheap, gandi; pick one at runtime via CADDY_ACME_DNS.
build:
context: .
dockerfile: Caddy.Dockerfile
restart: unless-stopped
ports:
- "80:80" # HTTP-01 challenge + HTTP->HTTPS redirect (not needed for DNS-01)
- "443:443" # public dashboard over TLS (agents use smoked's own :8443, not this proxy)
environment:
# From .env (see .env.example). Set DOMAIN=localhost to smoke-test with a
# self-signed cert instead of hitting Let's Encrypt.
DOMAIN: ${DOMAIN}
ACME_EMAIL: ${ACME_EMAIL}
# HTTP Basic Auth for the dashboard/read API (smoked's read API has no auth
# of its own). DASH_PASSWORD_HASH is a bcrypt hash from `caddy hash-password`.
DASH_USER: ${DASH_USER:-admin}
DASH_PASSWORD_HASH: ${DASH_PASSWORD_HASH}
# TLS challenge. Empty = HTTP-01. For DNS-01 set CADDY_ACME_DNS to your
# provider's acme_dns line + its credential var(s); see .env.example.
CADDY_ACME_DNS: ${CADDY_ACME_DNS:-}
# Credentials for the bundled DNS providers (only the one you use matters).
CF_API_TOKEN: ${CF_API_TOKEN:-} # cloudflare
DO_AUTH_TOKEN: ${DO_AUTH_TOKEN:-} # digitalocean
DUCKDNS_API_TOKEN: ${DUCKDNS_API_TOKEN:-} # duckdns
GANDI_BEARER_TOKEN: ${GANDI_BEARER_TOKEN:-} # gandi
NAMECHEAP_API_KEY: ${NAMECHEAP_API_KEY:-} # namecheap
NAMECHEAP_API_USER: ${NAMECHEAP_API_USER:-} # namecheap
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-} # route53
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-} # route53
AWS_REGION: ${AWS_REGION:-} # route53
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data # persists issued certs across restarts (LE rate limits)
- caddy_config:/config
depends_on:
- smoked
volumes:
tsdata: {}
caddy_data: {}
caddy_config: {}