Unofficial community image for readsb configured as an MLAT hub, built with LinuxServer.io style container patterns (s6, hardened defaults, practical runtime options). Aggregates multiple mlat-client Beast feeds into a single deduplicated Beast output for downstream tools (tar1090, dashboards, collectors). 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.
Warning
MLAT results must never be forwarded back to feeders — doing so contaminates MLAT calculations and will get you banned from aggregation services. mlat-hub runs as a separate readsb instance specifically to prevent this cross-contamination.
Links: Docker Hub · Balena block · GitHub · Upstream readsb
- Quick Start
- Image Availability
- About The mlat-hub Application
- Supported Architectures
- Usage
- Parameters
- Volume Details
- Configuration
- User / Group Identifiers
- Application Setup
- Connection Modes
- Troubleshooting
- Health Monitoring
- Release & Versioning
- Resources
- License
5-minute MLAT aggregator setup:
docker run -d \
--name=mlathub \
--restart unless-stopped \
-e TZ=Etc/UTC \
-e MLATHUB_INPUTS="mlat-adsbx,30105,beast_in;mlat-adsbfi,30105,beast_in" \
-p 30104:30104 \
-p 30105:30105 \
--security-opt no-new-privileges:true \
blackoutsecure/mlat-hub:latestCombined MLAT results are available on port 30105 (Beast protocol).
For compose files, balena, full-stack examples, and connection modes, see Usage below.
Docker Hub (Recommended):
- All images published to Docker Hub
- Simple pull command:
docker pull blackoutsecure/mlat-hub:latest - Multi-arch support: amd64, arm64
- No registry prefix needed (defaults to Docker Hub)
# Pull latest
docker pull blackoutsecure/mlat-hub
# Pull specific version
docker pull blackoutsecure/mlat-hub:1.2.3
# Pull architecture-specific (rarely needed)
docker pull blackoutsecure/mlat-hub:latest@amd64When you feed ADS-B data to multiple aggregation services (ADSBExchange, adsb.fi, airplanes.live, etc.), each service runs its own MLAT client that produces multilateration results on separate ports. mlat-hub combines all of these MLAT result feeds into a single, deduplicated Beast output for visualization tools like tar1090 or data collectors.
Internally, mlat-hub runs upstream readsb in network-only mode with --forward-mlat enabled — no SDR hardware required.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ mlat-client │ │ mlat-client │ │ mlat-client │
│ (ADSBx) │ │ (adsb.fi) │ │ (airplanes │
│ :30105 │ │ :30105 │ │ .live) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ beast_in │ beast_in │ beast_in
└───────────────────┼───────────────────┘
│
┌──────▼───────┐
│ mlat-hub │
│ (readsb │
│ --net-only │
│ --forward │
│ -mlat) │
└──────┬───────┘
│
Beast output :30105
│
┌────────────┼────────────┐
│ │ │
┌──────▼──────┐ ┌──▼───┐ ┌──────▼──────┐
│ tar1090 │ │ adsb │ │ influxdb/ │
│ (map UI) │ │ -to- │ │ grafana │
└─────────────┘ │ mqtt │ └─────────────┘
└──────┘
- Each mlat-client container connects to an aggregation service and produces MLAT results on port 30105
- mlat-hub connects to each mlat-client (via
--net-connector) and ingests the Beast data - readsb deduplicates overlapping positions from multiple sources
- The combined feed is output on Beast port 30105
- Visualization tools (tar1090, etc.) connect to mlat-hub for MLAT data
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/mlat-hub: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:
mlathub:
image: blackoutsecure/mlat-hub:latest
container_name: mlathub
environment:
- TZ=Etc/UTC
- MLATHUB_INPUTS=mlat-adsbx,30105,beast_in;mlat-adsbfi,30105,beast_in
- LOG_LEVEL=info # debug | info | warn | error | fatal
ports:
- 30104:30104 # Beast input (TCP) — push mode
- 30105:30105 # Beast output (TCP) — combined MLAT results
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
- /run:exec
restart: unless-stoppedA complete ADS-B receiver stack with readsb, multiple MLAT clients, mlat-hub, and tar1090:
---
services:
readsb:
image: blackoutsecure/readsb:latest
container_name: readsb
environment:
- TZ=Etc/UTC
- READSB_DEVICE_TYPE=rtlsdr
- READSB_AUTOGAIN=true
- FEED_PROFILES=adsbexchange,adsb-fi
- FEED_LAT=51.5074
- FEED_LON=-0.1278
volumes:
- readsb-config:/config
- readsb-run:/run/readsb
devices:
- /dev/bus/usb:/dev/bus/usb
ports:
- 30003:30003
- 30005:30005
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
- MLAT_CLIENT_LON=-0.1278
- MLAT_CLIENT_ALT=50m
- MLAT_CLIENT_RESULTS=beast,listen,30105
depends_on: [readsb]
restart: unless-stopped
mlat-adsbfi:
image: blackoutsecure/mlat-client:latest
container_name: mlat-adsbfi
environment:
- MLAT_CLIENT_INPUT_CONNECT=readsb:30005
- MLAT_CLIENT_SERVER=feed.adsb.fi:31090
- MLAT_CLIENT_LAT=51.5074
- MLAT_CLIENT_LON=-0.1278
- MLAT_CLIENT_ALT=50m
- MLAT_CLIENT_RESULTS=beast,listen,30105
depends_on: [readsb]
restart: unless-stopped
mlathub:
image: blackoutsecure/mlat-hub:latest
container_name: mlathub
environment:
- TZ=Etc/UTC
- MLATHUB_INPUTS=mlat-adsbx,30105,beast_in;mlat-adsbfi,30105,beast_in
ports:
- 30105:30105
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
- /run:exec
depends_on:
- mlat-adsbx
- mlat-adsbfi
restart: unless-stopped
tar1090:
image: blackoutsecure/tar1090:latest
container_name: tar1090
environment:
- TZ=Etc/UTC
- BEASTHOST=readsb
- MLATHOST=mlathub
- LAT=51.5074
- LONG=-0.1278
ports:
- 8078:80
depends_on:
- readsb
- mlathub
restart: unless-stopped
volumes:
readsb-config:
readsb-run:docker-cli (click here for more info)
docker run -d \
--name=mlathub \
-e TZ=Etc/UTC \
-e MLATHUB_INPUTS="mlat-adsbx,30105,beast_in;mlat-adsbfi,30105,beast_in" \
-p 30104:30104 \
-p 30105:30105 \
--security-opt no-new-privileges:true \
--restart unless-stopped \
blackoutsecure/mlat-hub: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/2354730/mlat-hub
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 30104:30104 |
Beast protocol input (TCP) — feeder containers can push MLAT data here (push mode) |
-p 30105:30105 |
Beast protocol output (TCP) — combined deduplicated MLAT results |
| Parameter | Function | Required |
|---|---|---|
-e TZ=Etc/UTC |
Timezone (TZ database) | Optional |
-e MLATHUB_INPUTS= |
Semicolon-separated host,port,protocol entries the hub will pull from. Example: mlat-adsbx,30105,beast_in;mlat-adsbfi,30105,beast_in. Required for pull mode; not required if you only use push mode. |
Conditional |
-e MLATHUB_BEAST_OUTPUT_PORT=30105 |
Beast protocol output port for combined MLAT results (default: 30105) |
Optional |
-e MLATHUB_BEAST_INPUT_PORT=30104 |
Beast protocol input listen port for push mode (default: 30104) |
Optional |
-e MLATHUB_SBS_OUTPUT_PORT= |
Optional SBS/Basestation output port. Leave empty to disable. | Optional |
-e MLATHUB_EXTRA_ARGS= |
Additional readsb arguments appended after all env-var-driven options | Optional |
-e MLATHUB_USER=abc |
Runtime user (default: abc) |
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 LOG_LEVEL=info |
Minimum log verbosity: debug, info (default), warn, error, fatal |
Optional |
The protocol field in MLATHUB_INPUTS entries supports:
| Protocol | Description |
|---|---|
beast_in |
Beast binary format (most common for MLAT results) |
raw_in |
Raw AVR format |
sbs_in |
SBS/Basestation format |
This container does not require any USB or hardware devices — it runs in network-only mode and aggregates upstream Beast feeds.
mlat-hub is stateless and does not require any persistent volumes. All MLAT input/output is over TCP, and the container holds only transient deduplication state in memory.
If you want a fully ephemeral/read-only filesystem, mount tmpfs for the standard scratch paths:
tmpfs:
- /tmp
- /run:execThis is already included in the recommended compose snippet above.
Environment variables are set using -e flags in docker run or the environment: section in docker-compose. See Parameters for the full list.
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):
- mlat-hub runs as the
abcuser by default - No USB or device permissions are required
- Set
PUIDandPGIDonly if you need file ownership to match a specific host user
Root mode (fallback):
- Set
MLATHUB_USER=rootif needed for other reasons
The container runs readsb in network-only mode with --forward-mlat enabled. It collects MLAT results from multiple feeder containers and outputs a single combined Beast feed.
- Network-only readsb: No SDR hardware, no RTL-SDR dependencies, no
/dev/bus/usbmount required - Multi-source aggregation: Pull MLAT Beast data from any number of
mlat-clientsidecars viaMLATHUB_INPUTS - Push mode: Accepts incoming Beast connections on
MLATHUB_BEAST_INPUT_PORT(default30104) - Deduplication: readsb merges overlapping MLAT positions from multiple sources into a single output stream
- Combined Beast output: Single port (
MLATHUB_BEAST_OUTPUT_PORT, default30105) for downstream consumers - Optional SBS output: Enable
MLATHUB_SBS_OUTPUT_PORTfor tools that consume SBS/Basestation format - Docker HEALTHCHECK: Built-in health monitoring — marks container unhealthy if the Beast output port stops listening
- Log Verbosity: Set
LOG_LEVELto control log output:debug,info(default),warn,error,fatal
Most mlat-hub settings have dedicated MLATHUB_* env vars. Use MLATHUB_EXTRA_ARGS for anything not covered by a dedicated variable — these are appended to the end of the readsb command line after all env-var-driven options.
# Adjust beast reduce interval
-e MLATHUB_EXTRA_ARGS="--net-beast-reduce-interval 1.0"
# Tune TCP heartbeat
-e MLATHUB_EXTRA_ARGS="--net-heartbeat 30"For all available readsb options, see the readsb documentation.
- Never forward mlat-hub's output back to your primary readsb feeder or to aggregation services. MLAT results are derived from aggregator timing — re-injecting them contaminates calculations and will get you banned. mlat-hub exists as a separate readsb instance specifically to prevent this.
- mlat-hub is intended for local visualization and analytics only (tar1090, dashboards, MQTT bridges, local databases).
- Read-only filesystem: Supported when
/tmpand/runare mounted as tmpfs (see the recommended compose snippet) - Non-root user: Supported by default — no device permissions are needed
There are two ways to get MLAT data into the hub. Both modes can be used simultaneously.
mlat-hub connects outbound to each mlat-client via MLATHUB_INPUTS:
environment:
- MLATHUB_INPUTS=mlat-adsbx,30105,beast_in;mlat-adsbfi,30105,beast_inIn this mode the mlat-client containers expose their MLAT result port (typically beast,listen,30105) and mlat-hub dials them.
mlat-client containers connect outbound to mlat-hub's Beast input port:
# mlat-client config
MLAT_CLIENT_RESULTS=beast,connect,mlathub:30104
# mlat-hub config (MLATHUB_INPUTS not required)
MLATHUB_BEAST_INPUT_PORT=30104This is useful when mlat-client containers run on different hosts or in environments where the hub cannot reach them directly.
Check logs:
docker logs mlathub
docker logs mlathub --tail 50 -f # Follow last 50 linesCommon causes:
MLATHUB_INPUTSsyntax error: must be semicolon-separatedhost,port,protocoltriples- Port conflict on the host: change the published port (e.g.
-p 31105:30105) - Configuration error: check
MLATHUB_EXTRA_ARGSsyntax against the readsb documentation
- Verify mlat-client containers are running and connected to their MLAT servers
- Check mlat-hub logs:
docker logs mlathub - Verify network connectivity between containers (must be on the same Docker network)
- Ensure mlat-clients are outputting Beast data on the expected port
- Confirm
MLATHUB_INPUTSentries resolve — DNS names must match the mlat-client container names
# Verify readsb process is running
docker exec mlathub ps aux | grep readsb
# Verify the s6 service is up
docker exec mlathub s6-svstat /run/service/svc-mlathub
# Verify the Beast output port is listening
docker exec mlathub ss -tln | grep 30105| Message | Meaning |
|---|---|
No MLAT inputs configured |
No outbound connections — use push mode or set MLATHUB_INPUTS |
N input(s), beast-out=30105 |
Hub will connect to N mlat-client containers |
- Check upstream readsb documentation
- Review container logs:
docker logs -f mlathub - Open an issue on GitHub
The container includes built-in health monitoring at multiple levels:
The image includes a HEALTHCHECK that verifies the Beast output port (30105) is in LISTEN state. Docker marks the container as unhealthy after 3 consecutive failures.
| Setting | Value |
|---|---|
| Interval | 30s |
| Timeout | 5s |
| Start period | 60s |
| Retries | 3 |
# Check container health status
docker inspect --format='{{.State.Health.Status}}' mlathub
# View health check history
docker inspect --format='{{json .State.Health}}' mlathub | jq .All long-running services are supervised by s6-overlay and automatically restarted on crash:
# Check service status (equivalent of systemctl status)
docker exec mlathub s6-svstat /run/service/svc-mlathub# Container health
docker inspect --format='{{.State.Health.Status}}' mlathub
# Beast output port listening
docker exec mlathub ss -tln | grep 30105
# Service uptime
docker exec mlathub s6-svstat /run/service/svc-mlathub
# Tail logs
docker logs -f mlathubThis 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/mlat-hub:latest
docker-compose up -d # if using composeCheck image version:
docker inspect -f '{{ index .Config.Labels "org.opencontainers.image.version" }}' blackoutsecure/mlat-hub:latest- Docker Hub: blackoutsecure/mlat-hub
- 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.
