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
; Remora Monitoring Stack — compose.reml
;
; Architecture:
;
; monitoring-net (10.89.1.0/24): prometheus ←→ grafana
; loki ←→ grafana
;
; Lisp features demonstrated:
;
; define — all ports and resource limits named at the top
; env — GRAFANA_PASSWORD read from host with safe default
; on-ready — hooks fire when prometheus and loki pass TCP checks
; depends-on — grafana waits for both prometheus:9090 and loki:3100
; dotted pairs — :env ("KEY" . value) where value can be a variable
; define-service — macro: flat keyword-style service definitions
;; ── Configuration ─────────────────────────────────────────────────
;;
;; Override the Grafana admin password without editing this file:
;; GRAFANA_PASSWORD=secret sudo remora compose up -f compose.reml -p monitoring
(define grafana-password
(let ((p (env "GRAFANA_PASSWORD")))
(if (null? p) "admin" p)))
(define port-prometheus 9090)
(define port-grafana 3000)
(define port-loki 3100)
(define mem-prometheus "256m")
(define mem-grafana "256m")
(define mem-loki "128m")
;; ── Network ───────────────────────────────────────────────────────
(define net-monitoring (network "monitoring-net" '(subnet "10.89.1.0/24")))
;; ── Lifecycle hooks ───────────────────────────────────────────────
;;
;; on-ready fires once the TCP port check passes, guaranteeing that
;; Grafana's datasource queries can connect on the first attempt.
(on-ready "prometheus"
(lambda ()
(log "prometheus: metrics backend ready — Grafana can scrape")))
(on-ready "loki"
(lambda ()
(log "loki: log aggregation ready — Grafana can query")))
;; ── Services ──────────────────────────────────────────────────────
;;
;; Flat keyword-value list. Each :keyword introduces a new option.
;; :env accepts dotted pairs ("KEY" . value) — value is evaluated at
;; call-site, so variables like grafana-password resolve correctly.
(define-service svc-prometheus "prometheus"
:image "monitoring-prometheus:latest"
:network "monitoring-net"
:port (port-prometheus . 9090)
:memory mem-prometheus)
(define-service svc-loki "loki"
:image "monitoring-loki:latest"
:network "monitoring-net"
:port (port-loki . 3100)
:memory mem-loki)
(define-service svc-grafana "grafana"
:image "monitoring-grafana:latest"
:network "monitoring-net"
:depends-on "prometheus" port-prometheus ; waits for metrics backend
:depends-on "loki" port-loki ; waits for log backend
:env ("GF_SECURITY_ADMIN_PASSWORD" . grafana-password)
("GF_USERS_ALLOW_SIGN_UP" . "false")
("GF_AUTH_ANONYMOUS_ENABLED" . "false")
("GF_PATHS_PROVISIONING" . "/etc/grafana/provisioning")
:port (port-grafana . 3000)
:memory mem-grafana)
;; ── Assemble and run ──────────────────────────────────────────────
(compose-up
(compose
net-monitoring
(volume "prometheus-data")
(volume "grafana-data")
svc-prometheus
svc-loki
svc-grafana))