Experience the future of Amateur Radio Networking. Deploy a professional-grade DX Cluster node in minutes, not hours.
What is this? β’ Quick Start β’ Documentation β’ Configuration β’ Architecture β’ Security β’ FAQ
A DX Cluster is a network of servers that share real-time "spots", reports of rare or interesting amateur radio stations heard on the air. When someone hears a DX station, they post a spot to their local cluster node, which then propagates it to all connected nodes worldwide within seconds.
DXSpider is the software that runs most of these cluster nodes. Originally created in the late 1990s and written in Perl, it has been actively maintained and extended by the amateur radio community for decades. This project packages DXSpider into a Docker container so you can deploy it without manually installing Perl, CPAN modules, or configuring a server.
| Feature | Description |
|---|---|
| Instant Launch | Three lines of code and you are live. Literally. |
| Web Console | Built-in web terminal (ttyd) for easy administration without SSH. |
| Hardened | cap_drop: ALL + no-new-privileges, with only the minimal kernel capabilities needed. |
| Data Safety | User database, spots, and logs are safe in persistent bind-mounted volumes. |
| Universal | Multi-arch images: amd64, arm64, arm/v7, arm/v6, 386, ppc64le, s390x, from servers to Raspberry Pi Zero. |
| Automation | Built-in Cron and Startup script support. |
| Optional DB | Flat-file storage by default; optional MariaDB backend via compose profile. |
| CI Tested | Every commit is smoke-tested in GitHub Actions before publishing. |
This project includes a complete docs/ directory with beginner-friendly guides:
| Document | What it covers |
|---|---|
| Getting Started | Installation, first run, your first spot, basic commands |
| Configuration Reference | All environment variables and DXVars.pm explained |
| Connecting to Partner Nodes | Joining the global DX Cluster network |
| Filters | Controlling what spots and announcements you see |
| User Commands Reference | Full command list for users and sysops |
| Cron & Automation | Scheduled tasks, auto-reconnect, startup scripts |
| Database Backend | Optional MariaDB setup for spot logging |
| Security | Hardening guide, capabilities, best practices |
| Troubleshooting | FAQ, debugging, common problems and solutions |
New to DX Clusters? Start with Getting Started. It walks you through everything step by step.
graph TB
subgraph Host["Your Station (Host Machine)"]
Env[".env<br/>Configuration"]
LD["local_data/<br/>Users, Spots, Logs"]
Conn["connect/<br/>Partner Scripts"]
Cmd["cmd/<br/>Custom Commands"]
Msg["msg/<br/>Bulletins"]
Cron["local_cmd/<br/>Crontab + Local Cmds"]
Startup["startup<br/>Boot Commands"]
end
subgraph Container["Docker Container (Alpine 3.23)"]
direction TB
EP["entrypoint.sh<br/>(root: fix perms)"]
subgraph App["su-exec β sysop (UID 1000)"]
Cluster["cluster.pl<br/>DXSpider Core"]
Console["console.pl<br/>via ttyd"]
end
DB{"DB enabled?"}
end
subgraph Optional["Optional (compose profile)"]
MariaDB["MariaDB 11<br/>Spot Logging"]
end
subgraph External["Global Network"]
World["DX Cluster Network<br/>(Partner Nodes)"]
Users["Hams via Telnet<br/>:7300"]
Web["Sysop via Web<br/>:8080"]
end
Env --> EP
LD <--> App
Conn --> Cluster
Cmd --> Cluster
Msg --> Cluster
Cron --> Cluster
Startup --> Cluster
EP --> App
Cluster --> DB
DB -->|yes| MariaDB
DB -->|no| LD
Cluster <--> World
Users -->|TCP 7300| Cluster
Web -->|HTTP 8080| Console
- Linux: Docker & Docker Compose
- macOS: Docker Desktop
- Windows: Docker Desktop with the WSL2 backend and WSL integration enabled
- An amateur radio callsign: needed to identify your node
- Your Maidenhead grid square: find it at https://grid.radio/
The easiest way to get started is using our interactive installation script. It will check dependencies, clone the repository, prompt you for the necessary configuration, and launch the cluster automatically.
curl -sSL https://raw.githubusercontent.com/9M2PJU/9M2PJU-DXSpider-Docker/main/install.sh | bashNote
This guide uses docker compose (Docker Compose V2) which is the recommended method to manage the container and its configuration.
- If you prefer not to use Compose at all, you can use standard
dockercommands. See the Standalone Docker Run section below.
1. Get the Code
git clone https://github.com/9M2PJU/9M2PJU-DXSpider-Docker.git
cd 9M2PJU-DXSpider-Docker2. Configure Identity
Important
After cloning this repository, make sure all configurations are correct in your .env file before starting the cluster. An incorrectly configured node can cause routing loops or be rejected by the DXSpider network.
cp .env.example .env
vi .env
# At minimum, set:
# CLUSTER_CALLSIGN=YOURCALL-2
# CLUSTER_SYSOP_CALLSIGN=YOURCALL
# CLUSTER_LOCATOR=YOURGRID
# WEB_PASS=a-secure-password3. Pull & Launch!
docker compose pull # Download the pre-built image from GHCR (fast)
docker compose up -d # Start the clusterTip
docker compose pull downloads the pre-built multi-arch image from GitHub Container Registry. No local build is needed, and this is much faster than --build.
If you prefer to build locally (e.g., offline or custom modifications), use docker compose up -d --build instead.
You are live! Access your node:
- Telnet:
telnet localhost 7300(users connect here) - Web Console:
http://localhost:8080(useWEB_USER/WEB_PASSfrom.env)
Warning
A standalone DXSpider node will not receive any DX spots on its own! To get spots flowing, you must connect your node to a partner node on the DX Cluster network. You will need to find a partner to connect to. See Connecting to Partners for instructions.
Important
The container runs as UID 1000 internally. If your host directories are owned by a different UID, either:
- Change
SPIDER_UIDin.envto match your host UID, or chown -R 1000:1000 local_data connect cmd msgon the host before starting.
Once connected via telnet, try these:
| Command | What it does |
|---|---|
help |
Show available commands |
sh/dx |
Show recent DX spots |
sh/dx 20m |
Show spots on the 20-meter band |
sh/configuration |
Show all connected nodes and users |
set/name Your Name |
Set your name |
set/homenode YOURCALL-2 |
Set your home node |
bye |
Disconnect |
See the User Commands Reference for the full list.
Tip
For a complete walkthrough including posting your first DX spot, see Getting Started.
This project supports a wide range of CPU architectures, making it compatible with everything from high-end servers to Raspberry Pi Zero.
Docker (Linux): Supported Platforms
| Platform | Typical Hardware |
|---|---|
linux/amd64 |
Standard 64-bit PC / servers |
linux/arm64 |
Raspberry Pi 4/5, Apple Silicon, AWS Graviton |
linux/arm/v7 |
Raspberry Pi 2/3 (32-bit) |
linux/arm/v6 |
Raspberry Pi Zero/1 |
linux/386 |
32-bit x86 PCs / older servers |
linux/ppc64le |
IBM POWER servers |
linux/s390x |
IBM Z / LinuxONE mainframes |
Pre-built images are published automatically to GHCR on every push to main:
docker pull ghcr.io/9m2pju/9m2pju-dxspider-docker:mainIf you prefer not to use Docker Compose, you can run the cluster with a single docker run command. This is useful for quick testing or cloud environments like AWS ECS or Azure ACI.
Important
Always mount all the bind volumes shown below. Without them, your connect scripts, crontab, startup commands, MOTD, and data will be lost when the container is recreated or the image is updated.
docker run -d \
--name dxspider \
-p 7300:7300 \
-p 8080:8080 \
-e CLUSTER_CALLSIGN=N0CALL-2 \
-e CLUSTER_SYSOP_CALLSIGN=9M2PJU \
-e CLUSTER_LOCATOR=OJ03UD \
-e CLUSTER_LATITUDE=+03.08 \
-e CLUSTER_LONGITUDE=+101.41 \
-e CLUSTER_QTH="Kuala Lumpur" \
-e WEB_USER=sysop \
-e WEB_PASS=your-secure-password \
-v $(pwd)/local_data:/spider/local_data \
-v $(pwd)/connect:/spider/connect \
-v $(pwd)/local_cmd:/spider/local_cmd \
-v $(pwd)/cmd:/spider/cmd \
-v $(pwd)/msg:/spider/msg \
-v $(pwd)/startup:/spider/scripts/startup \
-v $(pwd)/motd:/spider/local_data/motd \
-v $(pwd)/motd_nor:/spider/local_data/motd_nor \
ghcr.io/9m2pju/9m2pju-dxspider-docker:mainTip
Persistent Data: Always mount all volumes. The local_data volume holds your user database and spots. The connect, local_cmd, startup, motd, and motd_nor volumes hold your node configuration. Without them, rebuilding the image would reset your settings to the defaults baked into the image.
Note
This is optional. Our GitHub Actions automatically build and push these images for you. Use this only if you want to build a custom version locally.
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/386,linux/ppc64le,linux/s390x \
-t ghcr.io/9m2pju/9m2pju-dxspider-docker:latest \
--push .To pin DXSpider to a specific commit (for reproducible builds):
docker buildx build \
--build-arg SPIDER_GIT_REF=<commit-sha> \
-t dxspider-custom:latest \
--load .Edit these in your .env file (copy from .env.example). All variables have sensible defaults. Only CLUSTER_CALLSIGN and CLUSTER_SYSOP_CALLSIGN are truly required.
For a complete explanation of every variable and how configuration works, see the Configuration Reference.
| Variable | Description | Default |
|---|---|---|
CLUSTER_CALLSIGN |
Required. The callsign of your node. | N0CALL-2 |
CLUSTER_SYSOP_CALLSIGN |
Required. Your admin callsign. | 9M2PJU |
CLUSTER_SYSOP_NAME |
Sysop's display name. | DXSpider Sysop |
CLUSTER_SYSOP_EMAIL |
Sysop contact email. | 9m2pju@hamradio.my |
CLUSTER_SYSOP_BBS_ADDRESS |
BBS forwarding address. | bbs@localhost |
CLUSTER_QTH |
Your QTH description. | DXSpider Node |
CLUSTER_LOCATOR |
Maidenhead grid square. | JO01aa |
CLUSTER_LATITUDE |
Latitude (decimal degrees). | 0 |
CLUSTER_LONGITUDE |
Longitude (decimal degrees). | 0 |
| Variable | Description | Default |
|---|---|---|
CLUSTER_PORT |
Telnet port (user-facing). | 7300 |
CLUSTER_SYSOP_PORT |
Web Console port (ttyd). | 8080 |
| Variable | Description | Default |
|---|---|---|
WEB_USER |
Username for the Web Console (HTTP basic auth). | sysop |
WEB_PASS |
Password for the Web Console. | changeme-web |
Leave CLUSTER_DB_HOSTNAME empty (default) to use flat-file storage. To enable MariaDB for spot logging:
| Variable | Description | Default |
|---|---|---|
CLUSTER_DB_HOSTNAME |
Database hostname. Set to db to use the compose service. |
(empty = disabled) |
CLUSTER_DB_NAME |
Database name. | spider |
CLUSTER_DB_USER |
Database username. | sysop |
CLUSTER_DB_PASS |
Database password. | sysoppassword |
CLUSTER_DB_PORT |
Database port. | 3306 |
CLUSTER_DB_ROOT_PWD |
MariaDB root password (compose db service only). |
changeme-root |
# Enable MariaDB:
# 1. Uncomment the DB lines in .env
# 2. Start with the with-db profile:
docker compose --profile with-db up -d --buildSee Database Backend for full setup instructions.
| Variable | Description | Default |
|---|---|---|
CLUSTER_DXSPIDER_BRANCH |
DXSpider git branch (mojo or master). |
mojo |
SPIDER_GIT_REF |
Pin DXSpider to a specific commit SHA. | (pinned default) |
SPIDER_UID |
UID for the sysop user inside the container. | 1000 |
OVERWRITE_CONFIG |
Set to yes to force-regenerate DXVars.pm and Listeners.pm on every start. |
no |
This container follows security best practices:
| Measure | How |
|---|---|
| No privileged mode | privileged: true is not used. The container runs with normal Docker isolation. |
| Minimal capabilities | cap_drop: ALL + only CHOWN, FOWNER, SETUID, SETGID added back. |
| No new privileges | security_opt: no-new-privileges:true prevents privilege escalation. |
| Non-root application | cluster.pl and console.pl run as the sysop user (UID 1000) via su-exec. |
| Root only for setup | The entrypoint starts as root only to fix volume permissions, then drops to sysop. |
| tini as PID 1 | Proper zombie reaping and signal forwarding. |
| No secrets in git | .env and local_data/ are gitignored. Use .env.example as your template. |
For a full hardening guide including firewall setup, SSH tunneling, and abuse reporting, see Security.
All your node's configuration and data lives on the host filesystem, not inside the container. Docker bind mounts map host directories into the container, overriding the default files baked into the image. This means:
- Rebuilding the image does NOT change your configuration. Your host files always take precedence over the image's defaults.
- Upgrading is safe. Pull a new image, rebuild, and restart. Your connect scripts, crontab, startup commands, MOTD, user database, and spots are all preserved on the host.
- Backups are easy. Just back up the project directory (especially
local_data/).
| Host Directory | What's Stored There | Bind-Mounted? |
|---|---|---|
local_data/ |
User database, spots, logs, debug data, filter lists | Yes |
connect/ |
Partner node connection scripts | Yes |
local_cmd/ |
Cron schedule (crontab) + local command overrides |
Yes |
startup |
Boot-time DXSpider commands | Yes |
motd |
Message of the day (registered users) | Yes |
motd_nor |
Message of the day (non-registered users) | Yes |
cmd/ |
Custom user commands | Yes |
msg/ |
Bulletins and private messages | Yes |
| File | Why |
|---|---|
/spider/local/DXVars.pm |
Generated by entrypoint.sh from .env variables |
/spider/local/Listeners.pm |
Generated by entrypoint.sh from CLUSTER_PORT |
If you need to persist these too (e.g., you manually edit DXVars.pm inside the container), set OVERWRITE_CONFIG=no in .env and add a bind mount for ./local:/spider/local:rw in compose.yaml.
Tip
To force-regenerate config from .env after changing variables, set OVERWRITE_CONFIG=yes in .env and restart. Set it back to no afterward.
To join the global DX Cluster network, your node needs to link to at least one other node. This is both a technical and social process. You need the other sysop's permission.
Important
You cannot simply connect to any node without permission. Contact the sysop of the node you want to link with, agree on a password, and then configure your connect script.
Quick steps:
- Create a file in
connect/named after the partner node (lowercase), e.g.,connect/gb7mbc - Add the connection logic (telnet hostname, port, login, password)
- Mark the partner as a node:
set/spider gb7mbc - Connect:
connect gb7mbc - Add auto-reconnect to
local_cmd/crontab
See Connecting to Partner Nodes for the complete guide.
Startup Tasks: Edit the startup file. Commands here run every time the container boots:
# Example 'startup' file content:
load/forward
set/spider gb7mbc
connect gb7mbcScheduled Tasks (Cron): Edit local_cmd/crontab. Standard cron format, but commands are Perl expressions:
# Reconnect to GB7MBC every 10 mins if link dropped
0,10,20,30,40,50 * * * * start_connect('gb7mbc') unless connected('gb7mbc')The default crontab also fetches bad-IP, bad-DX, bad-spotter, bad-node, and bad-word lists hourly from the dxspider_info repo to keep your node protected from known abusers.
See Cron & Automation for the complete guide.
Windows users can run 9M2PJU DXSpider Docker easily using WSL2 or traditional virtualization.
- Install Docker Desktop and enable the WSL2 Backend.
- Install a Linux distribution (e.g., Ubuntu) from the Microsoft Store.
- In Docker Desktop, enable Settings > Resources > WSL Integration for that distribution.
- Open the distribution's WSL terminal and run the Interactive Setup command above.
- Keep the installation inside the WSL2 filesystem (
~/dxspider) rather than on the Windows C: drive.
- Enable the Hyper-V feature in Windows.
- Create a Linux Virtual Machine.
- Install Docker and follow the standard Linux instructions.
My container keeps restarting!
Check the logs immediately:
docker compose logs -fCommon issues:
- Invalid characters in
.env(e.g., unquoted spaces in values). - Port 7300 is already in use by another application.
- Bind-mounted directories owned by a UID other than 1000 (set
SPIDER_UIDto match, orchown -R 1000:1000the directories).
How do I access the internal shell?
If you need to run manual spider commands or debug internally:
docker compose exec dxspider sh
# Then run commands like:
/spider/perl/console.plHow do I update to the latest version?
Simple. We built this to be easy.
git pull # Get latest docker configs
docker compose pull # Download the latest pre-built image from GHCR
docker compose up -d # Restart with the new imageYour data in local_data/ will remain safe.
If you build locally instead of pulling, use
docker compose up -d --buildin place of thepull+upsteps.
Permission denied errors on volumes?
The container expects bind-mounted directories to be writable by UID 1000. Fix with:
chown -R 1000:1000 local_data connect cmd msgOr set SPIDER_UID in .env to match your host user's UID.
How do I force-regenerate the configuration?
Set OVERWRITE_CONFIG=yes in your .env and restart:
docker compose down
docker compose pull
docker compose up -dSet it back to no afterward to preserve manual edits.
For more troubleshooting, see the Troubleshooting Guide.
To understand why this project exists, we must look back at the rich history of Amateur Radio networking.
In the late 1980s, PacketCluster software revolutionized DX hunting. Before this, DX spots could only be shared via voice repeaters or local shouting. This software allowed spots to be distributed over Packet Radio (AX.25) networks. A "Sysop" would run a node, and users would connect via radio to see a live stream of DX spots.
By the late 1990s, the internet was emerging, and the original DOS-based PacketCluster software was showing its age. A new, more flexible solution was needed to bridge the gap between RF networks and the Internet.
Perl was chosen for its robust text handling and modularity, and DXSpider was born. It was designed to be:
- Compatible: It spoke the exact same protocol as the older software, so users didn't need to change their client software.
- Scalable: It could handle hundreds of simultaneous internet connections, something the old PC-based clusters couldn't dream of.
- Open: It allowed the community to contribute and extend the software.
Today, DXSpider powers a vast majority of the world's DX Cluster nodes, silently processing millions of spots a year and keeping the global amateur radio community connected. 9M2PJU-DXSpider-Docker is simply the next step in this evolution, packaging this history into a container for the future.
We love community involvement!
- Fork it.
- Create your feature branch (
git checkout -b feature/cool-thing). - Commit your changes.
- Push to the branch.
- Create a Pull Request.
Tip
Every PR is automatically smoke-tested by CI. The cluster must boot and listen on port 7300 before images are published.
Distributed under the GNU General Public License v3.0. See LICENSE for more information.
Enjoying the project? Star us on GitHub!
If you find this project useful, you can support its development:
π§ Contact: 9m2pju@hamradio.my Β |Β π Website: hamradio.my
Designed with care by 9M2PJU 73 and Good DX!
