Skip to content

Configuration

All configuration is via environment variables. For local development, docker-compose.yml sets sensible defaults.

VariableDescriptionExample
SECRET_KEYDjango secret key. Use a long random string.$(python -c "import secrets; print(secrets.token_urlsafe(50))")
DATABASE_URLPostgreSQL connection string. Include ?sslmode=require — production refuses to boot on a URL without an sslmode parameter (the connection could otherwise fall back to unencrypted transport), unless you set TRUEPPM_ALLOW_UNENCRYPTED_DB=true because TLS is enforced at the network layer.postgres://trueppm:password@db:5432/trueppm?sslmode=require
REDIS_URLConnection string for the Celery broker / Channels layer. TruePPM ships with Valkey (BSD-licensed Redis fork, wire-compatible); the redis:// scheme works against either.redis://valkey:6379
DJANGO_SETTINGS_MODULESettings module to load.trueppm_api.settings.prod
ALLOWED_HOSTSComma-separated list of allowed hostnames.trueppm.example.com
INTEGRATION_ENCRYPTION_KEYFernet key that encrypts stored integration credentials (connected-account PATs) at rest. Production refuses to boot if this is empty — the guard runs at settings-import time, so a missing key crash-loops the deploy rather than failing later.$(python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
Attachment storagePick one: set TRUEPPM_DEFAULT_FILE_STORAGE to a persistent object-storage backend, or set TRUEPPM_ALLOW_LOCAL_ATTACHMENT_STORAGE=true if local disk is backed by a persistent volume. Production refuses to boot on the ephemeral local default otherwise (see optional settings for the two variables).storages.backends.s3.S3Storage
VariableDefault
SECRET_KEYdev-secret-key-change-in-prod
DATABASE_URLpostgres://trueppm:trueppm@db:5432/trueppm
REDIS_URLredis://valkey:6379
DJANGO_SETTINGS_MODULEtrueppm_api.settings.dev
ALLOWED_HOSTS*
VariableDefaultDescription
TRUEPPM_EDITIONcommunityEdition discriminator read by /api/v1/edition/. Set to enterprise in the enterprise Helm chart so the React shell can make the post-login redirect decision without importing enterprise code (ADR-0029). Never set this in an OSS deployment.
HISTORY_RETENTION_DAYS90How many days of object-change history to keep. Records older than this are purged nightly by Celery beat. To disable automatic purging, set the Django setting to None in a settings override or toggle the table off in the Retention & purge editor. Do not set 0 — a zero-day window makes the cutoff “now” and purges all rows on the next run.
TASK_RUN_RETENTION_DAYS30How many days of completed/failed/canceled Celery task-run records to keep before the nightly purge. To disable, set the Django setting to None in a settings override or toggle the table off in the Retention & purge editor. Do not set 0 — a zero-day window purges all rows on the next run.
MSPROJECT_MAX_UPLOAD_MB50Per-file size cap for MS Project (.mpp / .xml) imports, in megabytes. See MS Project import limit below.
VITE_FEATURE_FLAGS{}Build-time JSON blob of feature flag overrides for the React frontend, e.g. '{"schedule_build_mode_v1":true}'. Set in packages/web/.env or .env.production before npm run build. Per-user localStorage overrides win over this default at runtime.
TRUEPPM_DEFAULT_FILE_STORAGEdjango.core.files.storage.FileSystemStorageBackend for task-attachment storage. The local default is ephemeral in a container — uploads are lost on every pod restart, and prod refuses to boot on it (see TRUEPPM_ALLOW_LOCAL_ATTACHMENT_STORAGE). Point this at a persistent object-storage backend for production, e.g. storages.backends.s3.S3Storage.
TRUEPPM_ALLOW_LOCAL_ATTACHMENT_STORAGEfalseOperator opt-in to run production on the local FileSystemStorage default (e.g. when local disk is backed by a persistent volume). prod refuses to boot on local storage unless this is true or TRUEPPM_DEFAULT_FILE_STORAGE is set to a remote backend.
TRUEPPM_ATTACHMENT_STORAGE_SIGNS_URLSfalseOperator opt-in confirming TRUEPPM_DEFAULT_FILE_STORAGE produces a real time-limited signed URL. The attachment Get signed download URL action only recognizes the built-in django-storages S3, GCS, and Azure Blob backends automatically; on FileSystemStorage (the default) or any other backend it refuses with 501 Not Implemented rather than hand back a link labeled “signed” that never actually expires. Set this to true only if your configured backend genuinely signs its URLs.
TRUEPPM_ALLOW_UNENCRYPTED_DBfalseOperator opt-in to run production against a DATABASE_URL that has no sslmode parameter (e.g. when TLS to the database is enforced at the network layer). prod refuses to boot on such a URL unless this is true; when set, the boot logs a warning instead.
CSRF_TRUSTED_ORIGINS(empty)Comma-separated origins (scheme included) trusted for cross-origin POST/CSRF. Required only for split-origin deploys where the web app and API are served from different hostnames, e.g. https://app.example.com,https://api.example.com.
TRUEPPM_FRONTEND_BASE_URL(empty)Public origin the web app is served from, e.g. https://trueppm.example.com. Used to build absolute task deep-links in notification emails (e.g. task.blocked). Leave empty to omit the link — emails still carry the blocker type, age, and actor. No trailing slash, no path. The legacy bare FRONTEND_BASE_URL is still accepted as a fallback.
TRUEPPM_AUTH_REFRESH_COOKIE_SECUREtrueSets the Secure flag on the refresh-token cookie. The browser drops a Secure cookie over plain HTTP, so set false only on a non-HTTPS dev/preview host. The dev settings already default this to false for localhost. Legacy bare AUTH_REFRESH_COOKIE_SECURE still accepted.
TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITEStrictSameSite policy for the refresh cookie. Strict blocks the cookie on any cross-site request. Split-origin deploys must relax this to Lax or None so the refresh request carries the cookie; None additionally requires Secure (HTTPS). See split-origin notes. Legacy bare AUTH_REFRESH_COOKIE_SAMESITE still accepted.
TRUEPPM_AUTH_REFRESH_COOKIE_NAMEtrueppm_refreshName of the refresh-token cookie. Override only to avoid a collision with another app on the same domain. Legacy bare AUTH_REFRESH_COOKIE_NAME still accepted.
TRUEPPM_AUTH_REFRESH_COOKIE_PATH/api/v1/auth/token/refresh/Path the refresh cookie is scoped to. Override only if you reverse-proxy the API under a non-default base path. Legacy bare AUTH_REFRESH_COOKIE_PATH still accepted.
CSP_CONNECT_SRC'self' wss:Space-separated connect-src sources for the Content-Security-Policy header — the origins the browser may open XHR / fetch / WebSocket connections to. Split-origin deploys must add the API origin (and its wss:// origin) here, e.g. 'self' https://api.example.com wss://api.example.com. See split-origin notes.
TRUEPPM_WEBHOOK_RETENTION_DAYS7Days of webhook delivery records to keep before the nightly purge. See Retention.
TRUEPPM_EXPORT_RETENTION_DAYS7Days of generated export artifacts to keep before purge. See Retention.
TRUEPPM_SYNC_BATCH_RETENTION_HOURS24Hours of processed offline-sync upload batches to keep before purge. See Retention.
RETENTION_PURGE_INFLIGHT_SECONDS600Lock TTL (seconds) guarding against overlapping retention-purge runs. See Retention.
TRUEPPM_BEAT_STALE_SECONDS120Age (seconds) after which the last Celery-beat heartbeat is considered stale by /health/beat/. See Durability.
TRUEPPM_RECURRENCE_HORIZON_DAYS14Look-ahead window (days) for spawning recurring-task occurrences. See Recurring tasks.
SYNC_WATERMARK_USE_COLUMNtrueSource for the offline-sync pull watermark. When true, the pull reads the denormalized Project.last_sync_version column. Set false to fall back to the slower 12-table UNION ALL watermark — a one-release escape hatch if a watermark-drift bug is found in production. The conformance test asserts the two sources agree, so the column source is safe by default.
WORKFLOW_BACKENDtrueppm_api.workflows.backends.default.DefaultWorkflowBackendDotted path to the WorkflowBackend implementation for the durable-execution engine. The OSS default composes the transactional outbox with Celery. Enterprise editions register an alternate backend (e.g. Temporal) by overriding this; do not change it in an OSS deployment.
WORKFLOW_HISTORY_RETENTION_DAYS30Days of WorkflowHistoryEvent records to keep before the nightly workflows.purge_old_records task purges them. Set the Django setting to None (or 0) to disable history purging.
WORKFLOW_DRAIN_BATCH_SIZE200Maximum rows the workflow outbox/timer drains process per tick. Bounds the work per run so a large backlog (e.g. after a broker outage) cannot exceed the Celery task time limit — later ticks drain the remainder.
WORKFLOW_PURGE_BATCH_SIZE500Rows deleted per statement by the nightly workflow retention purge. The purge deletes in bounded chunks rather than one unbounded statement, so the first run on a mature install cannot hold a long lock over a large slice of the history/outbox tables.
IDEMPOTENCY_RETENTION_HOURS24Hours to retain stored Idempotency-Key responses, purged hourly by the Celery beat task. After expiry, a retry with the same key re-runs the mutation. Set the Django setting to None to disable automatic purging.
IDEMPOTENCY_MAX_BODY_BYTES1048576Maximum stored response body size, in bytes (1 MiB default). Responses larger than this are not stored — the claim row is dropped so a retry re-runs the mutation. Single-object mutation responses effectively never approach this limit.
EMAIL_HOST / EMAIL_PORT / EMAIL_HOST_USER / …(Django default)SMTP settings for notification and invite email. Currently must be set via a Django settings override — dedicated env-var / Helm bindings are not yet wired, so setting bare EMAIL_HOST env vars has no effect. See Outbound email.

MS Project import accepts .mpp and .xml files. The per-file size cap is configurable:

VariableDefaultUnitWhat it bounds
MSPROJECT_MAX_UPLOAD_MB50MBMaximum size of a single MS Project import upload

This cap was raised from a previously hardcoded 10 MB. An import is read fully into memory and stored base64-encoded in a single database row (about +33%), so a 50 MB upload already costs roughly 67 MB of memory and row size — keep the limit close to the practical MS Project file ceiling rather than maximizing it.

Terminal window
# Allow MS Project imports up to 80 MB. Must stay <= 100 MB
# (DATA_UPLOAD_MAX_MEMORY_SIZE / nginx client_max_body_size).
MSPROJECT_MAX_UPLOAD_MB=80

Imported files are stored base64-encoded in an ImportRequest row only until the import is processed, then purged on the schedule set by TRUEPPM_IMPORT_RETENTION_DAYS (default 7 days). See Outbox & Record Retention to tune that window.

The Community (OSS) tier bounds Monte Carlo risk analysis with three caps. Like the email settings above, these are Django settings constants, not environment variables — override them in a Django settings module; setting bare env vars of the same name has no effect. The Enterprise edition raises or removes them.

SettingDefault (OSS)What it bounds
MC_SIMULATION_CAP1000Maximum simulation runs (iterations) per request. The Monte Carlo run endpoint rejects an n_simulations above this.
MC_TASK_CAP5000Largest project — by task count — Monte Carlo will run on. The vectorized NumPy path handles 5000 tasks × 1000 runs in a few seconds; a larger project is refused rather than run unbounded.
MC_HISTORY_CAP100Forecast-history rows kept per project. The nightly purge trims each project to its newest MC_HISTORY_CAP MonteCarloRun rows.

Set any cap to None for unlimited — the Enterprise default, where unbounded forecast history plus cross-program rollup is part of the portfolio tier. Operators on constrained hardware can lower MC_TASK_CAP to keep simulations cheap.

# settings override — raise the task ceiling for a large-project deployment.
MC_TASK_CAP = 10_000

A standard deploy serves the web app from the same origin as the API (e.g. nginx routes / to the SPA and /api to Django on one hostname). The secure defaults assume this and need no extra configuration.

A split-origin deploy serves the SPA from a different origin than the API — for example https://app.example.com (SPA) and https://api.example.com (API). Two security defaults are origin-aware and must be relaxed for the browser to talk to the API:

SettingWhy it must changeSet to
AUTH_REFRESH_COOKIE_SAMESITEThe refresh cookie is SameSite=Strict, so the browser will not send it on the cross-origin refresh request, and sessions silently fail to renew.Lax (or None if the SPA and API are on unrelated sites — None requires HTTPS, which the Secure flag already enforces).
CSP_CONNECT_SRCThe Content-Security-Policy connect-src defaults to 'self' wss:. With the SPA on a different origin, the browser blocks XHR / WebSocket connections to the API origin.'self' https://api.example.com wss://api.example.com — add the API origin and its wss:// origin.

Also set CSRF_TRUSTED_ORIGINS (above) for any split-origin deploy.

No manual steps are needed: the api container runs migrations and the create_admin bootstrap automatically on startup. Retrieve the generated admin password as described in Admin password setup.

The admin user can then authenticate via the API and create projects. When a user creates a project, they automatically become its Owner.