Skip to main content

Environment Variables

This page lists every environment variable the application, its workers, and its build read, with the default that applies when the variable is unset. Variables marked required have no usable default. Variables marked build-time are baked into the Next.js build and take effect only after the prod image is rebuilt, not after a restart.

The testplanit/.env.example file in the repository carries the same variables with commented examples. Setup pages describe how each area fits together; this page is the reference.

Core​

VariableDefaultDescription
DATABASE_URLrequiredPostgreSQL connection string, including ?schema=public.
NEXTAUTH_URLrequiredThe public URL of the application. Also forms the OAuth callback URL that issue-tracker integrations register with their providers: ${NEXTAUTH_URL}/api/integrations/oauth/<provider>/callback. Under a path prefix it must end in /api/auth; see Serving under a subpath.
APP_URLoptionalPublic URL of the install including any path prefix (https://example.com/testplanit). The base for every link the server writes. Derived from NEXTAUTH_URL when unset.
BASE_PATHbuild-timePath prefix the app is served under, e.g. /testplanit. A Docker build argument, not a runtime variable: it is compiled into the image. Empty = root. See Serving under a subpath.
NEXTAUTH_SECRETrequiredSession signing secret. Generate with openssl rand -base64 32.
ENCRYPTION_KEYrequiredKey for encrypting stored secrets such as integration credentials and two-factor seeds. Generate with openssl rand -hex 32. Changing it makes existing encrypted values unreadable.
VALKEY_URLrequiredValkey or Redis connection string, used for job queues, caches, rate limits, and real-time notifications.
INTERNAL_APP_URLsee descriptionAddress the server uses to call its own routes, such as when opening a shared report. Defaults to the address the server listens on (HOSTNAME:PORT in the Docker image), falling back to NEXTAUTH_URL. Set it only if neither is reachable from the server.

Database​

See External Database Deployment and Horizontal Read Scaling.

VariableDefaultDescription
DIRECT_DATABASE_URLDATABASE_URLA connection that bypasses a transaction-mode pooler such as pgbouncer. Schema sync, index creation, and audit-trigger setup use it. Required when DATABASE_URL points at a pooler.
DATABASE_POOL_MAX20Client-side connection pool size per process. One pool per replica; keep the total under the server's max_connections.
DATABASE_POOL_CONNECTION_TIMEOUT_MS10000How long a query waits for a free pool slot before failing. 0 waits without limit.
DATABASE_POOL_IDLE_TIMEOUT_MS10000How long an unused connection stays open before it is closed.
DATABASE_STATEMENT_TIMEOUT_MSunsetServer-side per-statement deadline. Set it on the web process only; workers run long statements legitimately.
DATABASE_REPLICA_URLSunsetComma-separated read-replica connection strings. Reads spread across them; writes and transactions stay on DATABASE_URL.
DATABASE_PRIMARY_STICKY_MS5000After a write, how long a user's reads stay on the primary so they see their own changes. 0 disables.
AUDIT_TRIGGER_BOOTSTRAPonSet to off to skip installing the audit triggers at startup, for example when the database role cannot run DDL.
AUDIT_TRIGGER_BOOTSTRAP_FATALunsetSet to 1 to abort startup when the audit triggers cannot be installed. Recommended on the worker tier. The default logs the failure and continues.
CREDENTIAL_ENCRYPTION_BOOTSTRAPunsetSet to off to skip the startup pass that encrypts code repository, issue integration, AI model and SSO provider credentials and outbound webhook URLs saved before they were encrypted at rest, and removes sign-in provider tokens stored by earlier versions.

Valkey and Job Queues​

VariableDefaultDescription
VALKEY_SENTINELSunsetComma-separated host:port list. When set, the app connects through Sentinel instead of VALKEY_URL directly; the password in VALKEY_URL still authenticates to the master.
VALKEY_SENTINEL_MASTERmymasterSentinel master name.
VALKEY_SENTINEL_PASSWORDunsetPassword for the Sentinel nodes themselves.
BULLMQ_PREFIXbullKey prefix for all job queues. Worker-group deployments set bull-<group> so each group works on its own keyspace. Letters, digits, _ and - only. The app and the workers serving it must use the same value.
SKIP_VALKEY_CONNECTIONunsetSet to true to skip connecting to Valkey. Used during builds.

See Search Configuration.

VariableDefaultDescription
ELASTICSEARCH_NODEunsetElasticsearch URL. Leave unset to disable search-backed features; keyword features fall back to the database where they can.

File Storage​

See File Storage.

VariableDefaultDescription
AWS_ACCESS_KEY_IDsee noteAccess key for S3 or S3-compatible storage (such as the bundled RustFS). Required for S3-compatible storage; optional on AWS.
AWS_SECRET_ACCESS_KEYsee noteSecret key for S3 or S3-compatible storage (such as the bundled RustFS). Required for S3-compatible storage; optional on AWS.
AWS_REGIONrequiredBucket region. AWS_BUCKET_REGION is accepted as a legacy alias.
AWS_BUCKET_NAMErequiredBucket name.
AWS_ENDPOINT_URLunsetStorage endpoint the server talks to. Leave unset for AWS S3; set to the storage service URL for S3-compatible storage (http://minio:9000 for the bundled RustFS).
AWS_PUBLIC_ENDPOINT_URLunsetBrowser-reachable storage URL for presigned links when S3-compatible storage sits behind a proxy. When unset on a hosted instance, files are served through the application instead.
MINIO_INTERNAL_ENDPOINTunsetBuild-time. An extra internal storage host to allow in the image optimizer.
UPLOAD_MAX_MB10Build-time. Per-file upload ceiling for attachments and inline images. Raise the bundled nginx limit to match.
IS_HOSTEDfalseRead it as "storage is not publicly readable". With no AWS_PUBLIC_ENDPOINT_URL this switches uploads to proxy mode and serves files through /api/storage. Required for a private S3 bucket, despite the name — see File Storage.
SELF_HOSTEDunsetBuild-time. Set to true for self-hosted images to turn off the Next.js image optimizer, so one image runs on any host without a baked-in domain allowlist.
BASE_DOMAINunsetBuild-time. Multi-tenant base domain; *.BASE_DOMAIN storage URLs are allowed in the image optimizer.
NEXT_PUBLIC_APP_URL, APP_URLunsetBuild-time. Fallback public URL for the image optimizer allowlist when AWS_PUBLIC_ENDPOINT_URL is unset.
Credentials are optional on AWS

Leave both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY unset and the AWS SDK falls back to its default provider chain — an EC2 instance role, EKS IRSA, an ECS task role, SSO, or the shared config file — so no long-lived keys sit on disk. Set both to use static credentials.

Setting only one is treated as setting neither. RustFS and other S3-compatible services accept static keys only, so those deployments must always set both. Temporary (STS) keys also work: set AWS_SESSION_TOKEN alongside the pair.

Authentication​

Single sign-on with SAML and OAuth2 providers is configured in Administration → Authentication, not with environment variables. The variables below cover the built-in social providers and the secrets behind tokens.

VariableDefaultDescription
PASSWORDLESS_DEVICE_BOUNDtrueMagic-link sign-in completes only in the browser that requested it. Set to false for a plain clickable link.
SIGNIN_ADMIN_CONTACTtrueShows the "Trouble signing in?" line on the sign-in page, linked to the first active admin's email. Set to false to remove it and hide the address.
API_TOKEN_SECRETNEXTAUTH_SECRETHMAC secret for hashing API tokens. Changing it invalidates every existing token.
TWO_FACTOR_ENCRYPTION_KEYNEXTAUTH_SECRETLegacy key used only to read two-factor secrets written before ENCRYPTION_KEY took over.
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETunsetEnables the Google sign-in button when both are set.
AZURE_AD_CLIENT_ID, AZURE_AD_CLIENT_SECRETunsetEnables the Microsoft Entra ID sign-in button when both are set.
AZURE_AD_TENANT_IDcommonEntra ID tenant for the button above.
APPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_PRIVATE_KEYunsetEnables the Apple sign-in button when all four are set. Newlines in the private key may be written as \n.
JIRA_CLIENT_ID, JIRA_CLIENT_SECRET, JIRA_REDIRECT_URIunsetLegacy global Jira OAuth credentials, consulted only when an integration has none of its own. New installs configure these per integration in the admin UI.
DISABLE_SCIM_RATE_LIMITfalseSet to true to disable the per-token SCIM request limit. Development only.

Email​

VariableDefaultDescription
EMAIL_SERVER_HOSTunsetSMTP host. Email features are off while unset.
EMAIL_SERVER_PORT587SMTP port.
EMAIL_SERVER_USER, EMAIL_SERVER_PASSWORDunsetSMTP credentials.
EMAIL_FROMunsetSender address for all outgoing mail.

API Rate Limiting​

See API Tokens.

VariableDefaultDescription
TIERprofessionalPreset hourly limit on authenticated API requests per instance: essentials 1,000, team 5,000, professional 10,000, dedicated 25,000.
API_RATE_LIMITunsetExplicit hourly limit. Overrides TIER.
DISABLE_API_RATE_LIMITfalseSet to true to turn rate limiting off. Use for load testing — Do not use in Production.
VariableDefaultDescription
ALLOWED_PRIVATE_HOSTSunsetComma-separated private or internal hostnames that outbound requests may reach: LLM endpoints, self-hosted git and issue trackers, and URL-based test generation. Private addresses are otherwise blocked.
WEBHOOK_OUTBOUND_ALLOW_HTTPfalseSet to true to let outbound webhooks target plain http:// URLs. Exists for test harnesses; never set it in production.
LINK_PREVIEW_MODEsafeWhat unauthenticated link previews show. safe shows the record kind only; names also shows the record and project name.
CRAWL_SCREENSHOTSfalseSet to true to capture a screenshot per page during URL-based test generation and offer it to vision-capable models. The official workers image enables this.
CHROMIUM_EXECUTABLE_PATHunsetChromium binary used for screenshots. The official workers image sets it.

Real-Time Notifications​

See SSE Notifications.

VariableDefaultDescription
SSE_PER_TENANT_CAP1000Open notification streams allowed per tenant.
SSE_PER_USER_CAP10Open notification streams allowed per user.
SSE_ISSUES_PER_TENANT_CAP1000Open issue-sync streams allowed per tenant.
SSE_ISSUES_PER_USER_CAP8Open issue-sync streams allowed per user.

Workers​

See Background Processes. Each variable sets how many jobs the named worker processes at once.

VariableDefaultWorker
TESTMO_IMPORT_CONCURRENCY1Testmo import
SYNC_CONCURRENCY2Issue sync
EMAIL_CONCURRENCY3Email
NOTIFICATION_CONCURRENCY5Notifications
FORECAST_CONCURRENCY5Forecast
ELASTICSEARCH_REINDEX_CONCURRENCY2Search reindex
AUDIT_LOG_CONCURRENCY10Audit log
BUDGET_ALERT_CONCURRENCY2AI budget alerts
ALERT_CONCURRENCY2Alerts
WEBHOOK_ACTIONS_CONCURRENCY2Inbound webhook actions
AUTO_TAG_CONCURRENCY3Auto-tag
DERIVE_CASE_STEPS_CONCURRENCY2Derive case steps
WEBHOOK_DISPATCH_CONCURRENCY5Webhook dispatch
EXECUTION_DISPATCH_CONCURRENCY3Automated execution dispatch
IMPACT_ANALYSIS_CONCURRENCY1Impact Analysis
FAILURE_TRIAGE_CONCURRENCY2Failure triage
RISK_SCORE_CONCURRENCY2Risk scoring

The repo cache, copy/move, duplicate scan, Magic Select, and step scan workers always process one job at a time. The matching *_CONCURRENCY variables for them only change the value shown on the Job Queues page.

Memory ceilings, applied by the process manager in the workers image. Each worker restarts when its resident size passes its ceiling, and its Node heap defaults to 75% of that ceiling, so setting a ceiling alone is enough. Set a *_MAX_OLD_SPACE_MB value only to choose the heap yourself.

WORKER_* raises every worker whose default ceiling is 512M. Workers with higher defaults keep them. A worker-specific variable takes precedence over WORKER_*, and a heap set in WORKER_MAX_OLD_SPACE_MB is ignored for a worker whose own ceiling is set. A heap must be smaller than its ceiling; one that is not is ignored, and the 75% default applies.

VariableDefaultDescription
WORKER_MAX_MEMORY_RESTART512MRestart each 512M-tier worker above this resident size.
WORKER_MAX_OLD_SPACE_MB75% of the ceilingNode heap for each 512M-tier worker.
TESTMO_IMPORT_MAX_MEMORY_RESTART4GRestart the Testmo import worker above this resident size.
TESTMO_IMPORT_MAX_OLD_SPACE_MB75% of the ceilingNode heap for the Testmo import worker.
ELASTICSEARCH_REINDEX_MAX_MEMORY_RESTART2GRestart the reindex worker above this resident size.
ELASTICSEARCH_REINDEX_MAX_OLD_SPACE_MB75% of the ceilingNode heap for the reindex worker.
REPO_CACHE_MAX_MEMORY_RESTART512MRestart the repo cache worker above this resident size. Raise it for very large repositories.
REPO_CACHE_MAX_OLD_SPACE_MB75% of the ceilingNode heap for the repo cache worker.

Testmo Import​

See Import from Testmo. Chunk sizes are rows per batch write.

VariableDefault
TESTMO_IMPORT_TRANSACTION_TIMEOUT_MS900000 (15 minutes)
TESTMO_AUTOMATION_TRANSACTION_TIMEOUT_MS2700000 (45 minutes)
TESTMO_REPOSITORY_CASE_CHUNK_SIZE500
TESTMO_TEST_RUN_CASE_CHUNK_SIZE500
TESTMO_TEST_RUN_RESULT_CHUNK_SIZE2000
TESTMO_ISSUE_RELATIONSHIP_CHUNK_SIZE1000
TESTMO_AUTOMATION_CASE_CHUNK_SIZE500
TESTMO_AUTOMATION_RUN_CHUNK_SIZE500
TESTMO_AUTOMATION_RUN_TEST_CHUNK_SIZE2000
TESTMO_AUTOMATION_RUN_FIELD_CHUNK_SIZE500
TESTMO_AUTOMATION_RUN_LINK_CHUNK_SIZE500
TESTMO_AUTOMATION_RUN_TEST_FIELD_CHUNK_SIZE500
TESTMO_AUTOMATION_RUN_TAG_CHUNK_SIZE500

Audit Log Retention​

See Retention. In multi-tenant deployments, a tenant's auditLogRetentionDays and auditLogRetentionMode override the first two.

VariableDefaultDescription
AUDIT_LOG_RETENTION_DAYSunsetKeep this many days of audit log entries in the database. Unset, retention is off and every entry is kept.
AUDIT_LOG_RETENTION_MODEarchiveWhat happens to older entries: archive moves them to object storage, delete removes them permanently.
AUDIT_LOG_ARCHIVE_BUCKETAWS_BUCKET_NAMEBucket for archive files. Uses the same endpoint and credentials as file storage.

Parameterized Test Runs​

VariableDefaultDescription
PARAMETERIZED_RUN_HARD_CAP5000Run creation refuses more total iterations than this.
PARAMETERIZED_RUN_SOFT_CAP1000Above this many iterations, creation asks for confirmation.
PARAMETERIZED_RUN_ASYNC_CAP500Above this many iterations, the run is built by a background job.

Frozen Reports​

See Live and Frozen Data.

VariableDefaultDescription
REPORT_SNAPSHOT_MAX_ROWS10000Most rows a frozen report keeps. A larger report can still be frozen after a warning, keeping the first rows. Also the most rows the Execution Log returns to a shared live link or an API request for every row.

Magic Select​

See Magic Select.

VariableDefaultDescription
MAGIC_SELECT_TRUNCATE_CASE_NAME80Characters of each case name sent to the model.
MAGIC_SELECT_TRUNCATE_TEXT_LONG100Characters of each long text field sent to the model.
MAGIC_SELECT_TRUNCATE_OTHER_FIELD100Characters of other fields sent to the model.
MAGIC_SELECT_TRUNCATE_ISSUE_DESC250Characters of each linked issue description sent to the model.
MAGIC_SELECT_SEARCH_THRESHOLD250Case count above which search pre-filters the candidates.
MAGIC_SELECT_MIN_KEYWORD_LENGTH3Shortest keyword used for search pre-filtering.
MAGIC_SELECT_MIN_SEARCH_SCORE50.0Lowest search score kept as a candidate.
MAGIC_SELECT_MAX_SEARCH_RESULTS2000Most candidates taken from search.

Impact Analysis​

See Test Impact Analysis.

VariableDefaultDescription
IMPACT_DIFF_TOKEN_BUDGET12000Tokens of diff sent to the model per analysis.
IMPACT_TRUNCATE_PATCH_CHARS4000Characters of each file's patch kept in the prompt.
IMPACT_MAX_PATCH_FILES40Files whose patch text is included; the rest are summarized by path.
IMPACT_MAX_DIFF_FILES500Most changed files read from the provider.
IMPACT_TRUNCATE_CASE_NAME80Characters of each case name sent to the model.
IMPACT_TRUNCATE_TEXT_LONG100Characters of each long text field sent to the model.
IMPACT_TRUNCATE_OTHER_FIELD100Characters of other fields sent to the model.
IMPACT_AI_FULL_REPO_THRESHOLD250Case count up to which every case is an AI candidate.
IMPACT_MAX_AI_CANDIDATES150Most cases the model ranks, strongest signal first. Cases a pin or another signal already settles are not sent.
IMPACT_AI_SAMPLE_SIZE50Most folder neighbours added under the cap in large repositories.
IMPACT_MIN_SEARCH_SCORE5Lowest keyword-search score kept.
IMPACT_SEARCH_RELATIVE_CUTOFF0.1Keyword hits scoring under this fraction of the top hit are dropped (0 to 1).
IMPACT_MAX_SEARCH_RESULTS500Most keyword-search hits kept.
IMPACT_BM25_SATURATION20Search score at which the keyword signal reaches its cap.
IMPACT_MIN_SCORE20Lowest score a case needs to appear at all. Pinned cases always appear.
IMPACT_AFFECTED_THRESHOLD50Score at which a case moves from the related to the affected tier.
IMPACT_HISTORY_LOOKBACK_DAYS365How far back earlier analyses count as history.
IMPACT_HISTORY_MAX_ANALYSES25Most earlier analyses consulted.
IMPACT_MAX_ANCHOR_FETCHES50Pinned files fetched at the base commit to relocate line and symbol pins.
IMPACT_THINKING_BUDGET1024Thinking tokens allowed to models that support it.
IMPACT_LINKED_EXPANSIONtrueAlso select cases linked to strongly selected cases.
IMPACT_REUSE_HOURS24Reuse a completed analysis of the same commits within this window.
IMPACT_ISSUE_MAX_COMMIT_FETCHES25Commits naming a ticket whose own file list is read per analysis.
IMPACT_ISSUE_SCAN_LOOKBACK_DAYS90How far back the ticket scan reads commits on each cache refresh.
IMPACT_ISSUE_SCAN_MAX_COMMITS300Most commits the ticket scan reads.
IMPACT_ISSUE_SCAN_MAX_COMMIT_FETCHES100Ticket commits whose file list the scan reads per refresh.
IMPACT_ISSUE_SCAN_MAX_FILES_PER_COMMIT50A ticket commit touching more files than this is skipped.
IMPACT_ISSUE_SCAN_FULL_MAX_COMMITS20000Commits a manual full-history ticket scan reads from the repository per run; running it again continues from where the last run stopped.
IMPACT_ISSUE_SCAN_FULL_MAX_COMMIT_FETCHES2000Matched commits whose file lists a full-history scan reads.
IMPACT_ISSUE_IMPORT_MAX_LOOKUPS100Tracker lookups a refresh-time ticket scan may make to import unknown tickets.
IMPACT_ISSUE_IMPORT_FULL_MAX_LOOKUPS5000Tracker lookups a full-history ticket scan may make to import unknown tickets.
IMPACT_ISSUE_SCAN_SYMBOL_PINStrueTicket scans read each commit's diff and pin the changed functions or classes; false pins whole files only (cheaper on providers without a server-side diff).
IMPACT_BUILD_PR_MAX_COMMITS100Push auto-runs: newest commits of the range asked for the pull requests they were merged through, for the run's description.
IMPACT_BUILD_BOT_COMMIT_PATTERN^Update version code toPush auto-runs: commits whose first message line matches this regular expression are a build bot's and are left out of the run's description. Empty = none.

Multi-Tenant Deployments​

See Multi-Tenant Workers.

VariableDefaultDescription
MULTI_TENANT_MODEfalseSet to true to serve several tenants from one worker process, each with its own database.
INSTANCE_TENANT_IDdefaultPins an application pod to one tenant.
TENANT_CONFIG_FILE/config/tenants.jsonJSON file of tenant configurations. Re-read when it changes.
TENANT_CONFIGSunsetJSON object of tenant configurations keyed by tenant id, with databaseUrl, replicaUrls, elasticsearchNode, elasticsearchIndex, baseUrl, auditLogRetentionDays, and auditLogRetentionMode. Overrides the file.
TENANT_<ID>_DATABASE_URLunsetPer-tenant database, as an alternative to the file or JSON. Companions: TENANT_<ID>_DATABASE_REPLICA_URLS, TENANT_<ID>_ELASTICSEARCH_NODE, TENANT_<ID>_ELASTICSEARCH_INDEX, TENANT_<ID>_BASE_URL, TENANT_<ID>_AUDIT_LOG_RETENTION_DAYS, TENANT_<ID>_AUDIT_LOG_RETENTION_MODE.
TENANT_SECRETS_NAMESPACEpod namespaceKubernetes namespace holding per-tenant secrets.
TENANT_SECRET_NAME_TEMPLATEtpi-{tenant}-envName of each tenant's Kubernetes secret; {tenant} is replaced by the tenant id.
TENANT_SECRETS_CACHE_TTL_MS600000How long a tenant secret is cached before it is read again.
KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORTkubernetes.default.svc, 443API server used to read tenant secrets. Kubernetes sets these in every pod.

Trial Instances​

VariableDefaultDescription
IS_TRIAL_INSTANCEfalseMarks a time-limited trial.
TRIAL_END_DATEunsetWhen the trial ends.
FEEDBACK_SURVEY_URLunsetSurvey link shown to trial users.
NEXT_PUBLIC_CONTACT_EMAIL[email protected]Contact shown when a trial has expired.
NEXT_PUBLIC_WEBSITE_URLhttps://testplanit.comWebsite link shown when a trial has expired.

Build-Time Variables​

These are read while the application is built. The release pipeline sets the version variables; operators building their own image may set the others.

VariableDefaultDescription
NEXT_PUBLIC_APP_VERSION, NEXT_PUBLIC_GIT_COMMIT, NEXT_PUBLIC_GIT_BRANCH, NEXT_PUBLIC_GIT_TAG, NEXT_PUBLIC_BUILD_DATEgeneratedVersion information shown in the application. Written by the build script.
NEXT_PUBLIC_FORCE_RTLunsetSet to true to render every locale right-to-left, for checking the mirrored layout.
DOCKER_BUILDunsetSet to true inside the image build to skip the local build cache.
CIunsetSet by CI runners; also skips the local build cache.
SELF_HOSTED, BASE_DOMAIN, UPLOAD_MAX_MB, MINIO_INTERNAL_ENDPOINT, NEXT_PUBLIC_APP_URLsee File StorageStorage and image optimizer settings that are also build-time.
NEXT_SERVER_ACTIONS_ENCRYPTION_KEYrandom per buildA base64 32-byte key (openssl rand -base64 32) that keeps pages left open during an upgrade working. Set it in the environment for pnpm build or docker buildx bake; with docker build, add --secret id=next_actions_key,env=NEXT_SERVER_ACTIONS_ENCRYPTION_KEY. The key is stored inside the built image, so never reuse ENCRYPTION_KEY.

Seeding, Development, and Testing​

VariableDefaultDescription
ADMIN_EMAIL, ADMIN_NAME, ADMIN_PASSWORDunsetThe first administrator account, created when the database is seeded.
TEST_REGULAR_USER_PASSWORDunsetPassword for the seeded non-admin user in test environments.
TEST_EMAIL_DOMAINexample.comDomain for generated E2E test user addresses.
E2E_PRODunsetSet to on to run E2E tests against a production build. See E2E Testing.
RUN_DB_INTEGRATIONunsetSet to 1 to run the live-database integration tests.
TEST_VALKEY_URLunsetValkey used by tests that need a real instance.
DEBUG_FORECASTunsetAny value turns on verbose forecast logging.

Docker Compose​

The variables in this section are read by Docker Compose as it reads docker-compose.prod.yml, not by the application. Compose resolves them from your shell, from the default .env file beside the compose file, or from a file passed with --env-file. It does not read .env.production, which is a runtime env_file: a value placed only there leaves the default in effect, and Compose reports no error.

VariableDefaultDescription
TESTPLANIT_VERSIONlatestRelease tag for the prod, workers, and db-init-prod images, for example 1.1.12.
DOCKER_PROD_APP_PORT30000Host port published for the application.
DOCKER_POSTGRES_PORT5432Host port published for the bundled PostgreSQL (with-postgres).
DOCKER_VALKEY_PORT6379Host port published for the bundled Valkey (with-valkey).
DOCKER_ELASTICSEARCH_HTTP_PORT9200Host port published for the bundled Elasticsearch HTTP API (with-elasticsearch).
DOCKER_ELASTICSEARCH_TRANSPORT_PORT9300Host port published for the Elasticsearch transport protocol (with-elasticsearch).
DOCKER_MINIO_API_PORT9000Host port published for the bundled file storage S3 API (with-minio).
DOCKER_MINIO_CONSOLE_PORT9001Host port published for the file storage console (with-minio).
DOCKER_NGINX_HTTP_PORT80Host port published for the bundled nginx (with-minio). Point your external reverse proxy here.
PROD_DATABASE_POOL_MAX100DATABASE_POOL_MAX for the application container. Overrides any value in .env.production.
WORKERS_DATABASE_POOL_MAX60DATABASE_POOL_MAX for the worker fleet. Overrides any value in .env.production.
WORKERS_MEMORY_LIMIT4GMemory cap for the worker fleet.
POSTGRES_CPU_LIMIT2CPU cap for the bundled PostgreSQL.
POSTGRES_SHM_SIZE256mbCap on the bundled PostgreSQL's /dev/shm, where parallel query workers put shared memory segments.
UPLOAD_MAX_MB10Per-file ceiling for attachments and inline document images. Takes effect on restart.
SELF_HOSTEDtrueBuild argument. Turns off the Next.js image optimizer. Applies only to up --build.
BASE_DOMAINexample.comBuild argument, consulted only when SELF_HOSTED is false. Applies only to up --build.

SELF_HOSTED and BASE_DOMAIN are baked into the image when it is built, so setting them has no effect while running the prebuilt ghcr.io/testplanit/testplanit-selfhost images. See Build-Time Variables.

Setting compose variables​

Put them in a local .env beside the compose file. Compose loads that file automatically on every command, and it is not tracked by git:

# testplanit/.env
TESTPLANIT_VERSION=1.1.12
DOCKER_PROD_APP_PORT=8080
POSTGRES_SHM_SIZE=1gb

Per-command alternatives:

# Inline, for one command
WORKERS_MEMORY_LIMIT=20G docker compose -f docker-compose.prod.yml up -d

# From a named file
docker compose --env-file prod.vars -f docker-compose.prod.yml up -d

Changing values the compose file does not expose​

Settings with no variable in the table above — an added volume, a changed healthcheck, extra PostgreSQL flags, another service — go in a second compose file that Compose merges over the first. Leave docker-compose.prod.yml unedited: it is tracked in git, and upgrading replaces it.

# testplanit/docker-compose.custom.yml
services:
postgres:
command:
- postgres
- -c
- max_connections=400

The repository ignores every docker-compose.*.yml file except the ones it ships, so a file named this way persists across upgrades and never appears in git status. Keep both parts of the name: a .yaml extension, or the shorter compose.* form, is not ignored and would show up as an untracked file.

Pass both files, in this order, on every command:

docker compose -f docker-compose.prod.yml -f docker-compose.custom.yml \
--profile with-postgres up -d

Naming that file docker-compose.override.yml has no effect. Compose loads an override file automatically only when it discovers the compose file itself, and every command in these guides names -f docker-compose.prod.yml explicitly. To avoid repeating both flags, name the pair in .env and run docker compose with no -f:

# testplanit/.env
COMPOSE_FILE=docker-compose.prod.yml:docker-compose.custom.yml

Run docker compose from testplanit/ when you do this. The paths are relative, and without a -f flag Compose has no compose file from which to locate .env, so from any other directory it reports no configuration file provided: not found. The -f form above has no such restriction: it works from the repository root as -f testplanit/docker-compose.prod.yml -f testplanit/docker-compose.custom.yml.

Confirm the merge took effect before starting the stack. config prints the combined result, so your change appears there if the file is being read:

docker compose -f docker-compose.prod.yml -f docker-compose.custom.yml config

Merging replaces single values, but it appends list entries. Repeating a ports or volumes entry therefore adds it alongside the original rather than replacing it, which leaves the original host port published as well. Change a published port with its DOCKER_*_PORT variable, or replace the whole list with the !override tag (Compose 2.24.4 or newer):

services:
prod:
ports: !override
- '8080:3000'