Salaheldinaz
Table of contents
Adsb-History Self-Hosted

Adsb-History Self-Hosted

Turnstone is a full-stack application for querying historical ADS-B aircraft data — originally built by Bellingcat as an investigative journalism tool. It lets you filter aircraft positions by geographic region, altitude, speed, bearing, type, and more against a large historical dataset.

The original project requires manually setting up PostgreSQL with PostGIS, a Flask API, a Vue.js frontend, and Firebase authentication. That’s a lot of moving parts to coordinate before you can run a single query. My modified version of Turnstone adds a Docker-based deployment that brings it all together with a single command.

Turnstone search interface

The Turnstone query builder with bounding box selection and filters

What Changed

Docker

The PR adds Docker Compose orchestration for the entire stack, optional Firebase-free (“local”) mode, support for an external Postgres database, improved data loading scripts, and updated documentation.

Everything persistent lives under a single data/ directory in the repo root — heatmap files, Postgres data, modes CSV, query history backups, and optionally your Firebase service account key.


Quick Start (Local Postgres, No Firebase)

This is the easiest path. You get a fully self-contained stack with no Google accounts required.

1. Clone and configure

git clone https://github.com/salaheldinaz/adsb-history.git
cd adsb-history
cp docker/.env.example docker/.env

Both DISABLE_AUTH=1 and VITE_DISABLE_AUTH=1 are set by default in .env.example, so no changes are needed for a Firebase-free setup. All other defaults work out of the box.

2. Download heatmap data

The heatmap data comes from adsblol/globe_history. The download script handles fetching and extracting the split .tar.aa/.tar.ab archive:

./scripts/download-globe-history.sh data

When run from a terminal, the script is interactive — it shows a numbered catalog of all available prod releases and lets you pick by index, a date, or a date range:

Turnstone interactive heatmap downloader

When piped or run non-interactively (e.g. in CI), it downloads the latest release automatically. The script extracts each release into data/<tag>/ — note the directory name for step 4.

3. Start the stack

docker compose -f docker/docker-compose.yml up -d

This starts Postgres (with PostGIS), the Flask API, and the Nginx-fronted Vue frontend.

4. Load the heatmap data

To load all downloaded releases at once (bulk):

docker compose -f docker/docker-compose.yml run --rm data-loading /data

Or to load a single specific release:

docker compose -f docker/docker-compose.yml run --rm data-loading \
  /data/<RELEASE_DIR>/heatmap

Replace <RELEASE_DIR> with the tag directory from step 2 (e.g. v2026.04.15-planes-readsb-prod-0). The loader accepts gzip-compressed and raw binary files, handles big-endian format, and supports both YYYY-MM-DD and YYYY.MM.DD date directory naming.

5. Open the app

  • Frontend: http://localhost:8080
  • API: http://localhost:5000
Aircraft positions on the map

Query results shown as points on the map


Using an External Postgres Database

If you already have a PostGIS-enabled Postgres instance (e.g. on a NAS, a VPS, or a managed cloud database), you can point the stack at it instead of running a local container.

1. Set DOCKER_DATABASE_URL in your .env

Docker containers can’t reach localhost — use the actual host address. On Docker Desktop (Mac/Windows) that’s host.docker.internal; on Linux the compose file already sets extra_hosts for you:

DOCKER_DATABASE_URL=postgresql://user:password@host.docker.internal:5432/adsb

2. Initialize the external database (once)

./scripts/init-external-db.sh

This script is idempotent — safe to run multiple times. It applies docker/postgres/01_schema.sql and loads data/modes.csv if present. It also checks that PostGIS is installed and exits clearly if it isn’t.

3. Start with the external-db override

docker compose \
  -f docker/docker-compose.yml \
  -f docker/docker-compose.external-db.yml \
  up -d

The override file stubs out the local Postgres container so depends_on chains still work, while the API and data-loading containers connect via DOCKER_DATABASE_URL.

4. Load data the same way

# Bulk — all releases under data/
docker compose \
  -f docker/docker-compose.yml \
  -f docker/docker-compose.external-db.yml \
  run --rm data-loading /data

# Or single release
docker compose \
  -f docker/docker-compose.yml \
  -f docker/docker-compose.external-db.yml \
  run --rm data-loading /data/<RELEASE_DIR>/heatmap

Optional: Firebase Authentication

If you want to enable user accounts and Firestore-backed query history (the original upstream behaviour), add Firebase credentials:

  1. Create a project at console.firebase.google.com and enable Authentication.
  2. Download a service account JSON key and place it in data/firebase-service-account.json.
  3. In docker/.env, set:
    FIREBASE_SERVICE_ACCOUNT_KEY=/data/firebase-service-account.json
    FIREBASE_PROJECT_ID=your-project-id
    VITE_FIREBASE_API_KEY=...
    # (plus the other VITE_FIREBASE_* vars from .env.example)
    
  4. Set DISABLE_AUTH=0 and VITE_DISABLE_AUTH=0 (overriding the defaults).

When Firebase is disabled, the frontend uses a stub user and stores query history in the browser’s IndexedDB instead of Firestore.


Query History Backup & Restore

Whether you use Firebase or not, you can back up and restore your saved queries through the UI. Look for the “Save to data folder” and “Restore from data folder” buttons in the Query History panel. These write to/from data/query-history-backup.json via the API, so backups survive container restarts.

Query history panel

Saved queries with backup and restore options


Environment Variables Reference

VariableDefaultDescription
DOCKER_DATABASE_URL(unset)Full Postgres connection string for Docker containers (use host.docker.internal for a local DB)
DB_HOSTpostgresPostgres hostname (bundled container)
DB_PORT5432Postgres port
DB_NAMEadsbDatabase name
DB_USERadsbDatabase user
DB_PASSadsbDatabase password
DISABLE_AUTH1Set to 0 to enable Firebase on the backend
VITE_DISABLE_AUTH1Set to 0 to enable Firebase on the frontend
VITE_API_BASE_URL(empty)API URL as seen by the browser; leave empty for Docker (frontend proxies /api internally)
FIREBASE_PROJECT_ID(unset)Firebase project ID
FIREBASE_SERVICE_ACCOUNT_KEY(unset)Path to service account JSON inside the container
QUERY_HISTORY_BACKUP_PATH/data/query-history-backup.jsonWhere backup/restore writes query history
POSTGRES_PORT(optional)Override host-side port binding for Postgres
API_PORT(optional)Override host-side port binding for the API
FRONTEND_PORT(optional)Override host-side port binding for the frontend

Copy docker/.env.example to docker/.env to see all available options with comments.


Data Format Notes

The data loader was updated to handle a few quirks in the wild:

  • modes.csv format: Both the original 7-column format and the newer 11-column format (with owner and aircraft fields) are supported via a staging table and mapped insert.
  • Heatmap filenames: The loader accepts both 047 (no extension) and 00.bin.ttf47.bin.ttf.
  • Compression: Gzip-compressed binary input is handled transparently.
  • Byte order: Big-endian binaries are supported.
  • Date directories: Both YYYY-MM-DD and YYYY.MM.DD naming conventions work.

The repo is at github.com/salaheldinaz/adsb-history. If you run into issues with the Docker setup, open an issue or leave a comment.