|
Documentation: Configuration reference

Deploy

Configuration reference

Every environment variable that matters, grouped by concern, plus the compose overrides for common topologies.

api-pacs: core

Variable Default Purpose
API_NAME, API_URL_REST_PORT api-pacs, 8000 Do not change unless compose and nginx wiring change too
APP_URL none Public URL used in emails and links
API_BASE_URL http://api-pacs:8000/v1 Internal base URL of the API
APP_TIMEZONE America/Toronto IANA timezone for logs, ingestion windows and Postgres sessions
DOCKER_NETWORK pacs-net External network attached to inference containers
DOCKER_USERNAME, DOCKER_PASSWORD none Registry credentials for private inference images (not the Docker socket, which compose mounts read-only)
POSTGRES_DB_HOST, POSTGRES_DB_PORT, POSTGRES_DB_DATABASE, POSTGRES_DB_USERNAME, POSTGRES_DB_PASSWORD postgresql, 5432, db_pacs, pacs, dev password Control-plane database; replace the dev password before any non-dev install
REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_IAM_DB redis, 6379, dev password, 1 IAM cache and rate-limit counters; the password must match redis/redis.conf and the cardio-agent REDIS_URL
ELASTICSEARCH_URL, KIBANA_BASE_URL http://elasticsearch:9200, http://kibana:5601 Study index and log deep links
FIREBASE_PROJECT_ID, FIREBASE_CONFIG_FILE_PATH, FIREBASE_WEB_API_KEY, FIREBASE_SUPERUSER_KEY none Firebase Admin SDK, tenant-aware password sign-in, and the superuser key that gates first-owner bootstrap and privileged endpoints
ORTHANC_AET, ORTHANC_BASE_URL, ORTHANC_LOCAL_CACHE_EXPIRATION_IN_HOURS PACS_AI, http://orthanc:8042, 24 Calling AE title, Orthanc REST URL, DICOM cache TTL
MAILGUN_API_KEY, MAILGUN_DOMAIN, MAILGUN_SENDER_EMAIL none Transactional email
MAILCHIMP_API_KEY, MAILCHIMP_BASE_URL, MAILCHIMP_LIST_ID none Optional marketing list sync
DOCUSIGN_INTEGRATION_KEY, DOCUSIGN_USER_ID, DOCUSIGN_ACCOUNT_BASE_URI, DOCUSIGN_AUTH_SERVER, DOCUSIGN_PRIVATE_KEY, DOCUSIGN_ACCOUNT_ID none Optional DocuSign JWT auth for e-signature flows
OPENAPI_DOCS_PASSWORD none Basic-auth password for the /docs OpenAPI UI; empty disables the gate
ORCHESTRATOR_API_URL http://medical-agent:8585 URL of the optional orchestrator service

api-pacs: ingestion, reconciliation and study-service handoff

Variable Default Purpose
INFERENCE_INGESTION_RUNNER_INTERVAL_MINUTES 1 Discovery (C-FIND) loop period
INFERENCE_INGESTION_RETRIEVAL_WORKER_INTERVAL_MINUTES 1 C-MOVE retrieval loop period
INFERENCE_INGESTION_RECONCILIATION_INTERVAL_MINUTES 5 Reconciliation loop period
INFERENCE_INGESTION_RECONCILIATION_PENDING_MINUTES, INFERENCE_INGESTION_RECONCILIATION_QUEUED_MINUTES, INFERENCE_INGESTION_RECONCILIATION_RUNNING_MINUTES 2, 10, 65 Per-state stale thresholds for processing executions
INFERENCE_INGESTION_RECONCILIATION_MODEL_RUNNING_MINUTES none JSON object of per-model running-stale overrides in minutes
INFERENCE_INGESTION_RECONCILIATION_FAILURE_THRESHOLD 3 Consecutive reconciliation failures before a run is flagged RECONCILIATION_FAILED
INFERENCE_INGESTION_RECONCILIATION_STALE_MINUTES unset Legacy single threshold, used only as a fallback for the three per-state values
INFERENCE_INGESTION_DEFAULT_RECENT_WINDOW_MINUTES 240 C-FIND lookback window for jobs that do not set recent_window_minutes
STUDY_SERVICE_BASE_URL http://study-service:8600 Execution plane URL
STUDY_SERVICE_INGEST_TOKEN, STUDY_SERVICE_OPERATOR_TOKEN, STUDY_SERVICE_CALLBACK_TOKEN none The three shared bearer tokens
STUDY_SERVICE_DISPATCH_CONCURRENCY none Parallel dispatch fan-out; the only bound on automated ingestion, which is exempt from user quotas
INFERENCE_REQUIRE_PROCESSING_RUN_ID true Strict processing-run correlation; set false only during a controlled compatibility or rollback window

api-pacs: public registration, login protection and quotas

Variable Default Purpose
CLOUDFLARE_SECRET_KEY, CLOUDFLARE_TURNSTILE_BASE_URL none, https://challenges.cloudflare.com/turnstile/v0 Server-side Turnstile verification; required when public registration or adaptive login is enabled
REGISTRATION_RATE_LIMIT_WINDOW_SECONDS 600 Registration throttle window
REGISTRATION_RATE_LIMIT_TENANT_ATTEMPTS, REGISTRATION_RATE_LIMIT_EMAIL_ATTEMPTS, REGISTRATION_RATE_LIMIT_IP_ATTEMPTS 100, 5, 10 Attempts per tenant, per normalized email, per trusted client IP within the window; throttled requests return HTTP 429 REGISTRATION_RATE_LIMITED with Retry-After
REGISTRATION_TRUSTED_PROXY_CIDRS, LOGIN_TRUSTED_PROXY_CIDRS empty Direct-proxy CIDRs allowed to supply X-Real-IP; use the exact pacs-net subnet (docker network inspect pacs-net) behind the bundled nginx. When empty, or when the peer is outside those networks, api-pacs ignores X-Real-IP and uses the socket peer address
POLICY_TERMS_VERSION, POLICY_TERMS_URL, POLICY_TERMS_EFFECTIVE_AT, POLICY_PRIVACY_VERSION, POLICY_PRIVACY_URL, POLICY_PRIVACY_EFFECTIVE_AT 2026-08-15, https://pacsai.co/terms-of-service, 2026-08-15, 2026-08-15, https://pacsai.co/privacy-policy, 2026-08-15 Versioned Terms of Service and Privacy Policy that registration must accept; publish these exact versions at the canonical HTTPS URLs before enabling public registration
POLICY_EXISTING_USER_GRACE_UNTIL empty Optional RFC3339 cutoff before existing users must accept current policies; empty means immediate enforcement
LOGIN_ABUSE_PROTECTION_ENABLED true Redis-backed adaptive login protection; disabling is emergency-only, logs a security warning, and still keeps password verification server-side
LOGIN_FAILURE_WINDOW_SECONDS 600 Fixed login failure window
LOGIN_ACCOUNT_CHALLENGE_FAILURES 3 Account failures before Turnstile is required; the account scope never hard-locks
LOGIN_IP_CHALLENGE_FAILURES / LOGIN_IP_MAX_FAILURES 5 / 30 Client-IP challenge and hard-limit thresholds
LOGIN_TENANT_CHALLENGE_FAILURES / LOGIN_TENANT_MAX_FAILURES 50 / 500 Tenant challenge and hard-limit thresholds
LOGIN_TURNSTILE_ALLOWED_HOSTNAMES none Exact frontend hostnames accepted for the login Turnstile action; production must not include localhost
INFERENCE_USER_QUOTA_WINDOW_SECONDS, INFERENCE_USER_QUOTA_ALLOWANCE 86400, 50 Per tenant and user inference units per window; one direct prediction or one manual reprocess consumes one unit
INFERENCE_USER_MAX_CONCURRENT_EXECUTIONS, INFERENCE_USER_RESERVATION_TTL_SECONDS 2, 7200 Active runs per user and the recovery TTL for a lost callback
INFERENCE_USER_QUOTA_TENANT_OVERRIDES_JSON empty JSON keyed by tenant ID; fields windowSeconds, allowance, maxConcurrentExecutions, reservationTtlSeconds

Quota semantics: input or plan validation failures and dispatch failures are refunded; work accepted by a model stays charged even if the model later fails; manual-run concurrency is held until the run reaches a terminal state, with the reservation TTL acting only as recovery for lost callbacks. Rejections return HTTP 429 with INFERENCE_QUOTA_EXCEEDED or INFERENCE_CONCURRENCY_EXCEEDED; Redis unavailability fails closed with HTTP 503 INFERENCE_QUOTA_UNAVAILABLE. Clients read their allowance at GET /v1/inference/quota. Owners and admins are subject to the same quota unless their tenant has an override.

api-pacs: request and input safety

Variable Default Purpose
API_MAX_REQUEST_BODY_BYTES 16777216 Hard body limit for regular /v1 requests (HTTP 413 REQUEST_BODY_TOO_LARGE)
INFERENCE_PREDICT_MAX_REQUEST_BODY_BYTES 1048576 Stricter JSON limit for direct model predictions
DICOMWEB_MAX_REQUEST_BODY_BYTES, DICOMWEB_READ_TIMEOUT_SECONDS 6442450944, 7200 The only bundled large-upload exception (authenticated DICOMweb); keep consistent with nginx and do not raise the general API limits to accommodate DICOM uploads
INFERENCE_MAX_SERIES_UIDS, INFERENCE_MAX_METADATA_BYTES, INFERENCE_MAX_METADATA_DEPTH, INFERENCE_MAX_METADATA_ENTRIES 256, 65536, 8, 256 Prediction input ceilings, validated before DICOM retrieval; the model dicomUploadMin / dicomUploadMax are authoritative and may be stricter, and an invalid model range fails closed with HTTP 503
ORCHESTRATOR_MAX_REQUEST_BODY_BYTES 8388608 Body limit for orchestrator thread, chat and DICOM-selection JSON
ORCHESTRATOR_MAX_STUDIES_PER_REQUEST, ORCHESTRATOR_MAX_SERIES_UIDS_PER_STUDY 20, 256 Studies per orchestrator request and series UIDs per study
ORCHESTRATOR_MAX_PREVIEW_BASE64_BYTES 2097152 Maximum encoded preview or image string
ORCHESTRATOR_MAX_METADATA_BYTES, ORCHESTRATOR_MAX_METADATA_DEPTH, ORCHESTRATOR_MAX_METADATA_ENTRIES 65536, 8, 256 Orchestrator metadata size and structural limits
ORCHESTRATOR_MAX_MESSAGE_BYTES 32768 Maximum chat message length
HTTP_SERVER_READ_HEADER_TIMEOUT_SECONDS, HTTP_SERVER_READ_TIMEOUT_SECONDS, HTTP_SERVER_IDLE_TIMEOUT_SECONDS 10, 30, 120 Server timeouts; the write timeout stays disabled because the worklist SSE stream is long-lived

After changing nginx limits, rebuild the nginx service. After changing api-pacs limits or timeouts, recreate api-pacs so it receives the new environment.

nginx and Orthanc

File Variables
nginx/.env SERVER_NAME; NGINX_FRONTEND_MAX_BODY_SIZE=1m, NGINX_API_MAX_BODY_SIZE=16m, NGINX_DICOMWEB_MAX_BODY_SIZE=6g (must be positive; zero or malformed values fail startup)
orthanc/.env ORTHANC_DICOM_AET, ORTHANC_DICOM_PORT (no shipped default; 4242 in the development compose)
root .env APP_TIMEZONE

study-service (cardio-agent/study-service/.env)

Variable Default Purpose
HOST, PORT, RELOAD, LOG_LEVEL, LOG_FORMAT 0.0.0.0, 8600, false, info, text Server basics
DATABASE_URL none Postgres URI for the cardio-agent database (5434); the repo default cardio:cardio is dev-only
REDIS_URL none Redis URI with password; integrated mode shares the root Redis on DB 2
ORTHANC_URL, ORTHANC_CHANGES_LIMIT http://orthanc:8042, 100 Orthanc REST and polling page size
ENABLE_ORTHANC_POLLING, ENABLE_GO_CALLBACKS false, true Exactly one must be true. The integrated topology uses callbacks (api-pacs hands off studies); standalone study-service uses polling
ORTHANC_POLL_START_MODE, ORTHANC_POLL_START_TIMESTAMP, ORTHANC_POLL_INTERVAL, ORTHANC_POLL_LOCK_TTL latest Polling-mode settings (latest, backlog, since_timestamp)
GO_CALLBACK_BASE_URL, GO_CALLBACK_URL, GO_CALLBACK_TIMEOUT_SECONDS, GO_CALLBACK_MAX_RETRIES, GO_CALLBACK_TOKEN none, none, 5, 4, none Callback target and auth (mirror of STUDY_SERVICE_CALLBACK_TOKEN)
STUDY_SERVICE_INGEST_TOKEN, STUDY_SERVICE_OPERATOR_TOKEN, STUDY_SERVICE_CALLBACK_TOKEN none Mirrors of the api-pacs values
ALLOW_UNAUTHENTICATED_INGEST, ALLOW_UNAUTHENTICATED_OPERATOR_ROUTES false Dev-only auth bypasses
CELERY_WORKER_CONCURRENCY 2 Worker pool size
PROMETHEUS_MULTIPROC_DIR /tmp/prometheus-multiproc Shared metrics directory so worker counters aggregate
CORS_ORIGINS, SHARED_DOCKER_NETWORK / CARDIO_AGENT_SHARED_NETWORK none, pacs-net CORS and the external network

Compose overrides for common topologies

Compose merges a docker-compose.override.yml placed next to the root compose file.

  • No GPU host: set study-celery-worker to replicas: 0 with a disabled profile (and ollama if enabled), then point REDIS_URL in study-service at the GPU host Redis so a remote worker consumes the queue.
  • Managed PostgreSQL (RDS, Cloud SQL): remove postgresql/docker-compose.yml from the root include: list (and the inline postgres: block if cardio-agent moves too), then override POSTGRES_DB_* on api-pacs and DATABASE_URL on study-service and the worker. Run migrations from a host with the migrate binary.
  • Managed Redis (ElastiCache, Memorystore): remove redis/docker-compose.yml from include: and override REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_IAM_DB on api-pacs and REDIS_URL (DB 2) on study-service and the worker.
  • Air-gapped: on a connected host run docker compose pull and docker save -o pacs-ai-images.tar $(docker compose config --images); on the air-gapped host docker load -i pacs-ai-images.tar and set pull_policy: never for every service. Firebase still needs egress to *.googleapis.com; agree with the customer on a self-hosted IdP or a tunneled allow-list for that endpoint.
PACS AI Logo status Status Terms and Conditions Privacy Policy Privacy Impact Assessment Document (EFVP)

© 2026 HeartWise AI Lab, Montreal Heart Institute. All rights reserved.