The operator-facing environment variables for GeoLens, their defaults, and descriptions. Unless noted otherwise, set these in the .env file at the project root. A few quota and policy settings (flagged below) are managed only from the admin UI and are not read from .env.
Many of the remaining settings are admin-overridable: the .env value is only the default, and a value saved from the admin UI takes precedence for as long as it is stored. That applies to REGISTRATION_ENABLED, ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_DAYS, LOG_LEVEL, LOG_JSON, CORS_ALLOWED_ORIGINS, UPLOAD_MAX_SIZE_MB, UPLOAD_ALLOWED_EXTENSIONS, TILE_CACHE_TTL, PUBLIC_APP_URL, PUBLIC_API_URL, and the AI rows marked below. If an .env edit appears to have no effect, check the matching admin tab — or set ENV_ONLY_CONFIG=true to lock every admin-overridable setting to its environment value. LOG_JSON is an exception: both the log format and the security posture below are read from the environment value when the process starts, so saving it in the admin UI does not change either one.
Secret key for signing JWT tokens. Generate with openssl rand -hex 32. Must be at least 32 chars; the boot validator also rejects six well-known public placeholders (change-me, secret, etc.).
JWT_ALGORITHM
HS256
No
JWT signing algorithm
ACCESS_TOKEN_EXPIRE_MINUTES
15
No
JWT access-token lifetime in minutes
REFRESH_TOKEN_EXPIRE_DAYS
7
No
JWT refresh-token lifetime in days
GEOLENS_ADMIN_USERNAME
None (required)
Yes
Username for the automatically created admin account
GEOLENS_ADMIN_PASSWORD
None (required)
Yes
Password for the initial admin account
REGISTRATION_ENABLED
false
No
Whether self-registration is enabled. When false, only admins can create users.
EMAIL_VERIFICATION_REQUIRED
true
Admin UI only
Managed on the admin Auth tab — not read from .env. The value shown is the built-in default. Require new self-registered accounts to verify their email address before first login. Only relevant when REGISTRATION_ENABLED=true; requires a configured SMTP channel to deliver the verification email.
LANDING_FIRST
false
No
Redirect unauthenticated visits to / to the login page instead of the catalog (admin-overridable, “Login-as-Landing Page”). Useful for demo or lead-capture front doors.
PASSWORD_MIN_LENGTH
12
No
Minimum password length, enforced at every password entry point (register, change-password, admin create, SAML->local conversion).
PASSWORD_REQUIRE_CLASSES
3
No
Number of character classes required out of 4 (lowercase, uppercase, digit, symbol). Accepts 1 to 4.
Directory for temporary file storage during ingestion/export. Both Compose stacks pin this to the literal /app/staging on the api and worker services, and titiler resolves the same path from the built-in default with no env passthrough at all. All three share the upload_staging volume at that fixed mount target, so setting it in .env has no effect. Override it only for a bare-metal/non-Compose run. Must be writable by the API runtime user (uid:gid 1001:1001).
Comma-separated list of allowed file extensions for upload. Note: an admin-saved override in Admin → Storage takes precedence over this default — existing deployments that customized the list must add newer extensions (such as .parquet, .fgb, .kml, .kmz) there
PRESIGNED_MULTIPART_THRESHOLD_MB
100
No
Files larger than this (MB) use multipart presigned S3 URLs. Only applies when STORAGE_PROVIDER=s3.
INGEST_HTTP_TIMEOUT_SECONDS
300
No
GDAL_HTTP_TIMEOUT passed to ogr2ogr during remote-fetched ingest. Raise for very large datasets; lower for fail-fast ingest debugging.
URL_IMPORT_FETCH_MAX_SECONDS
1800
No
How long one File URL import may spend downloading, in seconds. Accepts 30 to 86400; a value outside that range is refused at startup. The download runs on the worker with no proxy or client deadline over it, so this is the only ceiling.
MAX_STORAGE_BYTES_PER_USER
0
Admin UI only
Managed on the admin Storage tab — not read from .env. Per-user storage quota in bytes; 0 = unlimited. The value shown is the built-in default.
MAX_DATASETS_PER_USER
0
Admin UI only
Managed on the admin Storage tab — not read from .env. Per-user dataset-count quota; 0 = unlimited. The value shown is the built-in default.
GeoLens validates staging writability at startup and before export execution. Both UPLOAD_STAGING_DIR and ${UPLOAD_STAGING_DIR}/exports must allow write access for the API runtime user.
Quick validation command. It runs as uid 1001 deliberately: the api container starts as root and drops to 1001:1001 inside its own entrypoint, so an unqualified docker compose exec would test root’s access rather than the runtime user’s and pass on the very misconfiguration you are looking for.
The api entrypoint already chowns the staging directory to 1001:1001 on every boot while it is still root, and ignores any error from that chown; its own boot probe then runs as root, which passes on exactly the root-writable, appuser-unwritable state the command above is designed to catch. So a failing probe on a running container means the boot-time chown did not take effect — on an NFS root-squash mount, for instance — and restarting will not change that. Fix ownership on the mounted path directly with docker compose exec -u 0 api chown -R 1001:1001 /app/staging, then restart the API container. Under Compose, pointing UPLOAD_STAGING_DIR somewhere else is not an option: the path is pinned (see the table above).
Browser-facing app URL. Used for share links and OAuth redirect URIs.
PUBLIC_API_URL
http://localhost:8080/api
No
Externally-reachable API base URL. Used in OGC self/collection/next link hrefs. That default is the Docker Compose one; outside Compose the application falls back to the request origin, then to http://localhost:8000. Set it explicitly on any deployment behind a proxy or a real hostname — OAuth flows refuse the request-derived fallback outright.
PUBLIC_BASE_URL
None
No
Deprecated. Will be removed in a future release. Legacy alias for PUBLIC_API_URL. Use PUBLIC_API_URL instead. The application logs a deprecation warning at startup when this is set.
Comma-separated list of exact origins allowed to make credentialed cross-origin API requests (a literal * is rejected). Required when the frontend is served from a different domain than the API. Anonymous reads of the standards and tile routes are unaffected; see Settings -> Network.
Output logs in structured JSON format. Recommended for production. Also acts as the legacy security-posture switch: when ENVIRONMENT is unset (the default), LOG_JSON=true additionally hides /api/docs and /api/redoc and sets the Secure flag on the OAuth session cookie. Set ENVIRONMENT explicitly to decouple the two — see Deployment / security posture.
Storage backend for uploaded files. Options: local, s3, azure.
S3_ENDPOINT
None
No
S3-compatible endpoint URL. Leave unset for AWS S3. For MinIO: http://minio:9000.
S3_BUCKET
None
When s3
S3 bucket name.
S3_ACCESS_KEY_ID
None
When s3, without ambient credentials
S3 access key ID. Leave both keys unset when the pod or task supplies ambient AWS credentials (IRSA, EKS Pod Identity, or ECS container credentials) — see Deploy on EKS. A half-configured pair is always rejected at boot.
S3_SECRET_ACCESS_KEY
None
When s3, without ambient credentials
S3 secret access key.
S3_REGION
us-east-1
No
S3 region.
S3_ALLOW_HTTP
false
No
Allow HTTP (non-TLS) connections to S3 endpoint. Enable for local MinIO.
S3_ADDRESSING_STYLE
auto
No
S3 addressing style. Options: auto, path, virtual. Use path for MinIO.
Set STORAGE_PROVIDER=azure and configure a container plus one authentication path: either a connection string, or an account URL with an access key (leave the key unset to use managed identity / Entra ID).
Variable
Default
Required
Description
AZURE_STORAGE_CONTAINER
None
When azure
Blob container name (e.g. geolens-prod).
AZURE_STORAGE_CONNECTION_STRING
None
One auth path
Full connection string (also used for Azurite). Provide this orAZURE_STORAGE_ACCOUNT_URL.
Path to CA certificate file for database SSL verification.
DATABASE_POOL_PRE_PING
true
No
Enable connection pool pre-ping to detect broken connections before use. Adds slight latency per checkout. Set to false only if you need to disable this for a specific environment.
These variables control the SQLAlchemy connection pool. Ignored when DB_USE_EXTERNAL_POOLER is true.
Variable
Default
Required
Description
DB_POOL_SIZE
10
No
Maximum number of persistent connections in the pool.
DB_MAX_OVERFLOW
3
No
Maximum number of additional connections beyond DB_POOL_SIZE. Tuned down from SQLAlchemy’s default to fit the connection budget below.
DB_POOL_TIMEOUT
30
No
Seconds to wait for a connection from the pool before raising an error.
DB_STATEMENT_TIMEOUT_SECONDS
300
No
Query deadline for the API process, in seconds; 0 disables it. Distinct from DB_POOL_TIMEOUT: that one bounds how long a request waits for a connection, this one bounds how long a statement runs once it has one, so one pathological plan can’t pin a pool slot indefinitely. Applies to every API transaction; the worker is deliberately excluded, since it legitimately runs a single statement for minutes while indexing or reprojecting a freshly ingested table. Raise it (or set it to 0) if a legitimate API workload needs longer than five minutes.
DB_POOL_RECYCLE
1800
No
Seconds after which a connection is recycled (replaced). Prevents stale connections with managed databases.
TILE_POOL_MIN_SIZE
2
No
Minimum connections in the dedicated asyncpg tile query pool.
TILE_POOL_MAX_SIZE
10
No
Maximum connections in the dedicated asyncpg tile query pool.
UVICORN_WORKERS
2 (release stack); 1 (source-build stack)
No
Uvicorn worker processes for the API. Each one opens its own SQLAlchemy, asyncpg tile, and Procrastinate pool — check the connection budget below before raising it.
UVICORN_MAX_REQUESTS
10000
No
Recycle each API worker after this many requests (graceful exit + supervisor respawn), so slow memory growth can’t ride a worker into the container’s limit. Only applies with UVICORN_WORKERS ≥ 2 — with a single worker there is no supervisor to respawn it, so the prod stack omits the limit entirely rather than exiting the whole process.
UVICORN_TIMEOUT_KEEP_ALIVE
5
No
Keep-alive timeout in seconds; matches the uvicorn upstream default. Tune up for clients with chatty long-poll patterns.
UVICORN_TIMEOUT_GRACEFUL_SHUTDOWN
30
No
Max seconds uvicorn waits for in-flight requests to finish on shutdown before SIGKILL. The default covers slow GDAL ingest finalization.
Redis/Valkey connection URL for cross-instance caching, and, since 1.19.0, the shared store rate limits are counted in. Leave unset for in-memory caching and per-worker rate-limit counting (single-instance default); with more than one API worker, an unset value means every configured limit is enforced once per worker. Rate limits use the store only for a redis:// or rediss:// URL; any other scheme logs rate_limit_storage_scheme_unsupported and counting stays per worker. Example: redis://valkey:6379/0.
These variables configure the default-on backup service. Off-site upload stays disabled until BACKUP_S3_ENABLED=true.
Variable
Default
Required
Description
BACKUP_SCHEDULE
0 2 * * *
No
Cron expression for automated database backups. Default: daily at 2:00 AM UTC.
BACKUP_RETENTION_DAILY
7
No
Number of daily backups to retain locally. Must be an integer of at least 1; the container refuses to start otherwise.
BACKUP_RETENTION_WEEKLY
4
No
Number of weekly (Sunday) backups to retain locally. Must be an integer of at least 1; the container refuses to start otherwise.
BACKUP_S3_ENABLED
false
No
Enable off-site backup upload to S3-compatible storage. Uses S3_* credentials.
BACKUP_MAX_AGE_MINUTES
1560
No
Freshness threshold for the backup container’s healthcheck, in minutes. The service reports unhealthy when the last fully successful backup cycle is older than this. Raise it to about 1.5x the interval for less-frequent BACKUP_SCHEDULE values.
BACKUP_MEM_LIMIT
512m
No
Memory cap for the backup container (the nightly pg_dump + tar peaks around 300 MB). Honoured by the release stack (docker-compose.prod.yml) only; the source-build stack sets no memory cap on the backup container.
Optional email (SMTP) and webhook alerts for signup, ingest, and health events. Everything defaults to off, so existing deployments are unaffected until NOTIFICATIONS_ENABLED=true and at least one channel is configured. See Backups & Restore and Infrastructure & Monitoring for the operational context.
Variable
Default
Required
Description
NOTIFICATIONS_ENABLED
false
No
Master toggle. When false, all notification sends are a no-op regardless of channel config.
SMTP_HOST
None
No
SMTP server hostname. Configure together with SMTP_USERNAME, SMTP_PASSWORD, and SMTP_FROM_ADDRESS to enable the email channel.
SMTP_PORT
587
No
SMTP server port.
SMTP_USERNAME / SMTP_PASSWORD
None
No
SMTP credentials (secret; never rendered in logs).
SMTP_FROM_ADDRESS
None
No
From address for outbound email.
SMTP_USE_TLS
true
No
Use STARTTLS for the SMTP connection.
NOTIFICATION_WEBHOOK_URL
None
No
Incoming-webhook endpoint (Slack, Teams, or custom) that receives JSON notifications.
NOTIFICATION_WEBHOOK_SECRET
None
No
Optional HMAC signing secret for webhook payloads.
NOTIFICATION_ADMIN_EMAIL
None
No
Recipient for event alerts. Falls back to SMTP_FROM_ADDRESS when unset.
NOTIFY_ON_SIGNUP
false
No
Send an alert when a new account registers.
NOTIFY_ON_INGEST_COMPLETE
false
No
Send an alert when an ingest finishes successfully.
NOTIFY_ON_INGEST_FAILED
false
No
Send an alert when an ingest fails.
NOTIFY_ON_HEALTH_ALERT
false
No
Send an alert when the health check reports a degraded status (cooldown-deduplicated).
These are environment-only settings (not stored in the admin settings database) because the channel credentials are secrets.
Number of jobs the background worker processes concurrently.
WORKER_QUEUES
priority,ingest,raster,download,ingest-auth-v2
No
Comma-separated list of job queues the worker consumes, in priority order. Set it only to move work between workers, and start from the full default when you do. Dropping download leaves File URL imports enqueued with no consumer. ingest-auth-v2 is a drain queue with no producer in this release: it exists so a credentialed import queued by a 1.18.0 or 1.18.1 API still runs, and a worker that omits it logs worker_queue_missing_drain_queue at startup.
WORKER_SHUTDOWN_TIMEOUT
30
No
Graceful shutdown timeout for the background worker in seconds
When true, all admin-overridable settings are locked to their environment values. The PersistentConfig DB layer is bypassed for reads and returns 403 on writes. Use for hardened production deployments where operators want to prevent runtime configuration changes via the admin UI.
Deployment environment: development or production. When production, the API hides its docs (/api/docs and /api/redoc return 404) and sets the Secure flag on the OAuth session cookie. When unset, the posture falls back to LOG_JSON for backward compatibility. Set ENVIRONMENT=production on any public, TLS-terminated deployment.
These knobs matter when the frontend image fronts the API outside the bundled Compose stack (bare containers, Kubernetes, the community Helm chart). Under stock Docker Compose the defaults are correct and you can leave them unset.
Variable
Default
Required
Description
API_UPSTREAM
http://api:8000
No
Where the frontend’s nginx proxies /api, raster tiles, and embeds. Set to the API Service’s fully qualified name on Kubernetes. Trailing slashes are stripped.
NGINX_RESOLVER
first nameserver in /etc/resolv.conf, else 127.0.0.11
No
DNS resolver nginx uses to resolve API_UPSTREAM. Bare IPv6 resolvers are bracketed automatically.
CLIENT_MAX_BODY_SIZE
500m
No
Upload ceiling enforced by the frontend nginx. Invalid values fail fast at boot. Keep this at or above UPLOAD_MAX_SIZE_MB.
TRUSTED_PROXY_CIDRS
(empty)
No
Comma/space-separated CIDRs of proxies trusted to set X-Forwarded-For/-Proto (e.g. Cloudflare’s published ranges). Required for correct client IPs and anonymous raster rate limiting behind a load balancer or CDN. Empty treats the direct peer as the client.
For a non-AWS S3 backend, the API and worker derive AWS_S3_ENDPOINT, AWS_HTTPS, and AWS_VIRTUAL_HOSTING from the S3_* settings above automatically; you do not normally set them by hand.
GeoLens supports two AI subsystems: inference (chat, map generation, metadata drafts) and embeddings (semantic search). They can use different providers.
API keys are set exclusively via environment variables. Provider and model can also be overridden at runtime from the admin Settings > AI tab. Base URL cannot: once OPENAI_API_KEY is set, the API refuses any saved base URL that does not resolve to the environment-approved endpoint for that purpose — OPENAI_BASE_URL for inference, and EMBEDDING_BASE_URL (falling back to OPENAI_BASE_URL) for embeddings, with OpenAI’s own endpoint standing in when neither is set. A database write therefore cannot redirect the environment-provided credential. Leaving the admin field blank is still accepted and means “inherit the environment endpoint”.
These are the in-container ports; they are not operator knobs. The release stack (docker-compose.prod.yml) and the source-build stack (docker-compose.yml) differ only for the frontend:
/var/lib/postgresql on db (PostgreSQL 18+ takes a single mount here and keeps PGDATA at <major>/docker inside it; mounting the legacy /var/lib/postgresql/data path makes the entrypoint refuse to boot)
upload_staging
Uploaded file staging area
/app/staging on api, worker, and titiler
backup_data
Nightly pg_dump output plus a tar of upload_staging
/backups on backup
frontend_cache
nginx raster cache (release stack only)
/var/cache/nginx on frontend
minio_data / valkey_data
MinIO and Valkey data for the optional cloud-dev profile