How I Setup Trunk Recorder Using Docker - Suggested Deployment For Calls Platform

Not open for further replies.


Jul 4, 2007
Hey all! Sorry in advance for the wall of text. I have Trunk Recorder up and running for a system in my area, and I wanted to provide a guide so that others can replicate my results and hopefully be encouraged to move over to the new platform. Also hopefully this can provide suggestions from others about improvement, including some of my own that I have made. I am not trying to claim to be the expert on this, I just have this setup working well. If you have suggestions on how I can do this better, I am all ears. My goal is to make this approachable for all.

I did this post here instead of posting to a blog and linking here because I didn't want it to appear as my goal was self-promotion, rather helping the community. So sorry for the multiple posts in a single thread, but I have a limit I am working against!

I personally have found that Docker is the easiest way to deploy Trunk Recorder, especially now that Broadcastify calls platform has native support. As new versions are released it takes about a minute to update the software and be back up and running. Trunk Recorder can be frustrating for those of us who don't regularly build software from source. I ran into a lot of hiccups when I first tried it as a lot of dependencies had changed versions and requirements, and required changes in the instructions to get it running, which was to say the least, frustrating.

This is also a good project for someone looking to learn Docker and Linux - fairly basic. So I will do my best to explain a lot of what is going on.

This will cover setting up monitoring for a trunking system since to feed Broadcastify calls that seems like it is likely to be the biggest use case. Won't get too much into the basics since you can do a quick search and find out about what Luke Berndt's amazing software is, what it can do, etc.


You will need at least one SDR receiver. In my case I am using two RTL-SDR v3 blog dongles. Although technically these can handle more than 2MHz, I have found that one for every 2MHz of spectrum is a good rule of thumb, you can probably push the envelope a bit, but I have found the lower the sample rate (which correlates to simultaneous bandwidth coverage) the better.

Another note for my setup - with the RTL dongles (and probably other SDRs) as well, a broadcast FM filter will go a long way to enhancing your sensitivity/coverage. In my setup, I actually use a NMO antenna on a TRAM NMO to N base mount fed to a RTL-SDR FM Broadcast Filter which is then fed to a 8-way cable TV amplifier/splitter. That will probably make some people freak out about impedance matching and such, but the reality is RTL-SDR were originally designed as TV receives which use 75 Ohm antennas, so the loss of the 50 Ohm antenna going into 75 Ohm designed splitter is likely negligible. I am not a super RF engineer, but if someone want to help me calculate what kind of loss I am seeing, I am willing to learn.

You will need a computer that is running Linux for this - I haven't tried using Docker on other platforms, but your biggest hurdle is going to be getting the device to be read inside the container. The container itself as of this writing is based on Ubuntu, and you need to pass the SDR as a readable device into the console, so if you can support those things I suppose it will work on other platforms. I have not tested this on a Raspberry Pi (yet) - if someone wants to test and report back, that would be helpful. I suspect there may be an issue since it's an ARM processor and I am using x86 platform in this case, but won't know until it's tested. I may go back and see if there is a way to copy the image file as it sits and switch to an ARM-supported container base.

Learning Your SDR's Behavior

The next step in this process is going to be getting your antenna and SDR setup. Will not go into excruciating detail for this part, but what I did was use SDR# inside of a Windows laptop to do that. You can do this whatever computer/software you want. Your goal is to make sure you can actually receive the control channel clearly and confirm the even minuscule amount of drift your dongle has. Even the RTL-SDR v3 with a <1ppm temperature controlled oscillator will benefit significantly from knowing how far the drift is. Make sure you run your receiver software on the control channel for several minutes to bring the SDR to operational temperature. Trunk Recorder accepts the drift value in Hertz, so be as accurate as you want. Basically just compare where the actual center of the control channel is on your SDR vs what is reported by the RadioReference Database. If using SDR# like I did, best thing to do is make sure you turn off the automatic frequency steps and and frequency correction so you can be as accurate as possible. Make sure you record these value(s) for later. You will need to do this for every device you plan to use as even two otherwise made exactly the same at the same time can have different offsets.

The more difficult thing you need to figure out is the gain requirement. Yes, Trunk Recorder has the ability to use AGC like SDR#, but even the documentation says this isn't as accurate. I haven't even tried it to see if it's reliable. All the SDR software out there I have seen offers the ability to manually adjust this. With the RTL-SDR v3 my gain setting is somewhere in the 40-ish dB range, but your values may be different.

In general when checking for drift, the best piece of advice I can give is tune your SDR until you can sit on the control channel for several minutes without hearing "hiccups" or "burps" of the control channel, and do your best to remove all static noise.

Getting Started

Now that you have your RF ingest setup, it's time to set up the computer. I used Ubuntu Server, which for all intents and purposes is Ubuntu minus GUI and a very bare minimum set of packages. For those dipping their toes into Linux via command line for the first time, either Ubuntu Server or Debian is probably going to be your best bet (again, just my humble opinion). I understand there is a lot of debate on this subject, but Debian in general seems to have enough adoption and Ubuntu seems to be the most popular distribution of a Debian-based Linux and hence why I chose it, but you do you.

You will need two key pieces of software - Docker and Docker-Compose. With Ubuntu as part of install, you can actually have the installer on more recent versions of Ubuntu handle this for you. I have no doubt in my mind that whatever flavor of Linux you choose, there are guides on how to install these.

If you are newer to Docker, before you get into Trunk Recorder, I would just play a little. Try setting up a basic "hello, world" web server, or some other service. Get familiar with the concepts, how to pass data in, etc. There are containers for just about everything out there. A fun one you might want to try is code-server which will spin up a container running Visual Studio Code you can access from a web browser. BTW Visual Studio Code is what I use for messing with a lot of these configuration files because most of the syntax is supported out of the box, but Notepad++ is another good option.

Next you need to get a copy of Trunk Recorder for Docker from its GitHub page. The reason I don't recommend pulling it from docker hub (for example, running "docker run robotastic/trunk-recorder") is because the configuration files you need to create are copied as part of the image creation process, and I found it's just easier to download the entire thing from GitHub and manually modify on your local desktop then copy to your Linux box. If I actually get free time (lol) I have the aspiration of forking it so that you can generate the config for use with Broadcastify via environment variables, but again that it a little advance for me without allocating a ton of time. If someone wants to take that project on, I will be happy to lend a hand.

Optional, But Very Helpful Step

Some flavors of Linux have RTLSDR (the software) already pre-loaded as part of repositories, but most software distro libraries should have some flavor of it out there. What I would do is unplug all of your dongles and one by one plug them into your Linux box. This will let you set the serial number for the dongles. The dongles come out the box with a hodgepodge serial number, so this helps a ton. In my example, I numbered them 101 and 102, then actually took a label maker and stuck the labels on the dongles. This ensured if I needed to troubleshoot a dongle I could. It also helps with configuring and tracking the unique settings needed to keep your SDR tuned in accurately.

The command to set the serial number on your dongle in Debian is:

rtl_eeprom -s 00000001
where the number after -s is the serial number you want to use. You may need to use -d, for exampled "-d 0" to declare using the first device it finds.

This also works for Windows with rtl_eeprom.exe available from several sources. That command is:

rtl_eeprom.exe -s 00000001

You can make the serial number a single digit, but I think it makes more sense to make it an 8 digit value with leading zeroes if you want to make it a single digit. I just used 00000101, 00000102, etc since odds are low I would plug in another SDR with that serial number.

Tell Broadcastify You Want To Send Calls

Next you need to get some important information so Broadcastify you actually want to send calls to the system. Follow the steps here to get system ID (which is not the same a trunking system ID) and an API key.


Jul 4, 2007
Configuring Your Setup

Now that you have your PC setup, your radios setup, you are just about ready to go.

Open up the Trunk Recorder (Docker) folder you downloaded earlier. There are pretty much only two files we will need to mess with to get our SDR dongle(s) receiving calls and forwarding up to Broadcastify servers - the config.json and the docker-compose.yml file.

Here is a sanitized version of my config.json:
    "sources": [
            "center": 857000000.0,
            "rate": 525000.0,
            "error": 600,
            "gain": 48,
            "digitalRecorders": 5,
            "modulation": "fsk4",
            "driver": "osmosdr",
            "device": "rtl=00000101"
        }, {
            "center": 859000000.0,
            "rate": 1250000.0,
            "error": 700,
            "gain": 48,
            "digitalRecorders": 15,
            "modulation": "fsk4",
            "driver": "osmosdr",
            "device": "rtl=00000102"
    "systems": [{
        "control_channels": [857865000, 857687500, 857887500, 85937500],
        "bandplan": "800_reband",
        "type": "smartnet",
        "shortName": "mysys",
        "broadcastifyApiKey": "95ab039b-408a-4d4a-bad9-dae273c6accd",
        "broadcastifySystemId": "00",
        "audioArchvie": "false",
        "hideEncrypted": "true",
    "broadcastifyCallsServer": ""

First, off - that's a randomly generated GUID and that systemid is hopefully invalid, so please don't try to reuse them - they won't work. Also, the frequencies are wrong, please don't @ me - this was all done to help force learning each option.

Now you can go to the Trunk Recorder page on GitHub and not only read about all the settings, including a lot that are not in this configuration file, but I put the bare minimum needed to get a SmartZone (or SmartNet) system on sending calls to Broadcastify. But let me walk through what's there and how I arrived at what I did.

So first, you need to setup the SDR receivers for the system(s) you are monitoring. The great thing about Trunk Recorder is the separation of SDRs and systems. This means in theory you could set up enough SDRs to cover all public safety spectrum and record everything from conventional systems to digital P25 trunked systems you can receive. Be aware that your processing power requirements for the computer go up the more you are covering and recording. Since this instance is only covering one system, I basically plotted out the spectrum of frequencies that I could potentially receive and set my SDRs. To do that, I sat in a spreadsheet and listed all the system frequencies in numerical order. Then I sat there and tried to figure out how I could cover ALL the system frequencies while keeping the sample rate of the SDRs low. The lower the sample rate, the less the CPU is used. You can certainly just give the RTLs a sample rate of 2000000.0, which would give me 2 MHz of bandwidth in view of the SDR, but I was working to make this as efficient as possible.

So for the source options, let's walk through the settings:
  • center - the center tuning of the SDR. So in my case, I thought about the range of frequencies one dongle would need to receive, and split the difference
  • rate - this is the sample rate. Without getting too into the weeds, for now you can think of this of the bandwidth in Hertz. Yes I know that's not what it actually is, but for the purposes of the RTL, it makes sense to describe it as that.
  • error - this is how far (in Hertz) you SDR is off. These are the values you figured out earlier. So in my case of SDR #101, it's off by 600Hz. So when I want to listen to 857000000Hz (857.000 MHz), the SDR is actually tuned to 857000600. Please remember this value can be positive or negative!
  • gain - this is the gain value I figured out was best for the SDR using SDR#.
  • digitalRecorders - this is the number of digital voice records I can potentially have used against this SDR at any given time. So the nice thing for this sample system is with the exception of some telemetry (data) talkgroups, all the talkgroups use digital voice. So I arrived at 5 for SDR #101 because the system has five trunking channels available within the spectrum covered by that SDR. Likewise this system has most of the channels in the spectrum covered by SDR #102, so that has 15 digital recorders. You want to make sure that between all the dongles you have enough digital recorders to cover all possible channels. Remember that if your system is P25 Phase II you may have TDMA, so you will need two digitalRecorders per potential TDMA channel. One of the things you don't see reflected in RadioReference is on TDMA systems, you can actually define which channels can be TDMA. So you may be monitoring a system that only has TDMA for some of the channels - that's why using a tool like Unitrunker to babysit the system for like a week can be enlightening. Since this is a SmartZone system I could also assign analogRecorders with a different value for talkgroups with analog voice, but the only things on this system using it are telemetry, and who wants to listen to that?
  • modulation - this is the modulation type for your system. This is not hard and fast but generally it should be fsk4 for SmartNet/SmartZone and qpsk for P25 Phase I and Phase II systems. So here's one of the issues you can now see - if you are covering a P25 and a SmartZone system with the same dongles, you will have issues since they likely use different modulation. So you would probably be best buying a couple extra dongles separate for P25 and SmartZone (or Conventional) systems.
  • driver - this is the driver being used by GNURadio. There are only two options, usrp being the other. You may have to do some research if not using an RTL to determine if you need to change.
  • device - this is how we identify the device if you have more than one. If you have more than one, you can do what I did, and identify by the new serial numbers I assigned my RTLs. Please check the Trunk Recorder GitHub for other examples and an explanation if needed.
Systems - these are the configuration settings for what we are actually monitoring. Yes you can have more than one. In the "examples" folder, you can see how you would setup more than one system. But let's walk through a trunking system setup:
  • control_channels - hopefully you are this far along and considering this setup, this is obvious what these are. Value in Hertz
  • bandplan - this is needed for SmartZone/SmartNet systems only - not P25. Weirdly, the default option is 800_standard so if I didn't declare this, that's what it would assume the bandplan is. So like most other 800MHz systems, this has been rebanded, so I declare it as such. You can also do splinter or custom band plans for 400MHz Type II systems, read the GitHub page for information on that.
  • type - smartnet - yes even though I am monitoring a SmartZone which is slightly different, you still set this to smartnet. p25 is an option if you are monitoring P25 Phase I or II, and of course conventional or conventionalP25 if you are trying to get into that.
  • shortname - name the system whatever you want - needed for files and diagnostics. Should be 4-6 letters, no spaces.
  • broadcastifyApiKey - the API key assigned to you
  • broadcastifySystemId - this is the system ID assigned by Broadcastify, not the actual trunking system ID (since you can send conventional system calls too).
  • audioArchive - Trunk Recorder by default sets this to true. I don't want to blow up my hard drive, so I set this to false. This just determines if recordings are saved after successful upload.
  • hideEncrypted - setting this hides information from the logs. Does not actually stop from recording encrypted calls (at least on SmartNet/SmartZone). I need some further clarification on this - I think it doesn't actually skip encrypted unless the system knows it's encrypted - which is different from the ability to encrypt calls. i.e. ADP/ARC4 is programmed on the subscriber, not the system so the system doesn't actually know the call is encrypted. Someone might want to correct me on this.
Finally broadcastifyCallsServer defines the URL to upload to. There is a staging/testing for trying your configuration and the production server, which this is the URL for.

Again, you can check the GitHub page to see all the potential configuration options.

Now that you have made your config.json file, copy that to your "config" folder. Do not change the name.

Next is the docker-compose file. Using docker with docker-compose is basic container orchestration - basically using the YAML file, you define how docker should manage the containers. The thing with docker compose is that you can use multiple YAML files, but only one can exist per directory and they should be named docker-compose.yml for simplicity. Here is mine:
version: '3'
    privileged: true
    build: .
      - callstore:/app/media
      - /dev/bus/usb:/dev/bus/usb
      - "/etc/localtime:/etc/localtime:ro"
      - ./config:/app/config
    restart: always

      type: tmpfs
      device: tmpfs

There is a sample YAML file included with Trunk Recorder for Docker, but I modified this in a couple ways to make it better for Broadcastify Calls. This pretty much can be reused without having to change it for your specific setup. But let's walk through it:

I defined the one service for trunk recorder I simply called "recorder". You notice build is just set to "." since we will be building the docker image locally using the DOCKERFILE contained within the folder. It needs to be privileged since it is accessing USB devices and talking back to them. For volumes I did a couple things. First I am passing a docker volume that gets created called "callstore". Will explain that in a bit - this replaces the media folder inside the container. Next, we pass /dev/bus/usb into the same location inside the container so that the container has access to the same USB devices. You could potentially use this to be more granular if you wanted to run multiple instances on the same computer. Next I passed in the local clock - not a big deal, but I do this on most of my containers to ensure the container has the correct timezone and local time and such. finally, we pass in the config file to the same location inside the container. Oh and I set the container to always restart in case of power failure while away.

Next is the big change that has been very helpful - in my docker compose file I defined a volume called "callstore" which uses the tempfs docker driver. What this does is basically create a directory to memory. I am not saving the recordings so when the container is destroyed, these disappear with it. If you wanted to save recordings, you would need to modify your config.json to archive the recordings, then pass in a local folder to save the files to. So what happens is this container powering my Trunk Recorder setup runs on a VERY active system, and the disk IO is a couple kbs. Also, memory usage for this system hovers around 150MB but disk IO remains in the 10's of MB of block input and under 1MB of block output since start. That basically means this container almost never writes to your local hard drive. If you have some old computer hardware lying around you could get some sort of CF/SD card adapter, take the painfully slow boot/install off of that, but once running you are not wearing out a memory card and not having to buy an SSD just for performance.

An Important Step You May Need To Follow

One common problem, especially if your Linux install has software that supports RTL SDR is you need to blacklist a module that supports the dongles. What this will do is prevent any other software from grabbing a hold of it before you can run it in Docker. To do this you need to make a file. I assume you have nano, a text editor installed in Linux for doing this and you are in Ubuntu, so steps may change based on Linux distro:
sudo nano /etc/modprobe.d/sdr-blacklist.conf

This will create the blacklist file. In it add the following:
Apache config:
# RTL SDR gets in the way of the TV tuners.
blacklist dvb_usb_rtl28xxu

Then close (ctrl + c) and agree to safe ("y" key) and you have a file that will blacklist the module on boot that has the kernel grab onto the device.

Thanks to this post on the rtl-sdr forum where I pulled the steps from.
Last edited:


Jul 4, 2007
Get Up and Running

Now that you have made it this far, you are ready to get going. Take the Trunk Recorder Docker folder (now is a good time to change its name if you want) where you put your custom config.json and your docker-compose.yml file. By this point, it's best to get used to managing your dedicated server via SSH. So if you need to read up on that real quick to learn how to do that.

I use WinSCP to copy the folder somewhere on the server. Then I remote in using SSH. From there, it's pretty simple. Just change directory (cd) until you get to the folder. Then run the following command to get Trunk Recorder started:
sudo docker-compose up

What this will do is build the image as dictated by the DOCKERFILE inside of the folder, and start the container with the configuration in the docker-compose.yml. You should be able to watch the output of Trunk Recorder fly by. You will be able to quickly tell if Trunk Recorder is able to find the control channel, and pretty quickly see recording operations start. From here any issues should be displayed and you will have to troubleshoot as appropriate.

Now that you are up and running, check the calls page as appropriate to confirm calls are uploading. Once done you can stop the container by running:
sudo docker-compose down

Now you are going to run it in detached mode - which is basically letting it go off and run on it's own. To do that run:
sudo docker-compose up

The "-d" denotes detached mode.

If you want to check on how the container is running, use:
sudo docker stats

Now you should be good to go. One of the big advantages of deploying the container this way is that even through restarts the version will remain the same.

Ongoing Maintenance

As said, the version of Trunk Recorder will be maintained through restarts, which is atypical for most docker home users since they typically pull from a repository (typically Docker Hub) so that you have an always up-to-date container on restart. It's worth watching the GitHub page for updates then deciding if you want to update. To get a new version you have to clear the image. To do that, stop your container then run:
docker image prune -a

If the container isn't stopped, the image you built will persist. But by stopping containers, then running this everything will be deleted to rebuilt. This includes not only TrunkRecorder but a bulk of the dependencies. By using Docker the actual repository you downloaded from Trunk Recorder states it's been about two years since last update, yet you will get the version of Trunk Recorder that was last updated (as of time of writing this post) less than a week ago.

Phew, that was quite a lot. Hopefully it inspired some more people to dive into the new calls platform.

Final Thoughts

I found this the best way to get up and running with Trunk Recorder. What is cool is I basically have a couple MB file I save and if the machine blows up, Linux borks, or I get bored and want to change distro or computer hardware, I just need to install Linux, Docker and Docker Compose, then copy my folder, plug in my dongle and I am good to go. Currently I am running this on a server I have in my home lab and I am already trying to figure out what other systems I can add since this is easy to now copy, reconfigure and deploy.

Feel free to add, or ask questions here. Again - I want to encourage folks to get deploying systems on the calls platform. The ability to make a playlist is already really slick way to listen to multiple systems at the same time, but even listening to one system on this platform allows you to listen to multiple talkgroups in near real-time without having to hear people talk over each other. Can't wait to see what else the platform has in store.
Last edited:


SDS100, Bearcatter co-author
Feb 14, 2010
Excellent guide. Just wanted to point out that I just did a lot of work on trunk-recorder’s Docker setup so that images now are built for multiple architectures (AMD64, armv6, armv7) and we have now made it the preferred install method, especially for constrained platforms like Raspberry Pi. You can get a Docker setup going within 20 minutes on Raspberry Pi now vs the previous 2.5 hours or so. See this Github issue for more information.


Jul 4, 2007
Beat me to it!

I saw your PR and subscribed to changes to patiently await your much needed update to the Docker “Friendliness”.

Thanks for your work - since I can’t edit the original post I think I’m going to modify the Wiki here to reference your changes.
Not open for further replies.