Repository navigation
Expand file tree
/
Copy pathcompose.yml
More file actions
138 lines (132 loc) · 4.91 KB
/
Copy pathcompose.yml
File metadata and controls
138 lines (132 loc) · 4.91 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
# Docker Compose stack for the RPi camera plugin.
#
# Paired mode (default — captures push to the R9lab backend):
# docker compose up -d
#
# Standalone mode (no R9lab backend — captures land in a local RustFS bucket):
# Set APP_BUILD_TARGET=runtime-standalone and COMPOSE_PROFILES=standalone in
# .env, then ``docker compose up -d``. The standalone build target adds
# aioboto3; configure IMAGE_SINK=s3 and S3_* vars alongside it.
#
# With remote observability (Loki logs + optional Prometheus metrics):
# Add ``observability-ship`` to COMPOSE_PROFILES, e.g.
# ``COMPOSE_PROFILES=standalone,observability-ship``.
x-runtime-hardening: &runtime-hardening
tmpfs:
- /tmp
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
logging:
driver: local
options:
max-size: "10m"
max-file: "3"
services:
app:
<<: *runtime-hardening
image: r9cam
build:
context: .
# Override to ``runtime-standalone`` in .env for standalone mode.
target: ${APP_BUILD_TARGET:-runtime}
restart: unless-stopped
depends_on:
mediamtx:
condition: service_healthy
volumes:
- /run/udev:/run/udev:ro
- app_data:/app/data
- app_logs:/app/logs
- relay_credentials:/home/rpicam/.config/r9cam
env_file:
- path: .env
required: false
# Host networking lets the app reach MediaMTX (RTSP :8554, HLS :8888,
# control API :9997) over loopback, keeping those listeners localhost-only
# without any ``host.docker.internal`` bridge gymnastics. Port 8018 is bound
# directly on the host — equivalent to the previous ports mapping.
network_mode: host
mediamtx:
<<: *runtime-hardening
# MediaMTX sidecar: the single media seam for the plugin.
#
# - Ingest: RTSP on :8554 (lores cam-preview always, main cam-hires while
# a YouTube stream is active).
# - Browser/native preview: HLS on :8888.
# - Runtime control: HTTP API on localhost:9997. The plugin's
# ``mediamtx_client`` patches the ``cam-hires`` path at runtime with a
# ``runOnReady`` command that forks ffmpeg inside this container to
# re-publish the stream to YouTube RTMPS.
#
# ``-ffmpeg`` variant is required because ``runOnReady`` shells out to
# ffmpeg and the default image doesn't ship it. Runs on the host network so
# all three listeners (8554/8888/9997) are reachable from the app container.
image: bluenviron/mediamtx:1.21.2-ffmpeg@sha256:c0feb4acc548d5f410d57d9046f62dfa355e2abc4098f5243de9e86430c77a95
restart: unless-stopped
volumes:
- ./mediamtx.yml:/mediamtx.yml:ro
# Hit the control API on localhost:9997 with the busybox wget the upstream
# image already ships.
healthcheck:
test: [ "CMD-SHELL", "wget -q -O /dev/null http://localhost:9997/v3/config/global/get || exit 1" ]
interval: 30s
timeout: 3s
retries: 3
start_period: 5s
network_mode: host
alloy:
<<: *runtime-hardening
image: grafana/alloy:v1.20.1@sha256:2aa2099af76c0098d4af7a4d6e48f86cb66dc1a000222ad927a1c67c6542d13f
restart: unless-stopped
volumes:
- ./config.alloy:/etc/alloy/config.alloy:ro
- alloy_data:/var/lib/alloy/data
- app_logs:/var/log/app:ro
environment:
LOKI_PUSH_URL: ${LOKI_PUSH_URL:-}
PROMETHEUS_REMOTE_WRITE_URL: ${PROMETHEUS_REMOTE_WRITE_URL:-}
PROMETHEUS_SCRAPE_API_KEY: ${PROMETHEUS_SCRAPE_API_KEY:-}
OBSERVABILITY_INSTANCE: ${OBSERVABILITY_INSTANCE:-rpi-cam-plugin}
command:
- run
- --storage.path=/var/lib/alloy/data
- /etc/alloy/config.alloy
profiles:
- observability-ship
rustfs:
<<: *runtime-hardening
# S3-compatible object storage for standalone mode. The sidecar publishes
# loopback-only ports by default; set RUSTFS_*_BIND only when exposing it.
# See the ``standalone`` section of ``.env.example`` for the required vars.
image: rustfs/rustfs:1.0.0-beta.1@sha256:3c2d55977829620284ece8593901bf776bcfc0fc9972784352de4dcffdb92416
restart: unless-stopped
ports:
- "${RUSTFS_API_BIND:-127.0.0.1}:9000:9000" # S3 API
- "${RUSTFS_CONSOLE_BIND:-127.0.0.1}:9001:9001" # Web console
volumes:
- rustfs_data:/data
environment:
RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY:-rustfsadmin}
# RustFS refuses to start with an unset / short secret key, so the
# empty default here fails loudly only when the standalone profile is
# actually activated — paired-mode users don't need to set it.
RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY:-}
RUSTFS_ADDRESS: 0.0.0.0:9000
RUSTFS_CONSOLE_ADDRESS: 0.0.0.0:9001
healthcheck:
test: [ "CMD-SHELL", "curl -sf http://localhost:9000/health || exit 1" ]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
profiles:
- standalone
volumes:
app_data:
app_logs:
relay_credentials:
alloy_data:
rustfs_data: