Skip to content

Deployment

The system is fully containerised. Docker Compose brings up Redis, two navigation collectors, two dive detectors, the LiveLink Slack notifier, the SvelteKit dashboard, a Caddy TLS-terminating reverse proxy, and two coax video bridges — ten services in total — with a single command.

Prerequisites

  • Docker Engine 24+ and Docker Compose v2
  • Network access to:
  • NATS servers: coredata-rcsn.rc.mbari.org:4222 and coredata-dpkd.dp.mbari.org:4222
  • OBS WebSocket servers: rc-teleops.rc.mbari.org:4455 and dp-teleops.rc.mbari.org:4455
  • The HLS origin the coax bridges pull from (teledistributor.shore.mbari.org:5080) and the downstream transcoder/modulator hardware they send UDP to (134.89.5.12, ports 2345/2346)
  • Each OBS instance must have scenes named MainOnly and FeedOffline defined before the detector is started.
  • The Docker host running rc-coax-bridge/dp-coax-bridge must have an actual network route to 134.89.5.12 — these two services use network_mode: host, so they reach the modulator hardware over the host's own network stack rather than through Compose's redisnet bridge.

Starting all services

docker compose up -d --build

This starts ten containers:

Compose service Container name Description
redis redis Shared data cache
rc-navproc_collector navproc_collector_rcsn NATS → Redis for Rachel Carson
dp-navproc_collector navproc_collector_dpkd NATS → Redis for David Packard
rc-dive-detector dive_detector_rcsn Dive detection for Rachel Carson
dp-dive-detector dive_detector_dpkd Dive detection for David Packard
livelink_slackbot livelink_slackbot Posts Redis state changes to a Slack workflow webhook
frontend dive_detector_frontend SvelteKit dashboard (HTTP on internal port 3000)
caddy dive_detector_caddy TLS-terminating reverse proxy on ports 80/443
rc-coax-bridge rc-coax-bridge HLS → UDP coax video bridge for Rachel Carson
dp-coax-bridge dp-coax-bridge HLS → UDP coax video bridge for David Packard

Unlike the other eight services, the two coax bridges run with network_mode: host and don't join redisnet — they don't talk to Redis or anything else in the stack, only to the HLS origin and the modulator hardware. See Coax Video for the pipeline details.

The dashboard is then available at https://<configured-hostname> once you've placed the cert files (see Enabling TLS below). Until then the caddy container will fail to start because the cert files don't exist.

Viewing logs

# All services
docker compose logs -f

# A single service
docker compose logs -f rc-dive-detector
docker compose logs -f frontend
docker compose logs -f rc-coax-bridge

Stopping

docker compose down

Rebuilding images

After a code change in a single service, rebuild just that one:

docker compose up -d --build frontend
docker compose up -d --build rc-dive-detector dp-dive-detector
docker compose up -d --build rc-coax-bridge dp-coax-bridge

To rebuild everything:

docker compose build
docker compose up -d

Service configuration

Environment variables live in compose.yaml. See Configuration for the full reference. Key per-service variables:

Variable Example Description
SHIP_NAME rcsn Ship identifier (NATS bucket name)
SHIP_DOMAIN rc NATS domain suffix
REDIS_HOST redis Redis hostname (Docker service name)
POLL_INTERVAL 10 Seconds between data pulls

The collector's NATS key allow-list is in backend/collector_keys.toml — edits there require a collector rebuild.

dive_detector

Variable Example Description
SHIP_NAME rcsn Ship identifier (Redis key prefix)
OBS_HOST rc-teleops.rc.mbari.org OBS WebSocket server hostname
OBS_PORT 4455 OBS WebSocket port
OBS_PASSWORD (secret) OBS WebSocket password, empty if no auth
REDIS_HOST redis Redis hostname
POLL_INTERVAL 10 Seconds between Redis polls

frontend

Variable Example Description
REDIS_HOST redis Redis hostname
REDIS_PORT 6379 Redis port
NODE_ENV production Node environment

coax_bridge

Variable Example Description
HLS_URL http://teledistributor.shore.mbari.org:5080/LiveLink/streams/rvrachelcarson.m3u8 HLS playlist to pull (required)
UDP_HOST 134.89.5.12 Transcoder/modulator hardware IP (required)
UDP_PORT 2346 UDP destination port — 2345 for David Packard, 2346 for Rachel Carson
ALIGNMENT 7 MPEG-TS packets per UDP payload

No REDIS_HOST here — this service doesn't touch Redis. See Coax Video for what the pipeline does with these variables.

Dockerfiles

docker/dive_detector.dockerfile

Builds the image for both dive-detector services. Based on ghcr.io/astral-sh/uv:python3.13-trixie-slim. Dependencies are installed from the lockfile with uv sync.

docker/navproc_collector.dockerfile

Builds the image for both navproc-collector services. Same base image and dependency approach. The ADD /backend /app step automatically includes collector_keys.toml in the image.

docker/frontend.dockerfile

Multi-stage Node 22 build. Stage 1 installs all deps and runs npm run build to produce the SvelteKit Node bundle. Stage 2 contains only production dependencies plus the build output, and runs node build.

docker/coax_bridge.dockerfile

Just the restreamio/gstreamer base image (it already bundles the GStreamer plugins the pipeline needs) plus coax_bridge/entrypoint.sh copied in as the entrypoint — no dependency install step, since there's no application code beyond the shell script.

Running a single ship only

To run only the R/V Rachel Carson services plus the dashboard:

docker compose up -d redis rc-navproc_collector rc-dive-detector frontend

Add rc-coax-bridge to that list if you also want the coax feed for that ship running:

docker compose up -d redis rc-navproc_collector rc-dive-detector frontend rc-coax-bridge

Note rc-coax-bridge has no depends_on — it only needs the HLS origin to be live, not Redis or any other Dive Detector service, so it can be started and stopped independently of everything else.

Enabling TLS

The compose stack includes a Caddy reverse proxy in front of the SvelteKit container. Caddy terminates TLS on ports 80/443 and proxies plain HTTP requests to frontend:3000 over the internal redisnet network — the SvelteKit container is no longer exposed externally.

To turn it on with an MBARI-issued cert:

  1. Drop cert files into certs/. The Caddyfile expects certs/dashboard.crt (full-chain PEM) and certs/dashboard.key (PEM-encoded private key). See certs/README.md. Both files are gitignored.

  2. Set the hostname. Edit Caddyfile and replace dashboard.internal.mbari.org with the hostname this server answers to. Make sure DNS resolves that hostname to the docker host.

  3. Match ORIGIN to the hostname. Edit the frontend service in compose.yaml:

environment:
  - ORIGIN=https://dashboard.internal.mbari.org

This tells SvelteKit that incoming requests should be treated as HTTPS (so the session cookie picks up the Secure flag and links resolve correctly), even though the request to the SvelteKit container itself arrives over HTTP from Caddy.

  1. Bring it up.
docker compose up -d --build frontend caddy

Caddy will reload the Caddyfile on container restart. Logs land in docker compose logs -f caddy.

  1. (Optional) Re-enable SvelteKit's CSRF origin check. With ORIGIN set, the check that we disabled earlier in svelte.config.js will pass cleanly. Flip kit.csrf.checkOrigin back to true if you want that extra layer.

Renewing or rotating certs

Drop the new cert/key files into certs/ (same filenames) and:

docker compose restart caddy

Caddy picks them up without rebuilding.

Local debug without TLS

If you need direct HTTP access to the SvelteKit container (e.g. for troubleshooting), uncomment the port mapping under the frontend service in compose.yaml:

ports:
  - "3000:3000"

Re-comment it out when you're done so the only public surface is the Caddy-terminated HTTPS port.

Checking Redis data

Use the Redis dump tools to verify the navigation collector is populating data. Run them on the host with uv installed:

# Static snapshot of all keys under rcsn:
uv run backend/redis_dump.py rcsn

# Live auto-refreshing TUI (terminal mode)
uv run backend/redis_dump_textual.py rcsn

# Or have it serve a browser-friendly TUI
uv run backend/redis_dump_textual.py rcsn --web --host 0.0.0.0 --port 8000

If Redis isn't exposed on the host, exec into one of the running containers — they all have redis-cli-like access via Python:

docker compose exec rc-dive-detector uv run backend/redis_dump.py rcsn

See Redis Tools for more options.