GeoMetrikks tails your reverse proxy's access log, looks every request up in GeoLite2, and draws it on a world map within seconds. Traffic analytics, a searchable history, and CrowdSec bans next to the traffic that caused them. On your own hardware.
Sources
The format is detected per file. Rotated files are followed. Archives, plain or gzipped, can be backfilled through the same pipeline with one command.
geometrikks-jsonnginxcombinedlog_format geometrikks_json escape=json
'{'
'"client_ip":"$remote_addr",'
'"timestamp":"$time_iso8601",'
'"method":"$request_method",'
'"path":"$request_uri",'
'"protocol":"$server_protocol",'
'"status":"$status",'
'"bytes":"$body_bytes_sent",'
'"host":"$host",'
'"referrer":"$http_referer",'
'"user_agent":"$http_user_agent",'
'"remote_user":"$remote_user",'
'"request_time":"$request_time",'
'"upstream_time":"$upstream_response_time",'
'"request_raw":"$request"'
'}';
access_log /config/log/nginx/access.log geometrikks_json;ACCESS_LOG_DIR=/var/log/nginx LOGPARSER_LOG_PATHS=/var/log/access/access.log
traefik-jsonaccessLog:
filePath: "/var/log/traefik/access.log"
format: json
fields:
headers:
names:
User-Agent: keep
Referer: keepACCESS_LOG_DIR=/var/log/traefik LOGPARSER_LOG_PATHS=/var/log/access/access.log
caddy-json(log_settings) {
log {
output file /var/log/caddy/access.log
format json
level INFO
}
log_append upstream_duration_ms {rp.upstream.duration_ms}
}
app.example.com {
import log_settings
reverse_proxy app:8000
}ACCESS_LOG_DIR=/var/log/caddy LOGPARSER_LOG_PATHS=/var/log/access/access.log
Screens
Live map. Markers, clusters or a heatmap; flat or globe. Routes fly to your home beacon.

Overview. Requests, visitors, bandwidth and errors for the range, with trends against the previous one.

Analytics. Volume, status mix, latency percentiles, bytes, top URLs per host, top user agents, top ASNs.

Access logs. Every request, server-paginated, with free-text search and filters for status, method, IP, host, country and source.

Geo logs. Traffic grouped by place: which locations send requests and how that changes over time.

IP inspector. One panel per address: counts, bytes, status mix over time, hosts and paths hit, user agents, the latest requests, and its CrowdSec status with a ban button.

Security. Active CrowdSec decisions cross-referenced with your own traffic.

Debug logs. The raw source lines behind every request, with parse failures called out.
And the rest
A WebSocket-backed tail on the access-logs page. New rows prepend and pause while you hover.
Every request, server-paginated, with free-text search and filters for status, method, IP, host, country and source.
One panel per address: counts, bytes, hosts and paths hit, other locations it resolved to, and scanner tells like a hosting ASN or a wide path spread.
Traffic grouped by place: which locations send requests, how that changes over time, and which clients keep coming back.
Rotated archives, plain or gzipped, through the same pipeline with one command. Checksum-deduped, so re-running is safe.
The raw source lines behind every request, with parse failures called out and linked to their request.

CrowdSec
Connect the Local API and the Security page joins active decisions with your own logs: the country, city and request count for every banned IP, a Banned badge in the tables, and a map overlay for banned addresses still showing up in the selected range.
# prints the API key
docker exec crowdsec cscli bouncers add geometrikks
CROWDSEC_LAPI_URL=http://crowdsec:8080
CROWDSEC_BOUNCER_API_KEY=<key>CrowdSec decides; a bouncer in front of your stack still does the blocking. GeoMetrikks shows and manages decisions, it does not block traffic. CrowdSec setup in the docsAgent mode
When traffic comes in through more than one proxy or host, run one full instance plus an agent next to each log file. The agent tails, parses, geolocates and writes locally. The full instance owns everything else: the UI, the API, migrations, the scheduler and CrowdSec.
Every writer publishes committed events over PostgreSQL LISTEN/NOTIFY, so the live map shows a request the moment any agent stores it. Each source gets its own home beacon, and every page filters by recording hostname.
An agent needs four things: the mode, database credentials for the shared instance, MaxMind credentials, and its log mount. No admin password, no UI, no migrations.
APP_MODE=agent DB_HOST=timescale.example.internal LOGPARSER_LOG_PATHS=/var/log/access/access.log LOGPARSER_HOST_NAME=edge-01Multi-source setup in the docs
Install
Docker on amd64 or arm64. Two files, one command, and the app is on port 8000.
# Docker. Images for amd64 and arm64. mkdir geometrikks && cd geometrikks curl -LO https://raw.githubusercontent.com/GilbN/geometrikks/main/docker-compose.yml curl -Lo .env https://raw.githubusercontent.com/GilbN/geometrikks/main/.env.example $EDITOR .env # set APP_ADMIN_PASSWORD, MaxMind key, CARTO key, log path docker compose up -d # open http://localhost:8000
Built with