Production Deployment
Tally is designed for production use with minimal operational overhead. This guide covers security hardening, backups, monitoring, and scaling considerations.
Security checklist
-
Set a strong
ADMIN_PASSWORD— At least 16 characters. The server enforces this in production mode. -
Configure
ALLOWED_ORIGINS— Restrict which domains can send events to your Tally instance. -
Set
SITESwith unique tokens — Each site should have its own secret token for event validation. -
Set
API_KEY— Use a separate key for API access, distinct from the admin password. -
Enable
TRUST_PROXY_AUTHonly if you're behind a reverse proxy that setsX-Forwarded-ForandX-Real-IPheaders. - Use HTTPS — Put Tally behind a reverse proxy (nginx, Caddy, Traefik) with TLS termination.
-
Set
NODE_ENV=production— Enables production-only security checks.
Reverse proxy setup
Tally listens on a single port. Use a reverse proxy for TLS, domain routing, and rate limiting:
Caddy (recommended — automatic HTTPS)
tally.example.com {
reverse_proxy localhost:3000
}
nginx
server {
listen 443 ssl;
server_name tally.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Backups
Tally stores all data in a single SQLite file. To back up:
# Safe backup (SQLite hot backup)
sqlite3 /app/data/analytics.db ".backup /backups/tally-$(date +%Y%m%d).db"
# Or simply copy the file (works if the server is stopped)
cp /app/data/analytics.db /backups/tally-$(date +%Y%m%d).db
SQLite in WAL mode is crash-safe. For production, set up a daily cron job that runs the
.backup
command.
Monitoring
The Docker image includes a health check:
# The built-in health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD wget -qO- http://localhost:3000/ || exit 1
Monitor the
/
endpoint for uptime. The server returns 200 if healthy.
Resource requirements
Tally is extremely lightweight:
- CPU: 1 core (handles thousands of events/second)
- RAM: ~50MB idle, ~200MB under load
- Disk: ~50MB for the container, grows with data (SQLite)
- Network: Minimal — ~100 bytes per event, ~3KB tracker download
GeoIP updates
MaxMind updates GeoLite2 databases weekly. To update, restart the container or download the new database file and replace the existing one. The server will pick up the new file on next startup.
# Manual update
curl -fsSL "https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz" -u "$MAXMIND_ACCOUNT_ID:$MAXMIND_LICENSE_KEY" | tar -xzf - --strip-components=1 -C /app/data/ GeoLite2-City_*/GeoLite2-City.mmdb
Updating Tally
docker pull tally:latest
docker compose down
docker compose up -d
SQLite handles schema migrations automatically on startup. No manual migration steps needed.
Scaling
Tally is designed for single-instance deployment. SQLite handles concurrent reads efficiently via WAL mode. For most personal and small-business sites, a single instance handles millions of pageviews without issue.
If you need to scale beyond a single instance, consider:
- Running multiple Tally instances behind a load balancer with shared SQLite storage (via NFS or similar) — read-only replicas work well
- Using the CSV export to pipe data into a dedicated analytics database
- For very high traffic, the API can be used to build a custom aggregation pipeline