Unofficial community image for readsb, built with LinuxServer.io style container patterns (s6, hardened defaults, practical runtime options) for RTL-SDR ADS-B workloads. Sponsored and maintained by Blackout Secure.
Important
This repository is not an official LinuxServer.io image release. Want to help make it an officially supported LinuxServer.io Community image? Add your support in linuxserver/discussions/108.
Links: Docker Hub · Balena block · GitHub · Upstream readsb
- Quick Start
- Image Availability
- About The readsb Application
- Supported Architectures
- Usage
- Parameters
- Configuration
- Application Setup
- SDR Device Selection
- Feed Profiles
- Gain Control
- Bias-T Power for Active Antennas
- Advanced Tuning
- Troubleshooting
- Health Monitoring
- Release & Versioning
- Resources
- License
5-minute RTL-SDR receiver setup:
docker run -d \
--name=readsb \
--restart unless-stopped \
-e TZ=Etc/UTC \
-e READSB_DEVICE_TYPE=rtlsdr \
-p 30001:30001 \
-p 30002:30002 \
-p 30003:30003 \
-p 30004:30004 \
-p 30005:30005 \
-p 30104:30104 \
-v readsb-config:/config \
-v readsb-json:/run/readsb \
--device=/dev/bus/usb:/dev/bus/usb \
--security-opt no-new-privileges:true \
blackoutsecure/readsb:latestAccess live JSON output: docker exec readsb cat /run/readsb/aircraft.json | jq .
For compose files, balena, network-only mode, and more examples, see Usage below.
Docker Hub (Recommended):
- All images published to Docker Hub
- Simple pull command:
docker pull blackoutsecure/readsb:latest - Multi-arch support: amd64, arm64
- No registry prefix needed (defaults to Docker Hub)
# Pull latest
docker pull blackoutsecure/readsb
# Pull specific version
docker pull blackoutsecure/readsb:1.2.3
# Pull architecture-specific (rarely needed)
docker pull blackoutsecure/readsb:latest@amd64readsb is an ADS-B decoder often described upstream as an "ADS-B decoder swiss knife".
It is a detached fork lineage used by many ADS-B receivers and related tooling, with network outputs, JSON/API features, and broad SDR support used for local receivers and large-scale feed/aggregation workflows.
Author and maintenance credits (upstream):
- Primary upstream maintainer: wiedehopf (Matthias Wirth)
- Upstream credits/history lineage: antirez (original dump1090), Malcom Robb, mutability (dump1090-mutability / dump1090-fa), Mictronics (readsb fork), and wiedehopf (current fork)
- Upstream repository and documentation: wiedehopf/readsb
This image is published as a multi-arch manifest. Pulling blackoutsecure/readsb:latest retrieves the correct image for your host architecture.
The architectures supported by this image are:
| Architecture | Tag |
|---|---|
| x86-64 | amd64-latest |
| arm64 | arm64v8-latest |
docker-compose (recommended, click here for more info)
---
services:
readsb:
image: blackoutsecure/readsb:latest
container_name: readsb
environment:
- TZ=Etc/UTC
- READSB_DEVICE_TYPE=rtlsdr
- READSB_NET_BI_PORT=30004,30104 # Beast input ports
- READSB_NET_BO_PORT=30005 # Beast output port
# - READSB_BIASTEE=true # enable for powered LNAs (e.g. SAWbird+)
- LOG_LEVEL=info # debug | info | warn | error | fatal
volumes:
- /path/to/readsb/config:/config
- /path/to/readsb/json:/run/readsb
ports:
- 30001:30001 # Raw protocol (TCP)
- 30002:30002 # Raw protocol input (TCP)
- 30003:30003 # SBS protocol (TCP)
- 30004:30004 # Beast input (TCP)
- 30005:30005 # Beast output (TCP)
- 30104:30104 # Beast input (TCP)
devices:
- /dev/bus/usb:/dev/bus/usb
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
- /run:exec
restart: unless-stopped---
services:
readsb:
image: blackoutsecure/readsb:latest
container_name: readsb
environment:
- TZ=Etc/UTC
- READSB_EXTRA_ARGS=--lon <longitude> --lat <latitude>
volumes:
- /path/to/readsb/config:/config
- /path/to/readsb/json:/run/readsb
ports:
- 30001:30001
- 30002:30002
- 30003:30003
- 30004:30004
- 30005:30005
- 30104:30104
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
- /run:exec
restart: unless-stopped---
services:
readsb:
image: blackoutsecure/readsb:latest
container_name: readsb
environment:
- TZ=Etc/UTC
- READSB_DEVICE_TYPE=rtlsdr
# - READSB_BIASTEE=true # enable for powered LNAs (e.g. SAWbird+)
- FEED_UAT_INPUT=dump978:30978
# - READSB_DEVICE=00001090 # pin 1090 dongle by serial
volumes:
- readsb-config:/config
- readsb-json:/run/readsb
devices:
- /dev/bus/usb:/dev/bus/usb
ports:
- 30001:30001
- 30002:30002
- 30003:30003
- 30004:30004
- 30005:30005
- 30104:30104
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
- /run:exec
restart: unless-stopped
depends_on:
- dump978
dump978:
image: blackoutsecure/dump978:latest
container_name: dump978
environment:
- TZ=Etc/UTC
- DUMP978_SDR=driver=rtlsdr,serial=00000978
- DUMP978_PROFILE=adsbexchange
volumes:
- dump978-config:/config
- dump978-run:/run/dump978-fa
ports:
- 8978:8978
- 30978:30978
- 30979:30979
devices:
- /dev/bus/usb:/dev/bus/usb
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
- /run:exec
restart: unless-stopped
volumes:
readsb-config:
readsb-json:
dump978-config:
dump978-run:Note: UAT 978 MHz is US-only (below 18,000 ft). Tag your dongles with
rtl_eeprom -d 0 -s 00001090andrtl_eeprom -d 1 -s 00000978. See blackoutsecure/docker-dump978 for full dump978 documentation.
docker-cli (click here for more info)
docker run -d \
--name=readsb \
-e TZ=Etc/UTC \
-e READSB_DEVICE_TYPE=rtlsdr \
-p 30001:30001 \
-p 30002:30002 \
-p 30003:30003 \
-p 30004:30004 \
-p 30005:30005 \
-p 30104:30104 \
-v /path/to/readsb/config:/config \
-v /path/to/readsb/json:/run/readsb \
--device=/dev/bus/usb:/dev/bus/usb \
--security-opt no-new-privileges:true \
--restart unless-stopped \
blackoutsecure/readsb:latestThis image can be deployed to Balena-powered IoT devices using the included docker-compose.yml file (which contains the required Balena labels):
- Balena block listing: https://hub.balena.io/blocks/2351129/readsb
balena push <your-app-slug>For deployment via the web interface, use the deploy button in this repository. See Balena documentation for details.
| Parameter | Function |
|---|---|
-p 30001:30001 |
Raw protocol output (TCP) |
-p 30002:30002 |
Raw protocol input (TCP) |
-p 30003:30003 |
SBS protocol compatible output (TCP) |
-p 30004:30004 |
Beast protocol input (TCP) |
-p 30005:30005 |
Beast protocol output (TCP) |
-p 30104:30104 |
Beast protocol input (TCP) |
| Parameter | Function | Required |
|---|---|---|
-e TZ=Etc/UTC |
Timezone (TZ database) | Optional |
-e READSB_NET=true |
Enable TCP networking (Beast, raw, SBS listeners). Default: true. Set to false for decode-only mode. |
Optional |
-e READSB_DEVICE_TYPE=rtlsdr |
SDR device type (rtlsdr, modesbeast, etc.). Leave empty for network-only mode. Maps to --device-type. |
Optional |
-e READSB_EXTRA_ARGS= |
Additional readsb arguments appended after all env-var-driven options (e.g. --freq-correction 3). |
Optional |
-e READSB_USER=abc |
Runtime user (default: abc). USB permissions are fixed automatically during init. |
Optional |
-e PUID=1000 |
User ID for file ownership (LinuxServer.io base image standard) | Optional |
-e PGID=1000 |
Group ID for file ownership (LinuxServer.io base image standard) | Optional |
-e READSB_NET_BI_PORT= |
Beast protocol input port(s), comma-separated (e.g. 30004,30104). Maps to --net-bi-port. |
Optional |
-e READSB_NET_BO_PORT= |
Beast protocol output port(s), comma-separated (e.g. 30005). Maps to --net-bo-port. |
Optional |
-e READSB_DEVICE= |
RTL-SDR device index or serial for 1090 MHz (overrides auto-detection) | Optional |
-e FEED_PROFILES= |
Comma-separated feed exchanges (e.g. adsbexchange,adsb-fi). Defaults to adsbexchange if unset. |
Optional |
-e FEED_UUID_ADSBEXCHANGE= |
Per-profile UUID override. Use uppercase profile name with hyphens as underscores (e.g. FEED_UUID_ADSB_FI, FEED_UUID_AIRPLANESLIVE). Stored in /config/feed-uuid-<profile>. |
Optional |
-e FEED_STATS_ENABLED=true |
Enable periodic console stats logging and ADSBx RSSI/stats upload. Console stats log aircraft count, positions, and message totals. ADSBx upload activates automatically when adsbexchange is in FEED_PROFILES, using the same UUID as the beast feed. |
Optional |
-e STATS_LOG_INTERVAL=120 |
Console stats logging interval in seconds (default: 120 = every 2 minutes) |
Optional |
-e FEED_UAT_INPUT= |
UAT 978 MHz source as host:port (e.g. dump978:30978). Requires docker-dump978 sidecar. US only. |
Optional |
-e FEED_LAT= |
Receiver latitude (e.g. 47.6062). Fallback if --lat not in READSB_EXTRA_ARGS. |
Optional |
-e FEED_LON= |
Receiver longitude (e.g. -122.3321). Fallback if --lon not in READSB_EXTRA_ARGS. |
Optional |
-e READSB_AUTO_LOCATION=true |
Auto-detect latitude/longitude via IP geolocation when FEED_LAT/FEED_LON not set. |
Optional |
-e READSB_AUTOGAIN=true |
Enable readsb built-in autogain (--gain auto). Default: true. Set to false to disable. |
Optional |
-e READSB_GAIN= |
Set a fixed RTL-SDR gain value (e.g. 40.2). Overrides READSB_AUTOGAIN. |
Optional |
-e READSB_BIASTEE=false |
Enable bias-T DC voltage on the RTL-SDR coax to power active antennas/LNAs (default: false). Only enable if your antenna requires DC power. |
Optional |
-e LOG_LEVEL=info |
Minimum log verbosity: debug, info (default), warn, error, fatal. Set to debug for verbose operational detail, or warn to suppress routine informational messages. |
Optional |
| Parameter | Function | Required |
|---|---|---|
-v /config |
Configuration and persistent data | Recommended |
-v /run/readsb |
JSON output directory | Recommended |
| Parameter | Function | Required |
|---|---|---|
--device=/dev/bus/usb:/dev/bus/usb |
RTL-SDR USB device access | Required (for RTL-SDR local RX) |
The container uses two volumes for data persistence and output:
- Required: No (container runs without it, but state is lost on restart)
- Purpose: Stores persistent data including aircraft database cache and application state
- Contents:
aircraft.csv.gz(aircraft identification database)- Temporary caches and application state
- Any custom configuration files
- Example:
-v /path/to/readsb/config:/configor-v readsb-config:/config
- Required: No (but needed to access real-time ADS-B data)
- Purpose: Real-time JSON protocol output consumed by other applications and visualization tools
- Contents:
aircraft.json(current aircraft with positions and altitudes)receiver.json(receiver stats and information)- Other protocol outputs
- Update frequency: Multiple times per second
- Example:
-v /path/to/readsb/json:/run/readsbor-v readsb-json:/run/readsb
- For persistence: Always use named volumes or host paths for
/configto preserve aircraft database between container restarts - For JSON access: Mount
/run/readsbto a host path to query data from other containers or host processes - Alternative (tmpfs): Can use
tmpfsmounts for temp directories, but JSON output will be lost when container stops
Named volumes (recommended for single-host deployments):
volumes:
- config:/config
- json:/run/readsbHost paths (for direct file access):
volumes:
- /var/lib/readsb/config:/config
- /var/lib/readsb/json:/run/readsbAccessing JSON data from host:
docker exec readsb cat /run/readsb/aircraft.json | jq '.aircraft | length'Environment variables are set using -e flags in docker run or the environment: section in docker-compose.
By default, this container runs as the LSIO abc user (non-root) for better security isolation. The abc user is created by the LinuxServer.io base image with UID/GID 911 and remapped at container start via PUID/PGID.
Non-root mode (default, recommended):
- readsb runs as the
abcuser by default - RTL-SDR USB device permissions are automatically fixed during container init — no manual configuration needed
- Set
PUIDandPGIDonly if you need file ownership to match a specific host user
Root mode (fallback):
- Set
READSB_USER=rootif needed for other reasons - USB permissions are handled automatically regardless of user
The container runs readsb with network support and automatic RTL-SDR device detection by default.
- JSON Output: ADS-B data is output as JSON to
/run/readsb/and updated frequently - RTL-SDR Support: USB devices are auto-detected when passed to the container; permissions are fixed automatically
- Aircraft Database: Includes tar1090 aircraft database for accurate identification
- Automatic Gain: readsb's built-in autogain (
--gain auto) is enabled by default (READSB_AUTOGAIN=true) — adjusts gain at the IQ sample level every ~0.5s, in-process without restarts. SetREADSB_AUTOGAIN=falseto disable. - Bias-T Power: Optional DC voltage on coax for active antennas with built-in LNAs (see Bias-T Power for Active Antennas)
- Docker HEALTHCHECK: Built-in health monitoring — marks container unhealthy if
aircraft.jsonstops updating - Periodic Stats: Logs aircraft count, positions, and message totals every 2 minutes (configurable via
STATS_LOG_INTERVAL). Whenadsbexchangeis inFEED_PROFILES, also uploads RSSI/stats data to ADSBx every 5 seconds using the same UUID as the beast feed. Per-upload confirmations aredebug-level. - Log Verbosity: Set
LOG_LEVELto control log output:debug,info(default),warn,error,fatal - Feed Status URLs: Init logs include verification URLs for each active feed profile
- RTL-SDR Tools:
rtl_test,rtl_eeprom, andrtl_biastavailable inside the container for diagnostics, dongle tagging, and bias-T control
Most readsb settings have dedicated READSB_* env vars (READSB_NET, READSB_DEVICE_TYPE, READSB_GAIN, READSB_AUTOGAIN, READSB_BIASTEE, READSB_NET_BI_PORT, etc.). Use READSB_EXTRA_ARGS for anything not covered by a dedicated variable — these are appended to the end of the command line after all env-var-driven options.
Examples:
# Frequency correction
-e READSB_EXTRA_ARGS="--freq-correction 10"
# Location and range
-e READSB_EXTRA_ARGS="--lat 51.5 --lon -0.1 --max-range 350"
# See autogain decisions in the log
-e READSB_EXTRA_ARGS="--gain auto-verbose"
# Multiple extra options
-e READSB_EXTRA_ARGS="--freq-correction 3 --max-range 300 --ppm 2"For all available readsb options, see the readsb documentation.
aircraft.json- Current aircraft data with positions, callsigns, and altitudesreceiver.json- Statistics and receiver information
- Read-only filesystem: Supported when JSON and temp directories are mounted to volumes or tmpfs
- Non-root user: Supported by default — RTL-SDR USB permissions are fixed automatically during init
The container auto-detects RTL-SDR dongles at startup and assigns the 1090 MHz device automatically using the community serial-number convention.
Auto-detection (default):
- Single dongle: assumed to be 1090 MHz
- Multiple dongles: serial containing
978oruatis identified as UAT (informational); first non-UAT dongle is assigned as 1090
Manual override:
Set READSB_DEVICE to a device index or serial number:
environment:
- READSB_DEVICE=0 # by device index
- READSB_DEVICE=00001090 # by serial numberUAT 978 MHz (US only):
readsb only decodes 1090 MHz. For 978 MHz UAT, run blackoutsecure/docker-dump978 as a sidecar container and set FEED_UAT_INPUT=dump978:30978 to merge UAT aircraft into readsb's unified output. See the dump978 sidecar compose example above.
Tagging dongle serials:
rtl_eeprom -d 0 -s 00001090 # tag first dongle as 1090
rtl_eeprom -d 1 -s 00000978 # tag second dongle as UATFEED_PROFILES controls data forwarding to ADS-B data aggregators directly from the readsb container. It defaults to adsbexchange if unset. No separate feed container is needed — readsb forwards data using --net-connector arguments appended automatically per profile. Set FEED_PROFILES to an empty string to disable all feeding. MLAT positioning requires a separate mlat-client container per exchange (see MLAT table below).
| Variable | Default | Description |
|---|---|---|
FEED_PROFILES |
adsbexchange |
Comma-separated list of exchanges to feed. Options: adsbexchange, adsb-fi, airplaneslive, planewatch, opensky, flyitalyadsb, adsbhub, radarplane. Defaults to adsbexchange if unset. Set to empty string to disable feeding. |
FEED_UUID_<PROFILE> |
(auto-generated) | Per-profile UUID. Use uppercase profile name with hyphens as underscores (e.g. FEED_UUID_ADSBEXCHANGE, FEED_UUID_ADSB_FI, FEED_UUID_AIRPLANESLIVE). Auto-generated on first run and stored in /config/feed-uuid-<profile>. |
FEED_STATS_ENABLED |
true |
Enable periodic console stats logging and ADSBx RSSI/stats upload. ADSBx upload activates automatically when adsbexchange is in FEED_PROFILES, using the same UUID as the beast feed. |
STATS_LOG_INTERVAL |
120 |
Console stats logging interval in seconds. Default is 120 (every 2 minutes). Set to e.g. 60 for 1 min or 300 for 5 min. |
FEED_UAT_INPUT |
(empty) | UAT 978 MHz data source as host:port (e.g. dump978:30978). Only applies in the US. |
FEED_LAT |
(empty) | Receiver latitude in decimal degrees. Used as fallback if --lat is not in READSB_EXTRA_ARGS. |
FEED_LON |
(empty) | Receiver longitude in decimal degrees. Used as fallback if --lon is not in READSB_EXTRA_ARGS. |
READSB_AUTO_LOCATION |
true |
Auto-detect latitude/longitude via IP geolocation when FEED_LAT/FEED_LON are not set. Set to false to disable. |
READSB_NET |
true |
Enable TCP networking (Beast, raw, SBS listeners). Default: true. Set to false for decode-only mode with no network ports. |
READSB_DEVICE_TYPE |
(empty) | SDR device type (rtlsdr, modesbeast, etc.). Leave empty for network-only mode. Maps to --device-type. |
READSB_AUTOGAIN |
true |
Enable readsb built-in autogain (--gain auto). Adjusts gain at the IQ sample level every ~0.5s. Set to false to disable (uses max gain unless READSB_GAIN is set). |
READSB_GAIN |
(empty) | Set a fixed RTL-SDR gain value (e.g. 40.2). Overrides READSB_AUTOGAIN. |
READSB_NET_BI_PORT |
(none) | Beast protocol input port(s), comma-separated (e.g. 30004,30104). Maps to --net-bi-port. |
READSB_NET_BO_PORT |
(none) | Beast protocol output port(s), comma-separated (e.g. 30005). Maps to --net-bo-port. |
READSB_BIASTEE |
false |
Bias-T DC power for active antennas. See Bias-T Power for Active Antennas. |
LOG_LEVEL |
info |
Minimum log verbosity: debug, info, warn, error, fatal. Set to debug for verbose operational detail. |
| Profile name | --net-connector value |
|---|---|
adsbexchange |
feed1.adsbexchange.com,30004,beast_reduce_out,feed2.adsbexchange.com,64004 |
adsb-fi |
feed.adsb.fi,30004,beast_reduce_out |
airplaneslive |
feed.airplanes.live,30004,beast_reduce_out |
planewatch |
atc.plane.watch,30004,beast_reduce_out |
opensky |
feed.opensky-network.org,30005,beast_reduce_out |
flyitalyadsb |
dati.flyitalyadsb.com,4905,beast_reduce_out |
adsbhub |
data.adsbhub.org,5002,beast_reduce_out |
radarplane |
feed.radarplane.com,30001,beast_reduce_out |
MLAT (multilateration) is optional — your ADS-B feed and stats work without it. MLAT uses timing data from multiple receivers to locate aircraft that don't broadcast GPS positions. If you want to enable it, run a separate mlat-client container per exchange.
| Variable | Default | Description |
|---|---|---|
MLAT_CLIENT_INPUT_CONNECT |
readsb:30005 |
Beast data source (host:port) |
MLAT_CLIENT_SERVER |
feed.adsbexchange.com:31090 |
Multilateration server (host:port) — see table below |
MLAT_CLIENT_LAT |
(auto-detected) | Receiver latitude in decimal degrees. Auto-detected via IP geolocation if empty and MLAT_CLIENT_AUTO_LOCATION=true. |
MLAT_CLIENT_LON |
(auto-detected) | Receiver longitude in decimal degrees. Auto-detected via IP geolocation if empty and MLAT_CLIENT_AUTO_LOCATION=true. |
MLAT_CLIENT_ALT |
(auto-detected) | Receiver altitude with unit (m or ft). Auto-detected from terrain elevation when lat/lon are auto-detected. |
MLAT_CLIENT_USER_ID |
(required) | User identifier / feeder name for the MLAT server |
MLAT_CLIENT_RESULTS |
(none) | Results output destination(s), e.g. beast,connect,readsb:30104 |
MLAT_CLIENT_AUTO_LOCATION |
true |
Auto-detect lat/lon via ip-api.com and altitude via Open-Meteo Elevation API when MLAT_CLIENT_LAT/MLAT_CLIENT_LON are not set. Set to false to disable. |
MLAT_CLIENT_PRIVACY |
false |
Hide receiver on coverage maps |
MLAT_CLIENT_UUID |
(none) | UUID sent to the server |
MLAT_CLIENT_UUID_FILE |
(none) | Path to UUID file for persistent identity |
MLAT_CLIENT_NO_UDP |
false |
Disable UDP transport |
MLAT_CLIENT_ARGS |
(none) | Raw arguments (overrides individual env vars) |
Auto-location: When
MLAT_CLIENT_AUTO_LOCATIONistrue(the default) andMLAT_CLIENT_LAT/MLAT_CLIENT_LONare not set, the mlat-client container automatically detects your approximate location via IP geolocation. Altitude is resolved from terrain elevation at the detected coordinates. Explicit values always take priority. IP-based geolocation is approximate (city-level) — for best MLAT results, set your exact coordinates manually.
| Profile name | MLAT server endpoint |
|---|---|
adsbexchange |
feed.adsbexchange.com:31090 |
adsb-fi |
feed.adsb.fi:31090 |
airplaneslive |
feed.airplanes.live:31090 |
planewatch |
mlat.plane.watch:31090 |
flyitalyadsb |
dati.flyitalyadsb.com:30100 |
radarplane |
mlat.radarplane.com:40900 |
Feeder name: Most aggregators (e.g. ADSBExchange) derive your display name from the
--user/MLAT_CLIENT_USER_IDvalue sent by your mlat-client container. If no mlat-client is connected, the exchange typically auto-generates a random name. To set a custom name, configureMLAT_CLIENT_USER_IDin your mlat-client sidecar (see examples below).
services:
readsb:
image: blackoutsecure/readsb:latest
container_name: readsb
environment:
- TZ=Etc/UTC
- READSB_DEVICE_TYPE=rtlsdr
# - READSB_BIASTEE=true # enable for powered LNAs (e.g. SAWbird+)
- FEED_PROFILES=adsbexchange
- FEED_LAT=51.5074
- FEED_LON=-0.1278
volumes:
- readsb-config:/config
- readsb-run:/run/readsb
devices:
- /dev/bus/usb:/dev/bus/usb
restart: unless-stopped
mlat-client:
image: blackoutsecure/mlat-client:latest
container_name: mlat-adsbx
environment:
- MLAT_CLIENT_INPUT_CONNECT=readsb:30005
- MLAT_CLIENT_SERVER=feed.adsbexchange.com:31090
- MLAT_CLIENT_LAT=51.5074 # omit to auto-detect via IP geolocation
- MLAT_CLIENT_LON=-0.1278 # omit to auto-detect via IP geolocation
- MLAT_CLIENT_ALT=50m # omit to auto-detect from terrain elevation
# - MLAT_CLIENT_AUTO_LOCATION=true # default — auto-detects lat/lon/alt when not set
- MLAT_CLIENT_USER_ID=myfeeder-london
- MLAT_CLIENT_RESULTS=beast,connect,readsb:30104
depends_on: [readsb]
restart: unless-stopped
volumes:
readsb-config:
readsb-run:Minimal setup — lat/lon/alt are auto-detected from your IP address:
services:
readsb:
image: blackoutsecure/readsb:latest
container_name: readsb
environment:
- TZ=Etc/UTC
- READSB_DEVICE_TYPE=rtlsdr
- FEED_PROFILES=adsbexchange
volumes:
- readsb-config:/config
- readsb-run:/run/readsb
devices:
- /dev/bus/usb:/dev/bus/usb
restart: unless-stopped
mlat-client:
image: blackoutsecure/mlat-client:latest
container_name: mlat-adsbx
environment:
- MLAT_CLIENT_INPUT_CONNECT=readsb:30005
- MLAT_CLIENT_SERVER=feed.adsbexchange.com:31090
- MLAT_CLIENT_USER_ID=myfeeder-london
- MLAT_CLIENT_RESULTS=beast,connect,readsb:30104
# MLAT_CLIENT_LAT, MLAT_CLIENT_LON, MLAT_CLIENT_ALT auto-detected
depends_on: [readsb]
restart: unless-stopped
volumes:
readsb-config:
readsb-run:services:
readsb:
image: blackoutsecure/readsb:latest
container_name: readsb
environment:
- TZ=Etc/UTC
- READSB_DEVICE_TYPE=rtlsdr
# - READSB_BIASTEE=true # enable for powered LNAs (e.g. SAWbird+)
- FEED_PROFILES=adsbexchange,adsb-fi,airplaneslive
- FEED_LAT=51.5074
- FEED_LON=-0.1278
volumes:
- readsb-config:/config
- readsb-run:/run/readsb
devices:
- /dev/bus/usb:/dev/bus/usb
restart: unless-stopped
mlat-adsbx:
image: blackoutsecure/mlat-client:latest
container_name: mlat-adsbx
environment:
- MLAT_CLIENT_INPUT_CONNECT=readsb:30005
- MLAT_CLIENT_SERVER=feed.adsbexchange.com:31090
- MLAT_CLIENT_LAT=51.5074 # omit to auto-detect via IP geolocation
- MLAT_CLIENT_LON=-0.1278 # omit to auto-detect via IP geolocation
- MLAT_CLIENT_ALT=50m # omit to auto-detect from terrain elevation
- MLAT_CLIENT_USER_ID=myfeeder-london
- MLAT_CLIENT_RESULTS=beast,connect,readsb:30104
depends_on: [readsb]
restart: unless-stopped
mlat-adsb-fi:
image: blackoutsecure/mlat-client:latest
container_name: mlat-adsb-fi
environment:
- MLAT_CLIENT_INPUT_CONNECT=readsb:30005
- MLAT_CLIENT_SERVER=feed.adsb.fi:31090
- MLAT_CLIENT_LAT=51.5074 # omit to auto-detect
- MLAT_CLIENT_LON=-0.1278 # omit to auto-detect
- MLAT_CLIENT_ALT=50m # omit to auto-detect
- MLAT_CLIENT_USER_ID=myfeeder-london
- MLAT_CLIENT_RESULTS=beast,connect,readsb:30104
depends_on: [readsb]
restart: unless-stopped
mlat-airplaneslive:
image: blackoutsecure/mlat-client:latest
container_name: mlat-airplaneslive
environment:
- MLAT_CLIENT_INPUT_CONNECT=readsb:30005
- MLAT_CLIENT_SERVER=feed.airplanes.live:31090
- MLAT_CLIENT_LAT=51.5074 # omit to auto-detect
- MLAT_CLIENT_LON=-0.1278 # omit to auto-detect
- MLAT_CLIENT_ALT=50m # omit to auto-detect
- MLAT_CLIENT_USER_ID=myfeeder-london
- MLAT_CLIENT_RESULTS=beast,connect,readsb:30104
depends_on: [readsb]
restart: unless-stopped
volumes:
readsb-config:
readsb-run:Note
TO DO — Sidecar container images for the services listed below are planned but not yet available. Compose examples and documentation will be added as each image is published.
Some aggregators use proprietary protocols or require authentication that cannot be handled via a simple --net-connector feed profile. These require running a dedicated sidecar container that reads Beast data from readsb:30005.
| Service | Container | Auth | Status | Notes |
|---|---|---|---|---|
| FlightAware | flightaware/piaware | feeder-id |
🔜 Planned | Connects to readsb:30005 for Beast input. Handles auth, feeding, and MLAT independently. |
| FlightRadar24 | fr24feed | sharing-key |
🔜 Planned | Proprietary binary. Connects to readsb:30005 in AVR/Beast mode. |
| Radarbox | rbfeeder | sharing-key |
🔜 Planned | Proprietary binary. Connects to readsb:30005 in Beast mode. |
| Planefinder | pfclient | share-code |
🔜 Planned | Proprietary binary. Connects to readsb:30005 in Beast mode. |
-
MLAT: MLAT (multilateration) positioning is entirely optional — your ADS-B feed works without it. MLAT requires a separate
mlat-clientcontainer per exchange. The readsb container handles Beast data forwarding only. If you see "MLAT: Not Found" on your feeder status page, that is normal when no mlat-client container is running. See the MLAT server table above for the correct server endpoint per exchange if you want to set it up. The mlat-client container supports automatic location detection (MLAT_CLIENT_AUTO_LOCATION=true, enabled by default) — lat/lon are resolved from your IP address and altitude from terrain elevation, so you can omitMLAT_CLIENT_LAT/MLAT_CLIENT_LON/MLAT_CLIENT_ALTfor a quick start. For best MLAT accuracy, set your exact coordinates manually. -
UUID: Each feed profile gets its own UUID, stored in
/config/feed-uuid-<profile>(e.g./config/feed-uuid-adsbexchange). UUIDs are auto-generated on first run and persisted across container restarts. They are also injected into the s6 container environment (e.g.FEED_UUID_ADSBEXCHANGE) for downstream services. To override, setFEED_UUID_<PROFILE>env vars. To view:docker exec readsb cat /config/feed-uuid-adsbexchange -
Checking feed status:
- ADSBx: https://adsbexchange.com/myip/
- adsb.fi: https://adsb.fi/status
- airplanes.live: https://airplanes.live/
- plane.watch: https://plane.watch/
- flyitalyadsb: https://flyitalyadsb.com/
- radarplane: https://radarplane.com/
Feed verification URLs are also logged during container startup for each active profile.
Note: IP-based geolocation is approximate (typically city-level accuracy). Elevation is ground-level at the detected coordinates. For best MLAT results, set your exact coordinates manually.
Note on altitude: The auto-detected altitude represents ground elevation at the detected coordinates, not your antenna height above sea level. For accurate MLAT, your altitude should include the height of your antenna above ground. For example, if ground elevation is
50mand your antenna is on a10mrooftop mast, setMLAT_CLIENT_ALT=60m. When relying on auto-detection, consider adding your antenna height manually for better multilateration accuracy.
RTL-SDR dongles have a tunable Low-Noise Amplifier (LNA) in the R820T/R820T2 tuner with 29 discrete gain steps from 0.0 to 49.6 dB. Gain directly affects reception quality:
- Too much gain → the ADC (analog-to-digital converter) overloads, strong nearby signals clip, and you lose both close and distant aircraft
- Too little gain → weak signals from distant aircraft are buried in the noise floor and never decoded
- Optimal gain → the noise floor is just above the ADC's quantization noise, maximizing the dynamic range available for ADS-B signals
| Mode | READSB_AUTOGAIN |
READSB_GAIN |
How it works | When to use |
|---|---|---|---|---|
| Autogain (default) | true |
(empty) | readsb's built-in algorithm monitors IQ noise floor statistics every ~0.5s and adjusts the R820T tuner gain in-process. Converges quickly on startup, makes fine adjustments as conditions change. | Recommended for all users. Best results with minimal effort. |
| Fixed gain | true or false |
e.g. 40.2 |
A specific gain value from the R820T table is locked in. readsb will not adjust it. READSB_GAIN takes priority over READSB_AUTOGAIN. |
When you've tested your RF environment and know the optimal value, or when autogain isn't converging well (rare). |
| Disabled (max gain) | false |
(empty) | No --gain flag is passed to readsb. readsb defaults to maximum gain (equivalent to --gain 58 / hardware AGC). The R820T runs at its highest amplification. |
Not recommended. Overloads most receivers. Only useful for extremely remote locations with zero nearby RF sources. |
readsb's built-in autogain (--gain auto) is fundamentally different from older shell-script-based approaches:
- Operates at the IQ sample level — analyzes raw magnitude data from every USB transfer (~0.5s cycles), not decoded message statistics
- Monitors noise floor — tracks
noiseLowSamples,noiseHighSamples, andloudEventsper buffer - Adjusts in-process — changes gain via
rtlsdr_set_tuner_gain()without restarting readsb - Fast startup convergence — uses a gain sweep multiplier on first run, settling within seconds
- Loud signal detection — immediately lowers gain when clipping events are detected
The algorithm's thresholds can be tuned via READSB_EXTRA_ARGS (but there should be no need to change them):
--gain auto-verbose,<lowestGain>,<noiseLowThreshold>,<noiseHighThreshold>,<loudThreshold>
Defaults: --gain auto-verbose,0,34,36,243 (thresholds are 0–256 scale)
For most users (default — no changes needed):
environment:
- READSB_DEVICE_TYPE=rtlsdr
# READSB_AUTOGAIN defaults to true — autogain is activeTo see autogain decisions in the log:
environment:
- READSB_EXTRA_ARGS=--gain auto-verboseTo set a specific fixed gain:
environment:
- READSB_GAIN=40.2To disable autogain (max gain — not recommended):
environment:
- READSB_AUTOGAIN=falseIf you prefer manual control, use the strong signal percentage as your guide. You can check this inside the container:
docker exec readsb sh -c 'jq ".total.local | {accepted: (.accepted | add), strong: .strong_signals, signal: .signal, noise: .noise}" /run/readsb/stats.json'Target: ~5% strong signals (messages with RSSI > −3 dBFS). Guidelines from the upstream adsb-wiki:
| Strong signal % | Meaning | Action |
|---|---|---|
| > 10% | ADC is overloading — you're clipping strong signals and losing distant ones too | Decrease gain |
| 5–10% | Slightly high but acceptable, especially near airports | May be optimal for your location |
| 1–5% | Sweet spot — good balance of range and close-in reception | Ideal for most locations |
| < 1% | Gain may be too low — you could be missing weak distant signals | Increase gain (or this is normal for rural areas with an LNA) |
| < 0.1% | Very low — likely too conservative unless you have an external LNA | Increase gain |
Another approach: Aim for the weakest received signal (visible in graphs1090 or tar1090 stats) to be between −30 and −34 dBFS. This ensures you're using the full dynamic range without clipping.
The R820T/R820T2 tuner supports these discrete gain values (in dB):
0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7
16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4
37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6
Most receivers settle between 25–44 dB depending on antenna, LNA, cable loss, and local RF environment. Common sweet spots:
| Setup | Typical optimal gain |
|---|---|
| Bare RTL-SDR + short coax + outdoor antenna | 38–49.6 |
| RTL-SDR + LNA (e.g. SAWbird+) + outdoor antenna | 25–38 |
| RTL-SDR + LNA + long coax run (> 10m) | 33–44 |
| Indoor antenna / window mount | 42–49.6 |
| Near a major airport (< 10 km) | 20–33 |
- Start with autogain — it handles 95% of setups correctly with zero configuration
- Wait at least 5 minutes before evaluating gain performance — stats need time to accumulate
- After moving your antenna, let autogain re-converge (it will adjust automatically)
- USB power matters more than gain — voltage drop from long USB cables or shared hubs causes more reception problems than suboptimal gain. See USB topology warnings
- An external LNA changes everything — if you add a SAWbird+ or similar, you'll likely need lower gain than before (the LNA adds 15–20 dB of external amplification)
Some ADS-B setups use an inline LNA (Low Noise Amplifier) and/or SAW filter module between the antenna and the RTL-SDR dongle. These modules amplify and filter the signal right at the antenna before cable loss degrades it — significantly improving range and aircraft count. They need DC power delivered through the coaxial cable, which is called bias-T (bias tee).
A popular example is the Nooelec SAWbird+ ADS-B — a dual-channel cascaded ultra-low noise amplifier and SAW filter module with dedicated 1090 MHz (ADS-B) and 978 MHz (UAT) channels. It plugs inline between your antenna and dongle. It supports two power options: bias-T (3–5V DC via the SMA coax) or external power via a micro-USB port. You only need one — not both.
Option A — Bias-T powered (cleanest setup, no extra cables):
Antenna ──► SAWbird+ ADS-B (1090 MHz channel) ──► RTL-SDR dongle (serial 1090)
└─ bias-T powers SAWbird+ via coax
Option B — USB powered (bias-T not needed):
Antenna ──► SAWbird+ ADS-B (1090 MHz channel) ──► RTL-SDR dongle (serial 1090)
└─ micro-USB power cable
If using Option A (bias-T), set READSB_BIASTEE=true — without it the LNA has no power and actually adds insertion loss (makes reception worse than no filter at all). If using Option B (USB power), leave READSB_BIASTEE=false — the SAWbird+ is already powered and bias-T is unnecessary.
Enable READSB_BIASTEE=true if you use:
- Nooelec SAWbird+ ADS-B powered via bias-T (no USB power connected)
- Nooelec SAWbird+ NOAA or other SAWbird+ variants powered via bias-T
- RTL-SDR Blog ADS-B LNA (inline filtered LNA)
- Any inline LNA/filter that says "requires bias-T" or "requires 3.3–5V DC via coax"
- Active antennas with a built-in LNA that has no separate power input
Do not enable bias-T if:
- Your antenna is passive (simple whip, dipole, or ground plane with no active electronics)
- Your LNA/filter has its own separate USB or DC power supply (some models have a micro-USB port)
- You have a standalone bias-T injector powered externally
- You're unsure — leave it off and check your device documentation
Warning: Sending DC voltage to a device not designed for it won't damage most modern RTL-SDR dongles (they have short-circuit protection), but it's best practice to only enable when needed.
Note
Externally powered LNA? If your LNA has its own power source (e.g., micro-USB or barrel jack), you must set READSB_BIASTEE=false (the default). Enabling bias-T when the LNA is already externally powered provides no benefit, and in some configurations may cause unexpected behavior. The LNA will function normally using its own power supply — bias-T is only needed when the LNA has no other power source.
Caution
Only enable READSB_BIASTEE=true if your antenna or inline LNA requires DC power via coax. Bias-T is disabled by default. If you are unsure whether your setup needs it, leave it off and consult your antenna/LNA documentation.
environment:
- READSB_BIASTEE=trueThe container runs rtl_biast -b 1 before starting readsb. If a specific dongle serial is configured (READSB_DEVICE or auto-detected SDR_1090_SERIAL), it targets that dongle.
# Check bias-T status in logs
docker logs readsb 2>&1 | grep 'bias-T'
# Manually test bias-T inside the container
docker exec readsb rtl_biast -b 1 # enable
docker exec readsb rtl_biast -b 0 # disableWhen bias-T is active, you should see a noticeable improvement in aircraft count and range compared to running the SAWbird+ unpowered.
| RTL-SDR Dongle | Bias-T | Voltage / Current |
|---|---|---|
| RTL-SDR Blog V3 | Yes | 4.5V, ~180mA max |
| RTL-SDR Blog V4 | Yes | 4.5V, ~180mA max |
| Nooelec NESDR SMArt v5 | Yes | 4.5V |
| Nooelec NESDR SMArTee XTR | Yes (always on) | 4.5V |
| Generic RTL2832U dongles | Usually no | — |
| Module | Frequency | Power |
|---|---|---|
| Nooelec SAWbird+ ADS-B | 1090 MHz + 978 MHz (dual) | Bias-T (3–5V via coax) or micro-USB |
| Nooelec SAWbird+ NOAA | 137 MHz | Bias-T (3–5V via coax) or micro-USB |
| RTL-SDR Blog ADS-B LNA | 1090 MHz | Bias-T (3.3–5V via coax) |
| RTL-SDR Blog Wideband LNA | Wideband | Bias-T or micro-USB |
readsb's built-in autogain handles most situations. See Gain Control for a full guide on autogain vs. fixed gain vs. disabled, recommended values by setup type, and how to measure strong signal percentage.
Quick diagnostics:
# Check your strong signal percentage and noise floor
docker exec readsb sh -c 'jq ".total.local | {accepted: (.accepted | add), strong: .strong_signals, signal: .signal, noise: .noise}" /run/readsb/stats.json'
# Test the dongle hardware
docker exec readsb rtl_test -t
docker exec readsb rtl_test -s 2400000 -tForce a specific gain for testing:
environment:
- READSB_GAIN=38.6RTL-SDR dongles have a crystal oscillator that's typically off by a few PPM (parts per million). Most R820T2-based dongles are within ±1 PPM and don't need correction. If you're seeing fewer aircraft than expected:
# Test PPM offset (let it run for 1+ minute, read the cumulative value)
docker exec readsb rtl_test -pApply correction:
environment:
- READSB_EXTRA_ARGS=--ppm 3
- READSB_DEVICE_TYPE=rtlsdrThese defaults are applied automatically when feed profiles are active, but can be overridden in READSB_EXTRA_ARGS:
| Parameter | Default | Description |
|---|---|---|
--net-bi-port |
(none) | Beast protocol input port(s). Set via READSB_NET_BI_PORT env var or --net-bi-port in READSB_EXTRA_ARGS. |
--net-bo-port |
(none) | Beast protocol output port(s). Set via READSB_NET_BO_PORT env var or --net-bo-port in READSB_EXTRA_ARGS. |
--net-beast-reduce-interval |
0.5 |
Seconds between reduced beast output updates. Lower = more data, more bandwidth. |
--net-heartbeat |
60 |
Seconds between TCP keepalive heartbeats. |
--net-ro-size |
1280 |
Raw output buffer size in bytes. |
--net-ro-interval |
0.2 |
Raw output flush interval in seconds. |
--json-location-accuracy |
2 |
JSON position decimal places (1=~11km, 2=~1.1km). |
--range-outline-hours |
24 |
Hours of range data to retain for outline calculation. |
--max-range |
450 |
Maximum detection range in nautical miles. |
--write-json-every |
1 |
JSON output write interval in seconds. Increase to 5 to reduce CPU/IO. |
| Variable | Default | Description |
|---|---|---|
FEED_STATS_ENABLED |
true |
Master switch for console stats logging and ADSBx stats upload. |
STATS_LOG_INTERVAL |
120 |
Console stats interval in seconds. Set 60 for 1-min updates, 300 for 5-min. |
ADSBX_UPLOAD_INTERVAL |
5 |
ADSBx stats upload interval in seconds (matches official ADSBx feeder rate). Only active when adsbexchange is in FEED_PROFILES. |
LOG_LEVEL |
info |
Minimum log verbosity: debug, info, warn, error, fatal. Per-upload confirmations are debug-level. Set to debug to see them, or warn to suppress routine messages. |
For most users, IP geolocation (default) provides city-level accuracy which is sufficient for basic reception. For MLAT and precise range calculations:
environment:
- FEED_LAT=39.6195 # exact latitude
- FEED_LON=-86.1552 # exact longitude
- READSB_AUTO_LOCATION=false # disable IP geolocationIf CPU or disk I/O is a concern (e.g., on a Raspberry Pi Zero):
environment:
- READSB_EXTRA_ARGS=--write-json-every 5
- READSB_DEVICE_TYPE=rtlsdrThis reduces JSON writes from every second to every 5 seconds.
All log output uses syslog format so you can identify the source and severity of each line in docker logs, and it integrates natively with log aggregators (Loki, Datadog, Fluentd, etc.):
<timestamp> <service>[<priority>]: <message>
2026-04-02T11:38:20Z init-readsb-config[info]: adsbexchange UUID: 12345678-1234-1234-1234-123456789abc
2026-04-02T11:38:20Z init-readsb-config[info]: adsb-fi UUID: 87654321-4321-4321-4321-cba987654321
2026-04-02T11:38:20Z init-readsb-config[info]: Active feed profiles: adsbexchange,adsb-fi
2026-04-02T11:38:20Z init-readsb-config[info]: Verify adsbexchange feed: https://adsbexchange.com/myip/
2026-04-02T11:38:20Z init-readsb-config[info]: Verify adsb-fi feed: https://adsb.fi/status
2026-04-02T11:38:20Z init-readsb-config[info]: fixed USB permissions: /dev/bus/usb/001/004
2026-04-02T11:38:20Z init-readsb-config[info]: receiver location: 51.5074, -0.1278
2026-04-02T11:38:21Z svc-readsb[info]: readsb container startup configuration
2026-04-02T11:38:21Z svc-readsb[info]: Using readsb built-in autogain (--gain auto)
2026-04-02T11:38:21Z svc-readsb[decoder]: *8daa4b32584385ef2a7603346e29;
2026-04-02T11:38:21Z svc-readsb[decoder]: hex: aa4b32 CRC: 000000 fixed bits: 0 decode: ok
2026-04-02T11:38:21Z svc-readsb[decoder]: RSSI: -22.0 dBFS reduce_forward: 1
2026-04-02T11:38:22Z svc-feed-stats[info]: ADSBx stats upload enabled (UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890)
2026-04-02T11:38:22Z svc-feed-stats[info]: ADSBx stats URL: https://www.adsbexchange.com/api/feeders/?feed=a1b2c3d4-e5f6-7890-abcd-ef1234567890
2026-04-02T11:38:22Z svc-feed-stats[info]: Starting stats service (upload=5s, console=120s, JSON_DIR=/run/readsb)
2026-04-02T11:40:22Z svc-feed-stats[info]: stats: 42 aircraft tracked (38 with position), 128456 messages total
2026-04-02T11:38:52Z svc-feed-stats[warn]: /run/readsb/aircraft.json not updated in 45s.
| Service | Description |
|---|---|
init-readsb-config |
One-shot init: per-profile UUID generation and persistence (disk + s6 env), feed profile setup, ADSBx stats URL, USB permissions, geolocation |
svc-readsb |
Main readsb decoder service (startup config + decoder output) |
svc-feed-stats |
Periodic console stats (every 2 min by default) + ADSBx RSSI/stats upload when adsbexchange profile is active. Both use the same UUID. Per-upload confirmations are debug-level. |
| Priority | Meaning |
|---|---|
debug |
Verbose operational detail (e.g. per-upload ADSBx confirmations). Hidden at default info level. Set LOG_LEVEL=debug to see. |
info |
Normal operational messages |
warn |
Non-fatal issues that may need attention |
error |
Errors that affect functionality |
fatal |
Critical errors causing service exit |
decoder |
Raw readsb decoder output (ADS-B message decoding) |
Filter logs by service:
docker logs readsb 2>&1 | grep 'svc-readsb\['
docker logs readsb 2>&1 | grep 'svc-feed-stats\['
docker logs readsb 2>&1 | grep 'init-readsb-config\['Filter logs by priority:
docker logs readsb 2>&1 | grep '\[debug\]:' # verbose upload confirmations (requires LOG_LEVEL=debug)
docker logs readsb 2>&1 | grep '\[warn\]:'
docker logs readsb 2>&1 | grep '\[error\]:\|\[fatal\]:'
docker logs readsb 2>&1 | grep '\[decoder\]:'Log verbosity control:
Set LOG_LEVEL to control which messages appear. Default is info.
environment:
- LOG_LEVEL=info # default — stats summaries, startup info, warnings, errors
- LOG_LEVEL=debug # all of the above + per-upload ADSBx confirmations
- LOG_LEVEL=warn # warnings and errors only (quietest)Check logs:
docker logs readsb
docker logs readsb --tail 50 -f # Follow last 50 linesCommon causes:
- USB device not found: Verify RTL-SDR dongle is connected and restart container
- Permission denied on
/dev/bus/usb: Container may need elevated privileges or device permissions - Configuration error: Check
READSB_EXTRA_ARGSsyntax against Extra Arguments
This container feeds ADSBx via two pathways that share a single UUID from /config/feed-uuid-adsbexchange:
- Beast feed (
--net-connectorto port 30004) — primary ADS-B data connection (persistent TCP) - Stats upload (
svc-feed-stats) — RSSI/stats data (periodic HTTP POST every 5s, matching official ADSBx feeder)
Both pathways use the same UUID so ADSBx sees one feeder. The stats URL is logged at startup:
https://www.adsbexchange.com/api/feeders/?feed=YOUR-UUID-HERE
To link your account: Visit https://adsbexchange.com/myip/ and click the pre-selected Feed UID at the top of the "Link your receiver" section.
Verify connectivity:
docker exec readsb cat /run/readsb/aircraft.json | jq '.aircraft | length'If count is 0:
- Check RTL-SDR device:
docker exec readsb rtl_test -t - Verify antenna is connected and positioned properly
- If using a powered antenna, ensure
READSB_BIASTEE=trueis set - Try a manual gain: set
READSB_GAIN=35or use--gain auto-verboseinREADSB_EXTRA_ARGSto watch gain decisions - Look for RF interference: try a different location or antenna orientation
Check if readsb is running:
docker exec readsb ps aux | grep readsbVerify write permissions:
docker exec readsb ls -la /run/readsb/JSON directory should be writable by the container user.
Profile the container:
docker stats readsb --no-streamTroubleshooting steps:
- Reduce JSON output frequency: Add
--write-json-every 5(updates every 5 seconds instead of 1) - Check for decode storms: Monitor aircraft count with
watch 'docker exec readsb jq .aircraft.length /run/readsb/aircraft.json' - Restart container:
docker restart readsb
RTL-SDR USB device permissions are fixed automatically during container init. If you still see permission errors:
# Verify RTL-SDR devices are visible inside the container
docker exec readsb lsusb | grep -i realtek
# Check device permissions were applied
docker exec readsb ls -la /dev/bus/usb/*/*If devices are not visible, ensure the devices: mapping is correct in your compose file. On some hosts, you may need to restart the container after plugging in the dongle.
If ports are already in use, map to different host ports:
docker run ... \
-p 30011:30001 \
-p 30012:30002 \
-p 30013:30003 \
...Then update client connections to use the new ports.
- Check upstream readsb documentation
- Review container logs:
docker logs -f readsb - Open an issue on GitHub
The container includes built-in health monitoring at multiple levels:
The image includes a HEALTHCHECK that verifies aircraft.json exists and was updated within the last 60 seconds. Docker marks the container as unhealthy after 3 consecutive failures. This check is profile-agnostic — the same healthcheck applies regardless of which feed profiles are active, since all profiles share the same underlying readsb decoder and aircraft.json output.
| Setting | Value |
|---|---|
| Interval | 30s |
| Timeout | 5s |
| Start period | 60s |
| Retries | 3 |
# Check container health status
docker inspect --format='{{.State.Health.Status}}' readsb
# View health check history
docker inspect --format='{{json .State.Health}}' readsb | jq .All long-running services are supervised by s6-overlay and automatically restarted on crash:
# Check individual service status (equivalent of systemctl status)
docker exec readsb s6-svstat /run/service/svc-readsb
docker exec readsb s6-svstat /run/service/svc-feed-statssvc-feed-stats logs a summary every 2 minutes by default (configurable via STATS_LOG_INTERVAL). Stats logging works with all feed profiles — it is not limited to adsbexchange:
svc-feed-stats[info]: stats: 42 aircraft tracked (38 with position), 128456 messages total
To change the interval, set STATS_LOG_INTERVAL to the desired number of seconds (e.g. 60 for every minute, 300 for every 5 minutes). To disable stats logging entirely, set FEED_STATS_ENABLED=false.
# Container health
docker inspect --format='{{.State.Health.Status}}' readsb
# Aircraft count
docker exec readsb cat /run/readsb/aircraft.json | jq '.aircraft | length'
# Feed UUID (per-profile)
docker exec readsb cat /config/feed-uuid-adsbexchange
# List all profile UUIDs
docker exec readsb ls /config/feed-uuid-*
# Service uptime (all services)
docker exec readsb s6-svstat /run/service/svc-readsb
docker exec readsb s6-svstat /run/service/svc-feed-stats
# RTL-SDR dongle test
docker exec readsb rtl_test -t
# Tag a dongle serial
docker exec readsb rtl_eeprom -d 0 -s 00001090
# Check bias-T status
docker exec readsb rtl_biast -b 1 # enable
docker exec readsb rtl_biast -b 0 # disableThis project uses semantic versioning:
- Releases published on GitHub Releases
- Multi-arch images (amd64, arm64v8) built automatically
- Docker Hub tags: version-specific,
latest, and architecture-specific
Update to latest:
docker pull blackoutsecure/readsb:latest
docker-compose up -d # if using composeCheck image version:
docker inspect -f '{{ index .Config.Labels "build_version" }}' blackoutsecure/readsb:latest- Docker Hub: blackoutsecure/readsb
- Issues / bug reports: GitHub Issues — include Docker version, container logs, and reproduction steps.
- Releases: GitHub Releases
- Upstream: wiedehopf/readsb
- Container base: LinuxServer.io · Discord
GPL-3.0-or-later — see LICENSE. The upstream readsb project is also GPL-3.0-or-later.
Sponsored and maintained by Blackout Secure.
