# ADSB-Ultrafeeder: an all-in-one ADS-B receiver container

> ADSB-Ultrafeeder bundles readsb, tar1090, graphs1090, autogain, a multi feeder and a built-in MLAT hub into a single Docker image, configured almost entirely through environment variables. It suits a station operator with an RTL-SDR on the same host who wants a local map, aggregator forwarding and MLAT in one container.

**sdr-enthusiasts/docker-adsb-ultrafeeder** — ADSB-Ultrafeeder is an all-in-one ADSB container with readsb, tar1090, graphs1090, autogain, multi-feeder, and mlat-hub built in

- Repository: https://github.com/sdr-enthusiasts/docker-adsb-ultrafeeder
- Stars: 587 · Forks: 65
- Language: Shell
- License: GPL-3.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/sdr-enthusiasts-docker-adsb-ultrafeeder

## One container for decoding, mapping, forwarding and MLAT

adsb-ultrafeeder is described as an ADS-B data collector container, and the README lists what it is for in six bullets. It retrieves ADS-B data from your SDR or other device. It displays it on a local map, with options to show tracks, heatmaps and system performance graphs. It forwards the data to one or more aggregators using the BEAST, BEAST-REDUCE or BEAST-REDUCE-PLUS format. It sends MLAT data to those aggregators. It receives and consolidates MLAT results data through the built-in `mlathub`. And it interfaces with external visualisation tools such as Grafana using statistics data available in InfluxDB and Prometheus format.

That last pair of capabilities is what makes the container more than a decoder. A station that only wants raw aircraft positions needs readsb. A station that also wants a map, a heatmap of where aircraft have been, a performance view of the decoder itself, a feeder to a public aggregator and MLAT position resolution needs five programs, and this image assembles them so that one configuration surface drives all of them.

The repository description puts it the same way. ADSB-Ultrafeeder is an all-in-one ADSB container with readsb, tar1090, graphs1090, autogain, multi-feeder and mlat-hub built in.

The licence is GPL-3.0, and the README carries a separate section on trademarks alongside the GPLv3 licence text, which is worth reading before you put the project name on a public page.

## The components underneath and the architectures built

The container is assembled from upstream projects rather than from scratch, and the README names each one. The foundation is the SDR-Enthusiasts Docker base image. Decoding is done by Wiedehopf's branch of `readsb`. The graphical interface is Wiedehopf's `tar1090`, and the performance graphs come from Wiedehopf's `graphs1090`. The MLAT client is the adsbxchange project.

That composition explains a lot about the behaviour people see. The fork of readsb that is used here is not the stock one, and the images that the container builds on, such as the tar1090 base and the base image with the MLAT client, are separate upstream artefacts with their own release cadence. When something behaves differently from a standalone readsb installation, the difference is usually there.

The image is built for three architectures: linux/amd64, linux/arm/v7 which is also described as linux/armhf, and linux/arm64. That matters for the common case of a small always-on station, because a single board computer with an ARM processor can run the same container as a server, and the alternative of building separate images for each architecture is a lot of work for an image that already contains several compiled programs.

The Dockerfile shows how the build is staged. A first stage compiles a small distance helper from C with static linking, and a second stage starts from the tar1090 base image, installs the runtime packages, copies the helper into /usr/local/bin and stamps the version into the image. The build also sets a hadolint ignore for the environment variable whose name contains PRIVATE, with a comment explaining that the check matches on the variable name regardless of its value.

## Getting it running with compose and an environment file

The quick start is a compose file and an environment file, and both are in the repository. The README says an example `docker-compose.yml` file can be found in the repository and that the accompanying environment variable values are defined in the `.env` file in the same place.

Once you have installed Docker, the documented path is short. You create a directory for the deployment, change into it, and fetch the compose file.

```bash
sudo mkdir -p -m 777 /opt/adsb
cd /opt/adsb
wget https://raw.githubusercontent.com/sdr-enthusiasts/docker-adsb-ultrafeeder/main/docker-compose.yml
```

The service definition in the compose file is where the important settings live, and it is worth reading before you edit it.

```yaml
  ultrafeeder:
    image: ghcr.io/sdr-enthusiasts/docker-adsb-ultrafeeder
    # Note - if you want to enable telegraf for use with InfluxDB/Prometheus and Grafana,
    # use the following image instead:
    # image: ghcr.io/sdr-enthusiasts/docker-adsb-ultrafeeder:telegraf
    container_name: ultrafeeder
    hostname: ultrafeeder
    restart: unless-stopped
    device_cgroup_rules:
      - "c 189:* rwm"
    ports:
      - 8080:80 # to expose the web interface
      - 9273-9274:9273-9274 # to expose the statistics interface to Prometheus
    environment:
      # --------------------------------------------------
```

Two details in that block cause most first-time problems. The device_cgroup_rules entry with the c 189:* rwm pattern is what lets the container talk to a USB device, so without it the decoder sees no radio. And the port mapping of 8080 to 80 is the web interface, while 9273 and 9274 are the statistics interface exposed to Prometheus, so a Prometheus scrape should point at 9273 rather than at the web port.

The image reference is the registry path ghcr.io/sdr-enthusiasts/docker-adsb-ultrafeeder, and a comment in the compose file points out that if you want telegraf for InfluxDB and Prometheus with Grafana, you should use the same image with the telegraf tag instead.

## ULTRAFEEDER_CONFIG is the wiring between the parts

The environment section of the compose file shows how the pieces are connected to each other, and the structure is worth understanding because it is unusual.

```yaml
      # general parameters:
      - LOGLEVEL=${LOGLEVEL-verbose}
      - TZ=${FEEDER_TZ}
      # --------------------------------------------------
      # SDR related parameters:
      - READSB_DEVICE_TYPE=rtlsdr
      - READSB_GAIN=auto
      - READSB_RTLSDR_DEVICE=${ADSB_SDR_SERIAL}
      - READSB_RTLSDR_PPM=${ADSB_SDR_PPM}
      #
      # --------------------------------------------------
      # readsb/decoder parameters:
      - READSB_LAT=${FEEDER_LAT}
      - READSB_LON=${FEEDER_LONG}
      - READSB_ALT=${FEEDER_ALT_M}m
```

The ULTRAFEEDER_CONFIG value is a semicolon separated list of constructs, and the README has a whole section on it, including separate documentation for the `adsb` construct, which connects to an ADS-B data source or sends ADS-B data to another host or port, and the `mlat` construct, which sends MLAT data to an MLAT server. There is also a section on integrating external MLAT return data with Ultrafeeder, and a parameters section for ULTRAFEEDER_CONFIG itself.

The comments in the compose file explain the rules for the list. You remove the entries you are not using or feeding, and each line must end with a semicolon, with the exception of the last line, which should not have one. That is a strict format, and a missing semicolon is the kind of error that stops a feed rather than raising an error, which is why it is worth checking the end of each line the first time you edit it.

The lines themselves are compact source specifications. `adsb,dump978,30978,uat_in` reads ADS-B from a dump978 instance on port 30978 and injects UAT data, and `adsb,feed.adsb.fi,30004,beast_reduce_plus_out` sends data to feed.adsb.fi on port 30004 in the BEAST-REDUCE-PLUS output format. The same construct is used in both directions, which is why the section is titled around connecting to a data source or sending data to another host or port.

## tar1090, graphs1090 and the rest of the configuration surface

The README is a long configuration reference, and its table of contents shows how much surface there is.

For the web interface there is a `tar1090` configuration section, split into `config.js` settings for the title, the output, the map settings, the range rings, the expert options and the route display, plus a section on hints and one on using a locally modified tar1090 version. The `graphs1090` section covers its environment parameters and then four specific behaviours: enabling UAT data, enabling AirSpy graphs, enabling disk IO and IOPS data, and configuring the core temperature graphs. There is also a section on reducing disk IO for graphs1090, and a `timelapse1090` configuration section.

The decoder side has its own group of parameters. READSB_DEVICE_TYPE is set to rtlsdr in the compose file, READSB_GAIN to auto, READSB_RTLSDR_DEVICE to the value of ADSB_SDR_SERIAL, and READSB_RTLSDR_PPM to the value of ADSB_SDR_PPM. There are separate sections for connecting to an SDR or other hardware device with mandatory and optional parameters, and a section on Ultrafeeder AutoGain for RTLSDR devices, which offers two paths: the built-in autogain in readsb, which the README marks as recommended, or the original AutoGain algorithm.

The location settings sit in the same block. READSB_LAT and READSB_LON take the values of FEEDER_LAT and FEEDER_LONG, READSB_ALT takes FEEDER_ALT_M with an m suffix for metres, and READSB_RX_LOCATION_ACCURACY is set to 2. If the location is wrong, MLAT is wrong, and the location can be updated later with GPSD, which the README covers with instructions for GPS hardware and gpsd drivers and optional parameters that regulate the restart of mlat-client when the location changes.

## Feeding Grafana from the statistics interface

The metrics section of the README covers the Grafana and Prometheus and InfluxDB integration, and there is a separate README-grafana.md file in the repository for it.

The mechanism is that Ultrafeeder can output statistics in Prometheus format and in InfluxDBv2 format, and the compose file carries the matching image variant. The comment in the compose file is explicit about it: if you want to enable telegraf for use with InfluxDB, Prometheus and Grafana, use the image with the telegraf tag.

That is a clean way to add metrics without restructuring the container. The alternative is to have a separate telegraf container reading the statistics ports, which keeps the base image smaller but means the collection lives outside the image and has to be versioned and configured on its own.

The tar1090 web interface also has a section on message decoding introspection, which is the feature that helps when a signal is being received but not decoded as expected. The README covers web pages, paths, offline maps and logging as well, so the container is usable with no internet access for the map tiles if you supply them.

## Where this container is the wrong answer

The first and most obvious case is a user without a receiver. This container is a station appliance. It expects a USB SDR or other hardware on the same host, it needs the device cgroup rule to reach it, and without a radio there is nothing to decode. If you want to see aircraft, a hosted feed or a receiver with built-in networking is a different purchase.

The second case is a deployment that wants the parts separately. The image is deliberately a bundle, which means the version of readsb, tar1090, graphs1090 and the MLAT client inside it are fixed at build time. A user who wants to run tar1090 on the host and readsb in a container, or who wants to patch one component, is fighting the design. The README does accommodate some of this, with a section on using a locally modified tar1090 version and an alternate configuration method with `READSB_NET_CONNECTOR`, but the default path is the bundle.

The third case is a Windows desktop. The image is Linux, the documented quick start assumes Docker on a Linux host, and the compose file grants raw USB access through a Linux cgroup rule. A Windows user would need a Linux host or a virtual machine, and the README does not describe a Windows path.

The honest summary is that this project optimises for the station operator who wants one thing to run and one configuration file to edit, and it pays for that with flexibility. That is the right trade for a fixed installation at home or at a small site, and the wrong one for a platform team that wants to compose services.

## Conclusion

ADSB-Ultrafeeder suits an ADS-B station operator with a USB SDR on a Linux host who wants one container to do decoding, mapping, forwarding and MLAT, and who is comfortable editing a compose file and an environment file. It does not suit a user without an SDR, a user who wants a hosted service, or a Windows host that cannot run the container. Verify first that your receiver is an RTL-SDR or other supported device, set the device serial in the environment file so the container picks the right dongle, and read the ULTRAFEEDER_CONFIG section carefully, because its semicolon separated format is where a mistake silently stops data from being forwarded.

## FAQ

### What does the ADSB-Ultrafeeder container include?

It bundles readsb, tar1090, graphs1090, autogain, multi-feeder and a built-in mlat-hub, so that decoding, the local map, the performance graphs, aggregator forwarding and MLAT are configured from one place.

### How do I start ADSB-Ultrafeeder for the first time?

Install Docker, create a deployment directory, fetch the example docker-compose.yml from the repository and edit the .env file that goes with it. The compose file maps port 8080 to 80 for the web interface and 9273 and 9274 for the statistics interface used by Prometheus.

### How is the USB receiver passed into the container?

The compose file sets device_cgroup_rules to the c 189:* rwm pattern so the container can reach the USB device, and the environment sets READSB_DEVICE_TYPE=rtlsdr with READSB_RTLSDR_DEVICE taken from ADSB_SDR_SERIAL so the right dongle is selected.

### What is the ULTRAFEEDER_CONFIG environment variable for?

It holds the connections between the parts of the stack, as a semicolon separated list of adsb and mlat constructs. The README documents the adsb construct for connecting to a data source or sending data to another host or port, and the mlat construct for sending MLAT data to a server.

### How is ADSB-Ultrafeeder licensed?

The project is released under the GPL-3.0 licence, with the licence text in the LICENSE file in the repository, and the README also includes a section on trademarks alongside the GPLv3 licence terms.

## Sources

- [Issues](https://github.com/sdr-enthusiasts/docker-adsb-ultrafeeder/issues)
- [License: GPL-3.0](https://github.com/sdr-enthusiasts/docker-adsb-ultrafeeder/blob/main/LICENSE)
- [README](https://github.com/sdr-enthusiasts/docker-adsb-ultrafeeder/blob/main/README.md)
- [sdr-enthusiasts/docker-adsb-ultrafeeder on GitHub](https://github.com/sdr-enthusiasts/docker-adsb-ultrafeeder)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sdr-enthusiasts-docker-adsb-ultrafeeder
