Running an Azimuth Node in Docker
This guide covers running the Azimuth Tier 1 SDR node daemon as a container. You can either load a prebuilt image tarball or build from source. Grab the image tarballs from the downloads page. Maintained by the Azimuth team.
1. Get the image
Option A — load a released tarball (fastest)
# amd64 (x86_64)
curl -LO https://azimuth.day/downloads/azimuth-node-docker-amd64.tar.gz
docker load < azimuth-node-docker-amd64.tar.gz
# arm64 (aarch64), if published
curl -LO https://azimuth.day/downloads/azimuth-node-docker-arm64.tar.gz
docker load < azimuth-node-docker-arm64.tar.gz
Verify the download against the published azimuth-node-docker-sha256.txt:
sha256sum -c azimuth-node-docker-sha256.txt
Option B — build from source
git clone https://github.com/Azimuth-Official/azimuth-daemon.git
cd azimuth-daemon
docker build -t azimuth-node:latest .
2. Run with Docker Compose
Edit the AZIMUTH_* variables in docker-compose.yml, then:
docker compose up -d
docker compose logs -f
Check health:
curl http://127.0.0.1:8420/status
3. Configuration (environment variables)
On first boot the entrypoint generates /var/lib/azimuth/config.toml from the
variables below and then persists it (later boots reuse the saved file — the daemon also
writes its assigned node.id back to it). To regenerate, delete the file or the volume
and restart. To use the full config (including the advanced [scanning], [ntrip], and
[gps] sections), mount your own config.toml at AZIMUTH_CONFIG_PATH instead.
| Variable | Maps to | Default | Notes |
|----------|---------|---------|-------|
| AZIMUTH_API_URL | api.url | https://azimuth.day | Azimuth backend base URL |
| AZIMUTH_API_KEY | api.key | (empty) | Required to register and earn; from the azimuth.day dashboard |
| AZIMUTH_NODE_LABEL | node.label | azimuth-docker-node | Human-readable node name |
| AZIMUTH_HARDWARE_TYPE | node.hardware_type | tier1_rtlsdr | tier0_mobile / tier1_rtlsdr / tier2_gpsdo |
| AZIMUTH_NODE_LAT | node.location.latitude | 0.0 | Set your real latitude |
| AZIMUTH_NODE_LON | node.location.longitude | 0.0 | Set your real longitude |
| AZIMUTH_NODE_ALT | node.location.altitude_m | 0.0 | Altitude in meters |
| AZIMUTH_STATUS_BIND | daemon.status_bind | 0.0.0.0:8420 | Status server bind (keep 0.0.0.0 in a container) |
| AZIMUTH_HEARTBEAT_SECS | daemon.heartbeat_interval_secs | 15 | |
| AZIMUTH_SUBMIT_SECS | daemon.submit_interval_secs | 30 | |
| AZIMUTH_SDR_SAMPLE_RATE | sdr.sample_rate | 2048000 | Hz |
| AZIMUTH_SDR_GAIN | sdr.gain | -1 | -1 = automatic gain control |
| AZIMUTH_CONFIG_PATH | (config file location) | /var/lib/azimuth/config.toml | Where the config is written/read |
4. USB / RTL-SDR passthrough (real hardware)
The container reaches the RTL-SDR through /dev/bus/usb; the compose file maps it:
devices:
- "/dev/bus/usb:/dev/bus/usb"
Because the container runs as the non-root azimuth user (uid 10001), that user must be
in the host's USB device group. The group name plugdev is unreliable across the
host/container boundary (the numeric gid rarely matches), so use one of:
- Numeric gid (recommended) — find the host gid and pass it:
getent group plugdev # e.g. -> plugdev:x:46: ls -n /dev/bus/usb/001/ # the numeric group column is the device's gidgroup_add: - "46" # the numeric gid from above - Run as root — add
user: "0:0"to the service. - Privileged — add
privileged: true(broadest access, least isolation).
Without hardware the daemon runs in stub mode: /status reports
sdr_connected: false, device_count: 0, and the daemon does not crash — useful for
verifying the container itself.
5. State & identity persistence
/var/lib/azimuth holds node-identity.json and the generated config.toml. Keep it on
a named volume (the compose file does) so the node keeps its identity across restarts and
does not re-register.
6. Health endpoint
GET /status returns JSON with SDR connection state, device count, uptime, signal counts,
and any config_error. The image's HEALTHCHECK polls it.
Security:
/statusis unauthenticated.docker-compose.ymlbinds it to127.0.0.1on the host by default. Only expose it publicly behind an authenticating reverse proxy.