Configuration
Safe Harbor reads runtime configuration from environment variables. In Docker
Compose installs, most values come from .env; a few service-specific values
are also set directly in docker-compose.yml.
Use this page as the public operator reference for the variables shipped in
.env.example, plus the upload directory variable used by the application
runtime.
Each table uses the same columns:
- Variable is the environment variable name.
- Default is the application or Compose example default.
- Required describes when an operator must set it.
- Surface names the service or component that consumes it.
- Description gives the operational effect.
- Docs links to related local documentation when there is a deeper guide.
Flask Core
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
FLASK_APP |
safeharbor.wsgi:app |
Required for Flask CLI commands outside the container image | Flask CLI | Import path for the WSGI application used by flask commands. |
- |
FLASK_CONFIG |
development |
Required for production deployment | App factory, web, worker | Selects development, testing, or production config. |
Install |
SECRET_KEY |
change-me-in-prod |
Required in production | Flask sessions and CSRF | Secret used to sign sessions and forms; replace before using production config. | Install |
Development to production note: the checked-in Compose file is intentionally
friendly for first-run local use. It sets FLASK_CONFIG: development directly
on both web and worker, which overrides .env for those services.
Production deployment therefore requires a deployment override or host
environment that passes FLASK_CONFIG=production to web and any started
worker after a real SECRET_KEY is set.
Production startup validation fails when SECRET_KEY is blank or still set to
change-me-in-prod. It also requires DATABASE_URL, REDIS_URL, SMTP_HOST,
and STORAGE_DIR to be present in the production environment.
Database
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
DATABASE_URL |
Compose: postgresql+psycopg://safeharbor:safeharbor@postgres:5432/safeharbor |
Required in production | Web, worker, backup sidecar | Primary SQLAlchemy database URL. | Install |
TEST_DATABASE_URL |
postgresql+psycopg://safeharbor:safeharbor@postgres:5432/safeharbor_test in .env.example; app fallback uses local localhost |
Optional unless running tests | Test config | Database URL used by pytest fixtures and FLASK_CONFIG=testing. |
- |
Use the Compose hostname postgres from containers. Use localhost only for
host-run development commands that connect through a locally exposed database.
The production validator checks that DATABASE_URL is set. It does not create
the database for you; the target database must exist and be reachable before the
app starts.
Redis
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
REDIS_URL |
Compose: redis://redis:6379/0; app fallback redis://localhost:6379/0 |
Required in production | Web, worker | Redis connection URL for background work and shared runtime services. | Install |
RQ_QUEUES |
default |
Optional for custom worker commands | Custom worker command | Queue names for an overridden worker command. | - |
The current Compose worker command hardcodes the default queue and passes its
Redis URL directly to rq worker, so changing RQ_QUEUES in .env does not
change the provided worker service. Keep RQ_QUEUES documented for
.env.example compatibility and set it only when you also override the worker
command to read it.
Use the Compose hostname redis from containers. Use localhost only for
host-run development commands that connect through a locally exposed Redis
service.
Email/SMTP
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
SMTP_HOST |
.env.example: mailhog; app fallback localhost |
Required in production | Email sending | SMTP server hostname. | Install |
SMTP_PORT |
1025 |
Optional | Email sending | SMTP server port. | - |
SMTP_USER |
empty | Optional | Email sending | SMTP username for authenticated relays. | - |
SMTP_PASS |
empty | Optional | Email sending | SMTP password for authenticated relays. | - |
SMTP_FROM |
safeharbor@localhost |
Optional, recommended | Email sending | Sender address used for application emails. | - |
For local development, the Compose dev profile can start Mailhog. With
Mailhog enabled, use SMTP_HOST=mailhog and SMTP_PORT=1025, then open its web
UI on port 8025 to inspect messages.
For production, point SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, and
SMTP_FROM at your own relay. If the app sends password reset or account email
links from asynchronous code, also configure the public URL variables below.
Storage
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
STORAGE_DIR |
.env.example: /data/uploads; app fallback ./uploads |
Required in production | Production validation | Public storage setting retained for operator configuration and production validation. | Install |
UPLOAD_DIR |
/data/uploads |
Optional when using the provided Compose volume | Web, upload service, backup and restore CLI | Actual directory used for tank and animal image uploads. | Backups |
UPLOAD_DIR_REQUIRE_WRITABLE |
1 |
Optional | App startup check | When truthy, startup fails if UPLOAD_DIR is not writable by the app process. |
Restore |
Current upload behavior is writable. The web service mounts the named
uploads volume at /data/uploads, and the backups service also mounts that
same volume read-write so backup and restore commands can package and restore
uploaded files.
Keep UPLOAD_DIR_REQUIRE_WRITABLE=1 for normal backup and restore workflows;
uploads are writable in the standard services. Set it to 0 only for a custom
diagnostic container that needs to inspect configuration without writing
uploads.
STORAGE_DIR is still part of the public environment file and production
validation. UPLOAD_DIR is the current runtime path used by upload, backup, and
restore code. In the standard container setup they should both resolve to
/data/uploads.
Logging
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
LOG_LEVEL |
INFO |
Optional | Web, worker | Python logging level such as DEBUG, INFO, WARNING, or ERROR. |
Observability |
Production logs are intended for container collection. Set LOG_LEVEL=DEBUG
only while investigating a specific issue, then return to INFO or a quieter
level.
Reverse Proxy
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
TRUST_PROXY_HEADERS |
1 |
Optional | Flask request handling | Enables one trusted proxy hop so forwarded scheme, host, and client headers are honored. | Deploy |
FORWARDED_ALLOW_IPS |
* |
Optional | Gunicorn | Gunicorn allowlist for forwarded headers from upstream proxy IPs. | Deploy |
Leave TRUST_PROXY_HEADERS=1 when Safe Harbor is behind a trusted reverse proxy
or Cloudflare Tunnel. Set it to 0 when exposing the container directly without
a trusted proxy.
FORWARDED_ALLOW_IPS=* is convenient for dynamic proxy environments. If your
proxy source addresses are static, tighten this to the known IPs or CIDRs used
by that proxy.
Public URL
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
SERVER_NAME |
empty | Required when background email needs absolute URLs | Flask URL generation | Public hostname without a scheme, such as safeharbor.example.com. |
Install |
PREFERRED_URL_SCHEME |
https |
Optional | Flask URL generation | Scheme used when Flask builds external URLs without an active request. | Install |
Set these when Safe Harbor sends emails that include application links, such as
password reset or account confirmation messages. Request-handled pages can infer
the host from inbound traffic, but background jobs need SERVER_NAME and
PREFERRED_URL_SCHEME to build correct absolute URLs.
Use PREFERRED_URL_SCHEME=https for normal public deployments. Use http only
for local testing or a trusted internal environment that intentionally serves
plain HTTP.
Sentry
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
SENTRY_DSN |
empty | Optional | App startup, observability | Enables Sentry error reporting when set; blank disables Sentry initialization. | Observability |
SENTRY_TRACES_SAMPLE_RATE |
0.0 |
Optional | Sentry SDK | Transaction sampling rate from 0.0 to 1.0; 0.0 records errors only. |
Observability |
The production validator rejects SENTRY_TRACES_SAMPLE_RATE values outside the
0.0 to 1.0 range. Leave SENTRY_DSN blank if you do not use Sentry.
When enabled, the Sentry environment name follows the resolved Flask config
name, such as development, testing, or production.
Cloudflare Tunnel Opt-In
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
TUNNEL_TOKEN |
empty | Required only when using the tunnel Compose profile |
cloudflared service |
Cloudflare Tunnel token from Cloudflare Zero Trust. | Deploy |
The Cloudflare Tunnel container does not run by default. It runs only when you
start Compose with COMPOSE_PROFILES=tunnel or include tunnel in a
comma-separated profile list.
Keep TUNNEL_TOKEN in the deployment .env or secret manager. Do not commit a
real token.
Backups Opt-In
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
BACKUP_SCHEDULE |
0 0 3 * * * |
Optional when using the backup profile |
Ofelia scheduler | Six-field cron expression for scheduled backups; default is daily at 03:00 UTC. | Backups |
BACKUP_RETENTION_DAILY |
7 |
Optional when using the backup profile |
Backup CLI | Number of recent backup tarballs to keep regardless of weekday. | Backups |
BACKUP_RETENTION_WEEKLY |
4 |
Optional when using the backup profile |
Backup CLI | Number of additional weekly backup tarballs to retain. | Backups |
Scheduled backups do not run by default. They run only when you start Compose
with COMPOSE_PROFILES=backup or include backup in a comma-separated profile
list such as COMPOSE_PROFILES=tunnel,backup.
The backups sidecar stays idle until Ofelia executes the labeled backup job.
Manual backup and restore commands use the same application backup code and the
same /backups directory.
Image Version
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
SAFEHARBOR_VERSION |
empty, which resolves to latest in Compose |
Optional, recommended for production | Docker image tag | Tag used by Compose for ghcr.io/danner26/safeharbor. |
Update |
Leave SAFEHARBOR_VERSION blank only when you intentionally want Compose to use
the moving latest tag. For production, pin a release tag such as 1.0.0 so
pulls, restarts, and rollbacks are predictable.
Changing SAFEHARBOR_VERSION affects the image tag only. It does not switch the
Flask config mode or replace required secrets.
Timezone
| Variable | Default | Required | Surface | Description | Docs |
|---|---|---|---|---|---|
DEFAULT_TZ |
UTC when unset |
Optional | Tank form defaults and timezone migration fallback | IANA timezone used as a fallback for new tank forms and timezone backfills. | - |
Use an IANA timezone name such as America/New_York. Browser JavaScript may
preselect the user's local timezone on modern clients, but DEFAULT_TZ remains
the server-side fallback for clients without that behavior.
Changing DEFAULT_TZ does not rewrite existing tank records. Each tank stores
its own timezone once created.