Docker Deployment
Canonical env var reference: Environment Variables Reference
Prerequisites
Before you begin, ensure you have the following installed:
System Components Overview
GeoPulse consists of 3 mandatory services and one optional service:
- GeoPulse Backend - mandatory
- GeoPulse Frontend - mandatory
- GeoPulse Postgres (Postgis) database - mandatory
- MQTT Broker (Mosquitto) - optional
MQTT broker can be deployed if you want to use OwnTracks integration with MQTT protocol. If you don't use OwnTracks or want to use OwnTracks with HTTP - you don't need the MQTT broker. This guide provides steps on how to install the system with or without MQTT broker.
There is one-time service "geopulse-keygen" that creates JWT keys and AI encryption key in the "keys" folder. It starts and stops automatically, no manual steps are required.
Docker Compose Deployment
This section guides you through deploying GeoPulse using Docker Compose.
1. Quick Start
Option A: Regular install (without MQTT)
# 1) Download .env
curl -L -o .env https://raw.githubusercontent.com/tess1o/GeoPulse/main/.env.example
# 2) Configure .env (optional for local evaluation, required for production/security)
# nano .env
# 3) Download docker-compose
curl -L -o docker-compose.yml https://raw.githubusercontent.com/tess1o/GeoPulse/main/docker-compose.yml
# 4) Start
docker compose up -d
Option B: Install with MQTT
# 1) Download .env
curl -L -o .env https://raw.githubusercontent.com/tess1o/GeoPulse/main/.env.example
# 2) Configure .env (optional for local evaluation, required for production/security)
# For MQTT usage, set GEOPULSE_MQTT_ENABLED=true
# nano .env
# 3) Download docker-compose with MQTT
curl -L -o docker-compose.yml https://raw.githubusercontent.com/tess1o/GeoPulse/main/docker-compose-complete.yml
# 4) Start
docker compose up -d
Optional: Add Apprise (Geofence External Notifications)
Apprise is optional and is not bundled into the main compose files.
Create an overlay file named docker-compose.apprise.yml:
services:
apprise-api:
image: caronc/apprise:latest
container_name: geopulse-apprise
restart: unless-stopped
ports:
- "8000:8000"
Then start with the overlay:
docker compose -f docker-compose.yml -f docker-compose.apprise.yml up -d
After startup, configure Apprise in GeoPulse Admin UI:
- Open
Admin Dashboard > System Settings > Notifications - Set API URL:
http://apprise-api:8000when backend and Apprise run in the same docker-compose stack/networkhttp://localhost:8000only when backend runs on host and Apprise is exposed on host port8000
- Enable Apprise and run Test Apprise
For full admin/user configuration, templates, and in-app behavior, see:
After Start
- Open GeoPulse UI:
http://localhost:5555 - Check backend health:
curl http://localhost:8080/api/health - Follow logs if needed:
docker compose logs -f
If these checks pass, your deployment is up and running. The sections below are optional tuning and production guidance.
A Note on Docker Image Source
By default, GeoPulse uses images from Docker Hub. We also publish images to GitHub Container Registry (GHCR).
To switch to GHCR images, edit your
docker-compose.ymlfile. Forgeopulse-backendandgeopulse-ui, comment the defaultimageand uncomment theimagepointing toghcr.io.Example for
geopulse-backend:geopulse-backend:
# image: tess1o/geopulse-backend:${GEOPULSE_VERSION}-native
image: ghcr.io/tess1o/geopulse-backend:${GEOPULSE_VERSION}-nativeMulti-Architecture Support
The native backend images support both AMD64 and ARM64 architectures, including Raspberry Pi. Docker will automatically pull the correct image for your platform.
CPU Compatibility Issues
If your backend container fails to start with an error about missing CPU features like:
The current machine does not support all of the following CPU features that are required by the image:
[AVX2, BMI1, BMI2, FMA, F16C, LZCNT] (AMD64) or [FP, ASIMD, CRC32, LSE] (ARM64)This means you have an older CPU that doesn't support the optimized instructions used by the default image.
Solution: Use the compatible image variant instead:
Edit your
docker-compose.ymland replace the backend image:geopulse-backend:
# Comment out the default optimized image:
# image: tess1o/geopulse-backend:${GEOPULSE_VERSION}-native
# Use the compatible image instead:
image: tess1o/geopulse-backend:${GEOPULSE_VERSION}-native-compatWho needs the compatible image?
- Old x86 CPUs: Intel pre-Haswell (before 2013), AMD pre-Excavator (before 2015)
- Examples: Intel Core i5-3470T (Ivy Bridge), Core i7-2600 (Sandy Bridge)
- Raspberry Pi 3/4: ARM Cortex-A53/A72 processors
Performance Note: The compatible image uses x86-64-v2 (AMD64) or armv8-a+nolse (ARM64) instruction sets, which sacrifice some performance for broader compatibility. Modern CPUs (2015+) should use the default optimized image for best performance.
2. Configure Environment (.env)
The .env file is the central place for configuring your GeoPulse instance.
Core Configuration
It is highly recommended to set strong, unique passwords for your services.
GEOPULSE_POSTGRES_PASSWORD: The password for the PostgreSQL database.GEOPULSE_POSTGRES_PASSWORD=your-secure-database-passwordGEOPULSE_MQTT_PASSWORD: The password for the MQTT broker. Only needed if you are using the MQTT deployment.GEOPULSE_MQTT_PASSWORD=your-secure-mqtt-password
MQTT Configuration
GEOPULSE_MQTT_ENABLED: Set totrueto enable backend OwnTracks MQTT integration. Use this together with the MQTT compose file (docker-compose-complete.yml, Quick Start option B), which adds the Mosquitto service.GEOPULSE_MQTT_ENABLED=true
External MQTT Broker with TLS
If you use an external broker that only accepts TLS:
- Keep using
GEOPULSE_MQTT_ENABLED=true - Set broker host/port to the external broker (
GEOPULSE_MQTT_BROKER_HOST,GEOPULSE_MQTT_BROKER_PORT, typically8883) - Set
GEOPULSE_MQTT_TLS_ENABLED=true - If your broker uses private CA/self-signed certs and/or mTLS, mount truststore/keystore files into
geopulse-backendand set corresponding env vars
Example backend volume mount:
services:
geopulse-backend:
volumes:
- ./keys:/app/keys
- ./import-drop:/data/geopulse-import
- ./mqtt-certs:/app/mqtt-certs:ro
For full TLS variable list, mandatory/optional matrix, and certificate requirements, see External MQTT Broker TLS (OwnTracks).
Web Access & Security Configuration
These variables control how users access the GeoPulse frontend and how authentication cookies are handled.
GEOPULSE_CORS_ENABLED: Enables/disables backend CORS handling.GEOPULSE_CORS_ORIGINS: Comma-separated list of allowed origins when CORS is enabled.GEOPULSE_PUBLIC_BASE_URL: Public base URL used for generated callback/link URLs (recommended for OIDC).GEOPULSE_UI_URL: Legacy fallback variable kept for backward compatibility (deprecated).GEOPULSE_COOKIE_DOMAIN: The domain for authentication cookies. Keep empty for standard deployments.GEOPULSE_AUTH_SECURE_COOKIES: Set totrueto ensure cookies are only sent over HTTPS.
CORS Defaults
For standard docker-compose deployments, frontend and backend are served from the same origin through nginx (/api proxy).
Because of this, .env.example sets:
GEOPULSE_CORS_ENABLED=false
Only enable CORS if you intentionally deploy frontend and backend on different origins.
For cross-origin deployments, enable CORS explicitly and set the allowed origins:
GEOPULSE_CORS_ENABLED=true
GEOPULSE_CORS_ORIGINS=https://app.yourdomain.com
GEOPULSE_COOKIE_DOMAIN is usually not needed in standard docker-compose deployments because nginx serves a same-origin app and API.
Set it only for advanced split-subdomain setups (for example app.yourdomain.com + api.yourdomain.com).
For full cookie-domain guidance, see Authentication Configuration.
Advanced Performance Tuning
🚀 Advanced Performance Tuning (Click to expand)
Database Performance Tuning
GeoPulse includes optimized PostgreSQL/PostGIS configurations tailored for GPS tracking workloads. Different deployment sizes use appropriate memory allocations and query optimization settings.
Deployment-Specific Database Settings
Standard Deployment (docker-compose.yml and docker-compose-complete.yml) - Minimal resource usage:
shared_buffers=256MB- Conservative memory allocation for typical deploymentswork_mem=8MB- Low memory per connection, suitable for 1-10 concurrent userseffective_cache_size=1GB- Assumes minimal system memory availablelog_min_duration_statement=5000ms- Only log very slow queries- Both files use identical PostgreSQL settings; complete version adds MQTT support only
Development (docker-compose-dev.yml) - Enhanced debugging with minimal resources:
shared_buffers=128MB- Very low memory usage for local developmentwork_mem=6MB- Minimal memory per connectionlog_min_duration_statement=1000ms- Detailed query monitoring for optimizationtrack_functions=all- Comprehensive function performance tracking
Environment Variable Configuration
All PostgreSQL settings can be customized using environment variables in your .env file:
# Memory Settings
GEOPULSE_POSTGRES_SHARED_BUFFERS=512MB # Main PostgreSQL buffer cache
GEOPULSE_POSTGRES_WORK_MEM=16MB # Memory per connection for sorting/hashing
GEOPULSE_POSTGRES_MAINTENANCE_WORK_MEM=128MB # Memory for maintenance operations
GEOPULSE_POSTGRES_EFFECTIVE_CACHE_SIZE=2GB # Expected available OS cache
# WAL and Checkpoint Settings
GEOPULSE_POSTGRES_MAX_WAL_SIZE=1GB # Maximum WAL size before checkpoint
GEOPULSE_POSTGRES_WAL_BUFFERS=32MB # WAL buffer size in memory
GEOPULSE_POSTGRES_CHECKPOINT_TARGET=0.9 # Checkpoint completion target (0.0-1.0)
# Performance Settings
GEOPULSE_POSTGRES_RANDOM_PAGE_COST=1.1 # Cost of random page access (SSD optimized)
GEOPULSE_POSTGRES_IO_CONCURRENCY=100 # Expected concurrent I/O operations
GEOPULSE_POSTGRES_PARALLEL_WORKERS=2 # Max parallel workers per query
# Autovacuum Settings
GEOPULSE_POSTGRES_AUTOVACUUM_NAPTIME=60s # Time between autovacuum runs
GEOPULSE_POSTGRES_VACUUM_SCALE_FACTOR=0.2 # Vacuum threshold as fraction of table size
# Logging Settings
GEOPULSE_POSTGRES_LOG_SLOW_QUERIES=2000 # Log queries slower than this (ms)
GEOPULSE_POSTGRES_LOG_CHECKPOINTS=off # Log checkpoint activity
GEOPULSE_POSTGRES_LOG_STATEMENT=none # Log SQL statements (none/ddl/mod/all)
GEOPULSE_POSTGRES_LOG_AUTOVACUUM=0 # Log autovacuum activity duration (ms)
GEOPULSE_POSTGRES_TRACK_FUNCTIONS=none # Track function performance (none/pl/all)
GEOPULSE_POSTGRES_SYNC_COMMIT=on # Synchronous commit mode
Example Custom Configuration for High-Performance Setup
# High-performance .env settings (requires 4GB+ container)
GEOPULSE_POSTGRES_SHARED_BUFFERS=1GB
GEOPULSE_POSTGRES_WORK_MEM=24MB
GEOPULSE_POSTGRES_MAINTENANCE_WORK_MEM=256MB
GEOPULSE_POSTGRES_EFFECTIVE_CACHE_SIZE=3GB
GEOPULSE_POSTGRES_MAX_WAL_SIZE=2GB
GEOPULSE_POSTGRES_WAL_BUFFERS=64MB
GEOPULSE_POSTGRES_PARALLEL_WORKERS=4
GEOPULSE_POSTGRES_IO_CONCURRENCY=200
GEOPULSE_POSTGRES_LOG_SLOW_QUERIES=1000
GPS Workload Optimizations
The database configurations include optimizations specific to GPS tracking applications:
- Spatial Query Performance:
random_page_cost=1.1optimized for SSD storage - Bulk GPS Inserts: Enhanced
wal_buffersand checkpoint settings - Timeline Processing: Optimized for large sequential scans and complex spatial joins
- Concurrent Users:
work_memtuned to prevent memory exhaustion with multiple users
Monitoring Database Performance
Query Performance:
-- Monitor slow queries
SELECT query, mean_exec_time, calls, total_exec_time
FROM pg_stat_statements
WHERE mean_exec_time > 1000
ORDER BY mean_exec_time DESC;
Memory Usage:
-- Check buffer hit ratio (should be >99%)
SELECT round(100.0 * blks_hit / (blks_hit + blks_read), 2) AS hit_ratio
FROM pg_stat_database
WHERE datname = current_database();
Storage and Vacuum:
-- Monitor autovacuum performance
SELECT schemaname, tablename, last_vacuum, last_autovacuum
FROM pg_stat_user_tables
WHERE tablename LIKE '%gps%'
OR tablename LIKE '%timeline%';
Production Deployment Example: Reverse Proxy
For a production deployment using a domain name, you must terminate HTTPS at a reverse proxy and forward traffic to the GeoPulse containers.
-
Configure your
.envfile for production (as shown in the "Web Access & Security Configuration" section). -
Set up your reverse proxy. Here is a basic Nginx configuration example. You will also need to set up SSL certificates (e.g., using Let's Encrypt).
Example Nginx Configuration
# /etc/nginx/sites-available/geopulse.conf
server {
listen 80;
server_name geopulse.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name geopulse.yourdomain.com;
# SSL certs
ssl_certificate /path/to/your/fullchain.pem;
ssl_certificate_key /path/to/your/privkey.pem;
# Frontend
location / {
proxy_pass http://localhost:5555;
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;
}
}
Running as Non-Root User (Optional)
GeoPulse containers support running as non-root users for enhanced security in production environments. This is completely optional - by default, containers work without any changes.
Default Behavior (No Changes Required)
By default, GeoPulse containers run with these security settings:
- Backend (JVM & Native): Already runs as non-root user (secure by default) ✅
- Frontend (nginx): Runs as root user on port 80 (for backward compatibility)
Existing deployments continue to work without modifications.
Optional: Running Frontend as Non-Root
For enhanced security, you can configure the frontend to run as a non-root user. This requires two simple changes in your docker-compose.yml:
- Add the
userdirective with any UID:GID you prefer - Update the port mapping from
80to8080
Example for docker-compose.yml:
services:
geopulse-ui:
image: tess1o/geopulse-ui:${GEOPULSE_VERSION}
container_name: geopulse-ui
restart: unless-stopped
user: "1000:1000" # ← ADD THIS: Use your preferred UID:GID
env_file:
- .env
ports:
- 5555:8080 # ← CHANGE THIS: 80 → 8080
depends_on:
geopulse-backend:
condition: service_healthy
Flexibility: The container works with any UID:GID combination:
user: "1000:1000"- Your user UID and GID (most common)user: "1001:1001"- Different useruser: "1000:0"- OpenShift pattern (UID with root group)- Any other valid UID:GID pair
The container automatically detects the user and adjusts accordingly - no additional configuration needed!
Optional: Running Backend with Custom User ID
Both backend images (JVM and native) already run as non-root by default, so no changes are needed. However, if you want to use a specific user ID (e.g., to match your host filesystem permissions), you can override it:
services:
geopulse-backend:
image: tess1o/geopulse-backend:${GEOPULSE_VERSION}-native
container_name: geopulse-backend
user: "1000:1000" # ← OPTIONAL: Override with any UID:GID
# ... rest of configuration
Backend flexibility: Works with any UID:GID combination, including:
user: "1000:1000"- Standard docker-compose usageuser: "1000:0"- OpenShift pattern (recommended for Kubernetes)
How It Works
The frontend container automatically detects whether it's running as root or non-root:
- Running as root (UID 0): Uses port 80 (default, backward compatible)
- Running as non-root (any other UID): Automatically switches to port 8080 (non-privileged)
This auto-detection ensures backward compatibility while enabling security-enhanced deployments.
Benefits of Running as Non-Root
Running containers as non-root users provides several security advantages:
- Principle of Least Privilege: Containers run with minimal permissions required
- Container Escape Mitigation: Limits potential damage if a container is compromised
- Compliance: Meets security requirements for production environments and regulated industries
- Defense in Depth: Adds an additional security layer to your deployment
Kubernetes/OpenShift Compatibility
All GeoPulse container images are designed to run in Kubernetes and OpenShift environments with flexible security contexts:
Frontend Container:
- Uses world-writable directories (777) for runtime files
- Works with any UID:GID combination - no special requirements
- Automatically adapts to root or non-root execution
Backend Container:
- Uses OpenShift pattern (user:group 0 with group permissions)
- Works best with GID 0 for Kubernetes/OpenShift
- Also supports any UID:GID with proper volume permissions
Example Kubernetes security contexts:
# Frontend - Simple (any UID:GID works)
frontend:
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
# Backend - OpenShift Pattern (recommended)
backend:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 0 # Root group for OpenShift pattern
Both containers work seamlessly with Kubernetes runAsUser and OpenShift's automatic UID assignment. See the Helm deployment guide for complete Kubernetes/Helm configuration.
Backward Compatibility
Existing .env files continue to work without mandatory changes.
GEOPULSE_UI_URLis still supported as a legacy fallback for CORS/OIDC behavior.- New deployments should use
GEOPULSE_CORS_ENABLED,GEOPULSE_CORS_ORIGINS, andGEOPULSE_PUBLIC_BASE_URL. - For OIDC callback URLs, prefer explicit
GEOPULSE_OIDC_CALLBACK_BASE_URL. - Detailed migration guidance is available in Updating GeoPulse.
Troubleshooting
Non-root user issues:
If you've added the user directive to the frontend service but forgot to update the port mapping:
# Check container logs
docker compose logs geopulse-ui
# You'll see: "Running as non-root (UID 1000) - using port 8080"
# But the port mapping is still 5555:80 - this causes connection issues
Solution: Update the port mapping in docker-compose.yml from 5555:80 to 5555:8080.
Port conflicts:
docker ps --format "table {{.Names}}\t{{.Ports}}"
Service issues:
docker compose logs -f
curl http://localhost:8080/api/health
Authentication issues:
- Check domains match between frontend/backend
- For standard deployments (with nginx proxy):
GEOPULSE_COOKIE_DOMAIN=""(keep empty) - For HTTP deployments:
GEOPULSE_AUTH_SECURE_COOKIES=false - For HTTPS deployments:
GEOPULSE_AUTH_SECURE_COOKIES=true - Verify JWT keys exist in
keys/directory - For split-subdomain setups, see Authentication Configuration
Network/Proxy issues:
- API requests failing: Check that frontend nginx proxy is configured correctly
- Cross-origin browser requests failing: Enable CORS with
GEOPULSE_CORS_ENABLED=trueand configureGEOPULSE_CORS_ORIGINS - OIDC callback URL is wrong: Set
GEOPULSE_OIDC_CALLBACK_BASE_URL(preferred) orGEOPULSE_PUBLIC_BASE_URL - Development proxy issues: Ensure Vite dev server is running with proxy configuration
Configuration issues:
- Check
.envfile is properly formatted and readable by containers - Verify
GEOPULSE_BACKEND_URLuses correct container name (http://geopulse-backend:8080for Docker) - For homelab/public deployment: Set
GEOPULSE_PUBLIC_BASE_URLto your externally reachable URL
Key generation issues:
- JWT keys and AI encryption keys are automatically generated in the
keys/directory on first startup - If you see key-related errors, check that the
geopulse-keygenservice completed successfully:docker compose logs geopulse-keygen - Keys are persistent - they won't be regenerated if they already exist
- To regenerate keys: remove the
keys/directory and restart withdocker compose up -d
CPU compatibility issues:
If the backend container crashes immediately with errors about missing CPU features:
docker compose logs geopulse-backend
Look for errors like:
[AVX2, BMI1, BMI2, FMA, F16C, LZCNT](AMD64 CPUs)[FP, ASIMD, CRC32, LSE](ARM64/Raspberry Pi)
Solution: Switch to the compatible image tag as described in the "CPU Compatibility Issues" section above. Edit your docker-compose.yml to use ${GEOPULSE_VERSION}-native-compat instead of ${GEOPULSE_VERSION}-native.
Check your CPU capabilities:
# For x86/AMD64:
lscpu | grep -i "flags"
# Look for: avx2, bmi1, bmi2, fma
# For ARM64:
cat /proc/cpuinfo | grep "Features"
# Look for: asimd, crc32, atomics