Deploying
With Docker
bash
docker compose up -dOn first boot it downloads the DB-IP Lite databases (~62 MB compressed, ~140 MB on disk) and starts serving. /health answers 503 until it is ready.
Without Docker
It is a plain FastAPI app; Docker is packaging, not a requirement.
bash
python -m venv .venv
.venv/bin/pip install -r requirements.txt
EVO_LOCATE_DATA_DIR=./data .venv/bin/uvicorn app.main:app --port 9100On Windows (PowerShell): $env:EVO_LOCATE_DATA_DIR = ".\data", then .venv\Scripts\uvicorn app.main:app --port 9100.
The variable matters here: EVO_LOCATE_DATA_DIR defaults to /data, which exists in the container but rarely on a bare host. Point it at any writable directory — the databases are downloaded into it on first boot and refreshed in place, and everything else behaves exactly as under Docker.
Running as a service
The commands above run in the foreground and die with the terminal. To keep the service running on a machine:
Under Docker, the compose file already handles it: restart: unless-stopped brings the container back after crashes and reboots, as long as Docker itself starts on boot (systemctl enable docker).
Bare, on any systemd Linux, install a unit — adjust paths and user to taste:
ini
# /etc/systemd/system/evo-locate.service
[Unit]
Description=evo.locate IP geolocation API
After=network-online.target
Wants=network-online.target
[Service]
User=ubuntu
WorkingDirectory=/opt/evo.locate
Environment=EVO_LOCATE_DATA_DIR=/opt/evo.locate/data
ExecStart=/opt/evo.locate/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 9100
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.targetbash
sudo systemctl daemon-reload
sudo systemctl enable --now evo-locate
curl localhost:9100/healthBind 127.0.0.1 when a reverse proxy on the same machine fronts it — that is the setup the drop-in section assumes, and it keeps the service off the open network. Use --host 0.0.0.0 only if other machines must reach it directly.
Configuration
| Variable | Default | Meaning |
|---|---|---|
EVO_LOCATE_DATA_DIR | /data | Where the .mmdb files live. Mount a volume here. |
EVO_LOCATE_REFRESH_HOURS | 24 | How often to check DB-IP for a newer monthly release. |
EVO_LOCATE_SKIP_BOOT_DOWNLOAD | unset | Skip the first-boot download. Useful in tests and air-gapped setups. |
LOG_LEVEL | INFO | Standard Python log levels. |
The databases are never committed to the repository and are not baked into the image — they are large, republished monthly, and carry their own licence. The service fetches them.
Downloads are written to a temporary file and only moved into place after they have been fully written and successfully parsed, so a truncated or corrupt download never replaces a working database.
Troubleshooting
| Symptom | What it means and what to do |
|---|---|
/health answers 503 right after starting | Databases not loaded yet. Normal on first boot while the ~62 MB download runs — a minute or two. |
503 persists | The download failed; the log says why (no network, proxy, full disk). The refresh worker retries every EVO_LOCATE_REFRESH_HOURS (default daily), and restarting the service retries immediately. A failed download never corrupts anything — files are swapped in only after a complete download parses successfully. |
{"detail": "Not Found"} | No route at that path. The API surface is in the API reference; the bare / redirects to /docs. |
400 on a lookup | The input is neither a valid IP address nor a hostname that resolves. |
422 on a lookup | The address — or what the hostname resolved to — is private/loopback/link-local. No geolocation database can answer those. |
| Disk planning | ~140 MB for the two databases, plus ~62 MB transient during each monthly refresh download. |