Snitches on every interesting aircraft that dares fly near your locations — military jets, government spooks, emergency squawks, sketchy low-flyers, or whatever the fuck you tell it to watch for. Monitor multiple locations at once — your house, your office, grandma's house, Area 51, whatever. Rats them out straight to your Telegram or webhook like a paranoid neighbor with radar.
No SDR required. No antenna. No hardware. Just an internet connection and a config file. Alerts via Telegram and/or webhooks. Works anywhere on the fuckin planet — and for as many places as you want.
- 🚀 Quick Start
- ⚙️ Configuration
- 🤖 Telegram Setup
- 🗃️ Plane-Alert-DB Lists
- 📁 Project Structure
- 📝 License
# grab the example config and edit it
# with your location + notification settings
curl -sL \
https://raw.githubusercontent.com/psyb0t/docker-planesnitch/main/config.yaml.example \
-o config.yaml
# optional: download CSV watchlists for
# military/gov/police tracking
# (see Plane-Alert-DB section below)
# mkdir -p csv
# BASE=https://raw.githubusercontent.com/sdr-enthusiasts/plane-alert-db/main
# curl -sLo csv/plane-alert-mil.csv $BASE/plane-alert-mil.csv
# curl -sLo csv/plane-alert-gov.csv $BASE/plane-alert-gov.csv
# curl -sLo csv/plane-alert-pol.csv $BASE/plane-alert-pol.csv
# curl -sLo csv/plane-alert-civ.csv $BASE/plane-alert-civ.csv
# curl -sLo csv/plane-alert-pia.csv $BASE/plane-alert-pia.csv
# curl -sLo csv/plane-alert-db.csv $BASE/plane-alert-db.csv
# let it rip — without CSV watchlists
docker run \
-v ./config.yaml:/app/config.yaml:ro \
psyb0t/planesnitch
# or with CSV watchlists — mount your csv/ dir
docker run \
-v ./config.yaml:/app/config.yaml:ro \
-v ./csv:/csv:ro \
psyb0t/planesnitch
# full setup with persistent aircraft-type image cache
docker run \
-v ./config.yaml:/app/config.yaml:ro \
-v ./csv:/csv:ro \
-v ./images:/images \
psyb0t/planesnitch2026-03-07 22:25:39 [planesnitch] INFO planesnitch starting
2026-03-07 22:25:39 [planesnitch] INFO health endpoint listening on :8080
2026-03-07 22:25:40 [planesnitch] INFO watchlist military: 8709 aircraft loaded
2026-03-07 22:25:40 [planesnitch] INFO watchlist government: 1743 aircraft loaded
2026-03-07 22:25:40 [planesnitch] INFO fetched 14 aircraft
2026-03-07 22:25:40 [planesnitch] INFO ALERT [Military Spotter] [home] ae07e1 TEDDY64
Single YAML file. Define watchlists (what to snitch on), alert rules (when to lose your shit), and notification targets (where to scream about it).
Control how altitude, distance, and speed show up in alerts and webhook payloads:
display_units: aviation # default| Preset | Altitude | Distance | Speed |
|---|---|---|---|
aviation |
ft | nm | kts |
metric |
m | km | km/h |
imperial |
ft | mi | mph |
Define one or more named locations. Each location has coordinates and a search radius.
Units: Any distance or altitude value in the config accepts a unit suffix:
km,mi,nm,ft,m. They all convert internally — use whatever makes sense.radius: 100miandmax_altitude: 1kmboth work. Plain numbers without a suffix default to km for distances and ft for altitudes.
locations:
home:
name: "Home"
lat: 38.8719
lon: -77.0563
radius: 150km
area51:
name: "Area 51"
lat: 37.2350
lon: -115.8111
radius: 50nmEach location can have an optional name for pretty display in alerts. Falls back to the key if not set.
Where to get the goods. Use multiple sources — they fetch in parallel for each location and deduplicate by ICAO hex, keeping the entry with the most data.
Note:
adsb_fi,airplanes_live, andadsb_onereturn enriched data (full aircraft name, owner/operator, year).adsb_lolandultrafeederonly return raw ADS-B fields. When using multiple sources, planesnitch automatically keeps the richest entry for each aircraft.
sources:
# Public APIs — no hardware, no bullshit
- type: adsb_lol
- type: adsb_fi
- type: airplanes_live
- type: adsb_one
# Local ultrafeeder — if you're running your own receiver
- type: ultrafeeder
url: http://ultrafeeder:80/tar1090/data/aircraft.jsonTell the snitch what to look for. All types respect the location's radius — aircraft outside the radius are ignored regardless of type.
| Type | Matches On | Source |
|---|---|---|
all |
Every aircraft | Everything within the location's radius |
squawk |
Transponder squawk code | Inline list |
icao |
ICAO hex address | Inline list |
icao_type |
ICAO type designator (doc 8643) e.g. C17, B738 |
Inline list |
icao_csv |
ICAO hex from CSV | Local file in csv/ dir (plane-alert-db format) |
proximity |
Altitude filter | Location radius + altitude limits |
watchlists:
# The panic buttons
emergencies:
type: squawk
values: ["7500", "7600", "7700"]
# 8,709 military aircraft — the big boys
military:
type: icao_csv
source: plane-alert-mil.csv
# Government aircraft
government:
type: icao_csv
source: plane-alert-gov.csv
# Police / law enforcement
police:
type: icao_csv
source: plane-alert-pol.csv
# Stalk specific aircraft by hex
my_planes:
type: icao
values: ["4ca123", "a12345"]
# Stalk by aircraft type — any A400M, Rafale, or Alpha Jet
cool_jets:
type: icao_type
values: ["A400", "RFAL", "AJET"]
# Every single aircraft in range
everything:
type: all
# WTF just buzzed my house
low_flyers:
type: proximity
min_altitude: 0ft
max_altitude: 3000ftConnect watchlists to notifications. Optionally filter by locations — if omitted, all locations are checked. Cooldown so it doesn't spam the shit out of you about the same C-17 doing laps for 3 hours. Durations support s, m, h — e.g. 5m, 1h30m, 90s, or plain seconds:
alerts:
- name: "Emergency Alert"
watchlists: [emergencies]
cooldown: 1m
notify: [tg_emergencies, my_webhook]
- name: "Military Spotter"
watchlists: [military, government]
cooldown: 5m
notify: [tg_spotting]
# Only alert for this watchlist at specific locations
- name: "Everything at Home"
locations: [home]
watchlists: [everything]
cooldown: 1m
notify: [tg_main]Telegram — different alerts to different channels:
notifications:
tg_emergencies:
type: telegram
bot_token: "123456:ABC-DEF"
chat_id: "-100123456789"
tg_spotting:
type: telegram
bot_token: "123456:ABC-DEF"
chat_id: "-100987654321"When an aircraft has an ICAO type designator (field t), planesnitch fetches the matching aircraft image from doc8643.com and:
- attaches it to the Telegram message as a photo (caption = the alert text)
- embeds the JPEG bytes (base64) in webhook payloads as
image_base64
Images are cached in images/ (mount -v ./images:/images to persist across restarts). Misses are recorded as .notfound markers so types without images aren't re-fetched. The cache survives indefinitely — delete the dir to force a refresh.
To opt out per notification target (e.g. avoid blowing up a chat with photos, or cut webhook payload size on Home Assistant), set attach_image: false on the target. Default is true. If every target attached to a rule opts out, planesnitch skips the doc8643 fetch entirely for that alert — no wasted bandwidth or disk.
notifications:
tg_main:
type: telegram
bot_token: "..."
chat_id: "..."
attach_image: false # text-only alerts to this chatWebhook — POSTs a JSON array of alert objects per poll cycle:
notifications:
my_webhook:
type: webhook
url: "https://example.com/hook"
headers:
Authorization: "Bearer xxx"Webhook payload schema — always a JSON array, even for a single alert:
[
{
"alert": "Military Spotter",
"location": "Home",
"match": {
"reason": "icao_csv_match",
"watchlist": "military",
"info": {
"Registration": "94-0067",
"Operator": "USAF",
"Type": "BOEING C-17A Globemaster III",
"ICAO Type": "C17",
"CMPG": "Mil",
"Tag 1": "Cargo",
"Tag 2": "Strategic Airlift",
"Tag 3": "Freedom Delivery",
"Category": "US Military",
"Link": "https://w.wiki/..."
}
},
"units": {
"altitude": "ft",
"distance": "nm",
"speed": "kts"
},
"image_base64": "/9j/4AAQSkZJRgABAQ...", // null if no image cached
"aircraft": {
"hex": "ae07e1",
"flight": "TEDDY64",
"registration": "94-0067",
"type": "C17",
"description": "BOEING C-17A Globemaster III",
"owner_operator": "USAF",
"year": "1994",
"squawk": "1613",
"emergency": "none",
"altitude": 12350,
"lat": 37.9306,
"lon": -78.7019,
"speed": 413,
"track": 245.3,
"distance": 98.9
}
}
]The match object varies by watchlist type:
| Watchlist Type | Match Fields |
|---|---|
squawk |
{"reason": "squawk", "watchlist": "...", "squawk": "7700", "distance_km": 12.3} |
icao |
{"reason": "icao_match", "watchlist": "...", "distance_km": 12.3} |
icao_type |
{"reason": "icao_type_match", "watchlist": "...", "type": "C17", "distance_km": 12.3} |
icao_csv |
{"reason": "icao_csv_match", "watchlist": "...", "info": {"Operator": "..."}, "distance_km": 12.3} |
all |
{"reason": "all", "watchlist": "...", "distance_km": 12.3} |
proximity |
{"reason": "proximity", "watchlist": "...", "distance_km": 12.3} |
🔔 Emergency Alert
🚨 squawk 7700 (EMERGENCY)
✈️ RYR1234
🛩️ BOEING 737-800 | EI-ABC | 2015
💼 RYANAIR
📍 45.5000, 28.1000 | 3,200 ft
📏 6 nm from home
💨 280 kts
🗺️ https://globe.adsb.fi/?icao=4ca123
🔔 Military Spotter
✈️ TEDDY64
🛩️ BOEING C-17A Globemaster III | 94-0067 | 1994
💼 USAF
🏷️ USAF — USAF
📍 37.9306, -78.7019 | 12,350 ft
📏 99 nm from home
💨 413 kts
📡 squawk 1613
🗺️ https://globe.adsb.fi/?icao=ae07e1
Click the link, watch the bastard in real time on globe.adsb.fi.
- Message @BotFather, send
/newbot, get a token - For personal alerts: message your bot, then check
https://api.telegram.org/bot<TOKEN>/getUpdatesfor your chat ID - For channel alerts: add bot as admin, post something, check
getUpdatesfor the channel ID (starts with-100)
Uses the community-curated lists from sdr-enthusiasts/plane-alert-db — 15,000+ aircraft catalogued by the fine degenerates of the plane spotting community:
| List | Count | File |
|---|---|---|
| 🎖️ Military | 8,709 | plane-alert-mil.csv |
| 🏛️ Government | 1,743 | plane-alert-gov.csv |
| 🚔 Police | 932 | plane-alert-pol.csv |
| 4,530 | plane-alert-civ.csv |
|
| 🔒 Privacy (PIA) | 94 | plane-alert-pia.csv |
| 📋 Everything | 15,914 | plane-alert-db.csv |
Download what you need into your csv/ directory:
mkdir -p csv
BASE=https://raw.githubusercontent.com/sdr-enthusiasts/plane-alert-db/main
curl -sLo csv/plane-alert-mil.csv $BASE/plane-alert-mil.csv
curl -sLo csv/plane-alert-gov.csv $BASE/plane-alert-gov.csv
curl -sLo csv/plane-alert-pol.csv $BASE/plane-alert-pol.csv
curl -sLo csv/plane-alert-civ.csv $BASE/plane-alert-civ.csv
curl -sLo csv/plane-alert-pia.csv $BASE/plane-alert-pia.csv
curl -sLo csv/plane-alert-db.csv $BASE/plane-alert-db.csvplane-alert-db.csv contains everything (mil + gov + pol + civ + pia) in one file. If you just want to watch all 15,000+ aircraft, use that one and skip the rest.
Re-download whenever you want fresh data. Or write your own CSV — just needs an ICAO hex column first.
├── planesnitch/
│ ├── __init__.py
│ ├── __main__.py # entry point + main loop
│ ├── config.py # config loading, unit conversion, constants
│ ├── sources.py # ADS-B API fetching + dedup
│ ├── geo.py # distance calculations
│ ├── watchlists.py # watchlist loading + matching
│ ├── alerts.py # alert checking + cooldowns
│ ├── images.py # doc8643 aircraft type image caching
│ └── notify.py # telegram + webhook formatting + sending
├── config.yaml.example # example config — copy to config.yaml and fill in your shit
├── csv/ # CSV watchlists go here, mounted to /csv
├── images/ # cached doc8643 type images, mounted to /images
├── run.sh # build + run in docker
├── requirements.txt
├── Dockerfile
└── README.md
Not 47 microservices. It watches planes and sends messages. That's it.
Environment variables:
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
INFO |
Set to DEBUG for verbose output |
PLANESNITCH_CONFIG |
config.yaml |
Path to config file |
PLANESNITCH_CSV_DIR |
/csv |
Path to CSV watchlist files |
PLANESNITCH_IMAGES_DIR |
/images |
Path to cached doc8643 aircraft-type images |
A health endpoint runs on port 8080. The Docker image includes a built-in healthcheck against it.
WTFPL — Do What The Fuck You Want To.