A self-hosted, Docker-native network diagnostic tool for continuous ICMP traceroute with real-time visualization. Think PingPlotter Pro, but open-source and designed to run on any server you control.
TraceAnywhere answers the question: "Where between point A and point B is the network broken or degraded?"
It runs persistent traceroutes from the host where it's deployed, storing results in a local database and streaming them to a web UI with per-hop latency charts, jitter bands, and packet loss indicators. Leave it running for hours or days to catch intermittent routing issues that one-off diagnostic tools miss.
Commercial tools like PingPlotter Pro work well from a desktop, but they can't run unattended on a headless server in a data center. Standard CLI tools (mtr, traceroute) give you a snapshot but no history. TraceAnywhere fills the gap: deploy it anywhere Docker runs, point a browser at it, and get persistent, visual traceroute data from the server's perspective.
Built by Volantic Systems as part of ongoing network infrastructure work managing multi-site server deployments across data centers in the US and Europe.
- Continuous traceroute via
mtrwith configurable probe intervals (1-300 seconds) - Real-time web UI with WebSocket streaming, no polling
- Per-hop timeline charts with green probe dots, red dashed average line, and jitter range band (min/max fill)
- Summary bar chart showing average latency across all hops at a glance
- Sparkline trend lines in the hop table for quick visual triage
- Multiple concurrent traces in browser tabs, each with independent state
- Trace history auto-saved to the database with resolved IP addresses
- JSON export of full trace data for offline analysis or sharing
- Single container deployment with Docker Compose
- Non-root execution inside the container;
CAP_NET_RAWscoped to themtr-packetbinary viasetcap
git clone https://github.com/VolanticSystems/trace-anywhere.git
cd trace-anywhere
docker compose up -dOpen http://localhost:7890 in a browser. Enter a hostname or IP and click Trace.
- Docker and Docker Compose
- The host must be able to send ICMP packets (most servers can; some cloud VPCs restrict this)
Browser (SPA)
|
| REST API + WebSocket
v
FastAPI (Python 3.12)
|
|-- ProbeManager --> mtr-tiny (ICMP probes, CAP_NET_RAW)
|-- SQLite (WAL mode, file-based migrations)
|-- Static file server (serves the frontend)
Backend: FastAPI with async SQLite (aiosqlite). The ProbeEngine interface uses dependency injection so the mtr integration can be swapped for a fake engine in tests.
Frontend: Vanilla JavaScript SPA with Chart.js for visualization. No build step, no framework, no node_modules.
Probe engine: Wraps mtr --report --report-wide --report-cycles 1 --no-dns and parses the text report output. The mtr-tiny package in Debian/Ubuntu produces text reports (not JSON), so the parser uses regex extraction rather than JSON parsing.
Database: SQLite with WAL journaling for concurrent read/write. Schema managed via numbered SQL migration files (backend/migrations/). Tables: trace, trace_result, hop_result, saved_target, schema_version.
The default configuration works out of the box. The app listens on port 7890 inside and outside the container.
# docker-compose.yml
services:
traceanywhere:
build: .
ports:
- "7890:7890" # Change the left side to use a different host port
cap_add:
- NET_RAW # Required for ICMP probes
volumes:
- trace-data:/app/data # Persists SQLite database across restarts
restart: unless-stoppedAll endpoints return JSON. Errors use a consistent {error, message, category} format.
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check (mtr available, DB writable) |
| POST | /api/traces |
Start a new trace |
| GET | /api/traces |
List all traces |
| GET | /api/traces/{id} |
Get trace details |
| PATCH | /api/traces/{id} |
Pause, resume, or stop a trace |
| DELETE | /api/traces/{id} |
Delete a trace and its results |
| GET | /api/traces/{id}/results |
Get probe results (supports time range + limit) |
| GET | /api/traces/{id}/export |
Export full trace data as JSON |
| GET | /api/targets |
List saved target history |
| DELETE | /api/targets/{id} |
Delete a saved target |
| DELETE | /api/targets |
Clear all target history |
| WS | /ws/traces/{id} |
Real-time probe result stream |
trace-anywhere/
backend/
app/
main.py # FastAPI app, routes, WebSocket handler
database.py # SQLite implementation, migrations
manager.py # ProbeManager, probe loop orchestration
probe.py # ProbeEngine ABC, MtrProbeEngine, FakeProbeEngine
models.py # Pydantic models (Trace, HopResult, etc.)
validation.py # Target input validation
migrations/ # Numbered SQL schema files
requirements.txt
frontend/
static/
index.html # Single-page application
css/style.css # Dark theme UI
js/app.js # Client-side logic, Chart.js integration
Dockerfile
docker-compose.yml
SPEC.md # Full project specification
DEFERRED.md # Future improvements (not needed yet, not forgotten)
Why mtr instead of raw ICMP? mtr handles the probe/response cycle, TTL incrementing, and loss calculation reliably. Reimplementing that in Python would be fragile and add no value.
Why SQLite? A single-file database that needs zero administration is the right choice for a tool that ships as one Docker container. WAL mode gives adequate concurrent performance for this workload.
Why vanilla JS? The frontend is a single page with a hop table and two charts. React/Vue would add a build step, 200KB of framework, and no meaningful capability. Chart.js is the only dependency.
Why non-root with setcap? ICMP requires CAP_NET_RAW. Rather than running the entire container as root, the capability is granted only to the mtr-packet binary. The Python process runs as an unprivileged user.
See DEFERRED.md for planned improvements, organized by phase:
- Phase 2: Environment variable configuration, data retention policies, WebSocket reconnection, accessibility
- Phase 3: Target allowlists, rate limiting, Prometheus metrics, ASN/GeoIP enrichment
- Phase 4: CI/CD pipeline, API versioning, WCAG 2.1 AA compliance, keyboard shortcuts
MIT