Docker
eodia analytics is built as a single image from the repository’s Dockerfile. It contains
the API (which also serves the script and the collection endpoint), the interface and the
worker; the argument given to the container chooses what it runs. docker-compose.prod.yml puts
this image together with PostgreSQL and Caddy. All configuration goes through
environment variables.
The image
Section titled “The image”docker build -t eodia-analytics .It is based on Node 22. The applications run from their TypeScript sources with tsx; the
interface (next build) and the script (/a.js, /p.js, with esbuild) are compiled at build
time. Optionally, the build downloads the DB-IP Lite country database (CC BY 4.0 license),
which GEOIP_DB then points to.
| Role | Command | Port | What it does |
|---|---|---|---|
api | docker run eodia-analytics api | 4600 | REST /api/v1, authentication, /a.js, /p.js, /collect, /collect/server |
web | docker run eodia-analytics web | 3600 | the Next.js interface |
worker | docker run eodia-analytics worker | — | the job queue: partitions, retention, daily salt, generated views, export and erasure requests |
all (default) | docker run eodia-analytics | 3600, 4600 | api, web and worker in a single container |
In all mode, a small supervisor starts the three processes, prefixes their logs ([api],
[web], [worker]), forwards shutdown, and stops the whole container as soon as one of them
dies: the orchestrator then restarts it in full.
The production deployment
Section titled “The production deployment”docker-compose.prod.yml runs one service per role:
| Service | Image | Role |
|---|---|---|
db | postgres:17-alpine | the database: configuration, raw data, views (data volume) |
api | eodia-analytics | the API and collection; applies migrations on startup |
worker | eodia-analytics | the jobs (INPROCESS_WORKER=0 for the API) |
web | eodia-analytics | the interface |
caddy | caddy:2-alpine | automatic HTTPS, ports 80 and 443 |
docker compose -f docker-compose.prod.yml up -d --build # build and startdocker compose -f docker-compose.prod.yml logs -f api # follow the APIdocker compose -f docker-compose.prod.yml ps # status and health of the servicesdocker compose -f docker-compose.prod.yml down # stop (volumes are kept)docker/Caddyfile serves DOMAIN over HTTPS (Let’s Encrypt; ACME_EMAIL as the contact),
compresses responses, sets HSTS and a few security headers, then routes:
| Path | To |
|---|---|
/a.js, /p.js | api:4600: the script, and the preview mode drawing |
/collect, /collect/* | api:4600: the public collection endpoint and server-side collection |
/api/* | api:4600: REST and authentication |
| everything else | web:3600 |
Caddy keeps no access log: it would store visitors’ IP addresses. If you put another proxy
in front, also turn off its access log for /collect, or strip the addresses from it.
Writes and replicas
Section titled “Writes and replicas”- Writes are batched in the receiving process: one batch per second (
COLLECT_FLUSH_MS) or every 500 events. A clean shutdown (SIGTERM) flushes the buffer; an abrupt shutdown loses at most one second of events. - Several API replicas can receive together: the daily salt is in the database, so the hash is the same everywhere. Visits, however, are assigned in memory: behind a load balancer without affinity, a visit can be split in two. Prefer affinity by address.
- Several workers can run together: each one claims its jobs with
FOR UPDATE SKIP LOCKED.
Data over time
Section titled “Data over time”- Raw data (
collect) is partitioned by month; the worker creates partitions three months ahead. ADEFAULTpartition serves as a safety net and must stay empty: Administration › Settings › Collection monitors it. - Each site’s retention period (25 months at most) is applied every night.
Backing up
Section titled “Backing up”Two things are enough to rebuild an instance:
- the database (
db), for example withpg_dump; the views and the read role are recreated by the migrations; - the
SECRET_KEYkey: without it, the site secrets cannot be read.
eodia analytics is free software by Eodia.