Skip to main content

Overview

TLQ FHIR Server (TLQ FHIR) loads configuration from config.yaml and then applies environment variable overrides. This lets you keep non-sensitive defaults in a file while keeping secrets (passwords, tokens, connection strings) in a .env file or your deployment’s secret manager.
If you installed via the Quickstart script, you’ll have a tlq-fhir-server/ folder containing compose.yaml and config.yaml. This page documents that config.yaml and how to use .env with Docker Compose.

Configuration Files

config.yaml (main settings)

  • Location (Docker bundle): next to compose.yaml
  • Location (source checkout): server/config.yaml (optional)
  • Purpose: non-secret server configuration (features, limits, performance knobs)

.env (secrets + deployment-specific values)

Docker Compose uses a .env file in the same directory as compose.yaml for variable substitution, and the release bundle’s compose.yaml also loads ./.env into the server containers via env_file. Use it for:
  • Database credentials (POSTGRES_PASSWORD, etc.)
  • Admin UI password (FHIR__UI__PASSWORD)
  • Any FHIR__... overrides you don’t want in config.yaml
Don’t commit .env files. Treat them as secrets.

Load Order and Overrides

Configuration precedence (last wins):
  1. Built-in defaults
  2. config.yaml (if present)
  3. Environment variables with the FHIR__ prefix (e.g. FHIR__SERVER__PORT)
  4. Convenience fallback: DATABASE_URL is used as database.url if FHIR__DATABASE__URL is not set

Environment Variable Mapping

Nested config keys map to environment variables by joining segments with double underscores (__). Examples:
# Network / HTTP
FHIR__SERVER__HOST=0.0.0.0
FHIR__SERVER__PORT=8080

# Database
FHIR__DATABASE__URL=postgresql://fhir:supersecret@db:5432/fhir
FHIR__DATABASE__POOL_MAX_SIZE=20

# Admin UI auth
FHIR__UI__PASSWORD=change-me

# Logging
FHIR__LOGGING__LEVEL=debug

Using .env with Docker Compose

Create a .env next to compose.yaml:
# Postgres (used by the db container)
POSTGRES_USER=fhir
POSTGRES_PASSWORD=change-me
POSTGRES_DB=fhir

# Optional: bind services to all interfaces (careful in production)
# BIND_ADDRESS=0.0.0.0

# FHIR server containers
RUST_LOG=info

# Secrets: keep these out of config.yaml
FHIR__UI__PASSWORD=change-me
Then restart the stack:
docker compose up -d
In the provided compose.yaml, the FHIR server containers get a DATABASE_URL derived from the Postgres variables above. This overrides the database.url value in config.yaml automatically.

config.yaml Reference

The release bundle ships with an example config.yaml. If you’re browsing the source repository, the same template lives at tlq-fhir-server/dist/config.yaml. You typically only need to change a few values.

server

Network and HTTP settings.
server:
  host: "0.0.0.0"                    # Bind address for the FHIR API (env: FHIR__SERVER__HOST)
  port: 8080                         # Port for the FHIR API (env: FHIR__SERVER__PORT)
  cors_origins:                      # List of allowed origins (empty = no CORS headers)
    - "http://localhost:3000"
  max_request_body_size: 10485760    # Max request size in bytes (10 MB)
  max_response_body_size: 52428800   # Max response size in bytes (50 MB)

database

PostgreSQL connection and performance tuning.
database:
  url: "postgresql://user:pass@db:5432/fhir"  # Connection string (env: FHIR__DATABASE__URL or DATABASE_URL)
  test_database_url: null                      # Optional test database connection string

  # API server connection pool
  pool_min_size: 2
  pool_max_size: 20
  pool_timeout_seconds: 60

  # Worker connection pool
  worker_pool_min_size: 1
  worker_pool_max_size: 5
  worker_pool_timeout_seconds: 60

  # Query and lock timeouts
  statement_timeout_seconds: 300     # Max query execution time
  lock_timeout_seconds: 30           # Max time waiting for locks

  # Indexing performance
  indexing_batch_size: 50            # Resources per batch
  indexing_bulk_threshold: 200       # Min resources to use bulk indexing

fhir

FHIR behavior, interactions, search, packages, and capabilities.
fhir:
  version: "R4"                      # R4, R4B, or R5
  default_format: "json"             # json or xml
  default_prefer_return: "representation"  # minimal, representation, or operationoutcome
  allow_update_create: true          # Allow PUT to create resources
  hard_delete: false                 # Permanently delete resources (true) or soft-delete (false)

  # Enable/disable FHIR interactions
  interactions:
    system:
      capabilities: true             # GET /metadata
      search: true                   # GET /
      history: true                  # GET /_history
      delete: true                   # POST / with delete bundle entries
      batch: true                    # POST / with batch bundles
      transaction: true              # POST / with transaction bundles
      history_bundle: true           # POST / with history bundles

    type_level:
      create: true                   # POST /Patient
      conditional_create: true       # POST /Patient with If-None-Exist
      search: true                   # GET /Patient
      history: true                  # GET /Patient/_history
      conditional_update: true       # PUT /Patient with conditional match
      conditional_patch: true        # PATCH /Patient with conditional match
      conditional_delete: true       # DELETE /Patient with conditional match

    instance:
      read: true                     # GET /Patient/123
      vread: true                    # GET /Patient/123/_history/1
      update: true                   # PUT /Patient/123
      patch: true                    # PATCH /Patient/123
      delete: true                   # DELETE /Patient/123
      history: true                  # GET /Patient/123/_history
      delete_history: true           # DELETE /Patient/123/_history
      delete_history_version: true   # DELETE /Patient/123/_history/1

    compartment:
      search: true                   # GET /Patient/123/Observation

    operations:
      system: true                   # POST /$operation
      type_level: true               # POST /Patient/$operation
      instance: true                 # POST /Patient/123/$operation

  # Package installation
  install_internal_packages: true    # Install bundled packages
  internal_packages_dir: null        # Custom path to internal packages
  registry_url: null                 # Custom package registry URL

  # Search behavior
  search:
    enable_text: true                # Enable _text search
    enable_content: true             # Enable _content search
    default_count: 20                # Default page size
    max_count: 1000                  # Max page size
    max_total_results: 10000         # Max total results across pages
    max_include_depth: 3             # Max depth for _include/_revinclude
    max_includes: 10                 # Max number of _include parameters
    search_parameter_active_statuses: ["draft", "active"]  # Which SearchParameter statuses to honor

  # FHIRPath expression evaluation
  fhirpath:
    enable_resolve: true             # Allow resolve() function
    enable_external_http: false      # ⚠️ Allow external HTTP fetches (security risk)
    resolve_cache_size: 100          # LRU cache size for resolved resources
    http_timeout_seconds: 5          # Timeout for external HTTP requests

  # Default packages from registry (e.g., hl7.fhir.r4.core)
  default_packages:
    core:
      install: false                 # Install FHIR core definitions
      install_examples: false
      include_resource_types:
        - "StructureDefinition"
        - "CodeSystem"
        - "ValueSet"
        - "SearchParameter"
        - "CompartmentDefinition"
    extensions:
      install: false                 # Install FHIR extensions pack
      install_examples: false
      include_resource_types: ["StructureDefinition"]
    terminology:
      install: false                 # Install FHIR terminology
      install_examples: false
      include_resource_types: ["ValueSet", "CodeSystem", "ConceptMap"]

  # Additional implementation guides
  packages: []
  # packages:
  #   - name: "hl7.fhir.us.core"
  #     version: "5.0.1"
  #     install_examples: false
  #     include_resource_types: ["Patient", "Observation"]

  # Capability statement metadata (GET /metadata)
  capability_statement:
    id: "tlq-fhir-server-capability-statement"
    name: "tlq-fhir-server-capability-statement"
    title: "TLQ FHIR"
    publisher: "ThalamiQ"
    description: "Capability Statement for **TLQ FHIR**"
    software_name: "tlq-fhir-server"
    software_version: "0.1.0"
    contact_email: null
    supported_resources: []          # Empty = all available resources

workers

Background job processing (e.g., package installation, indexing).
workers:
  enabled: true                      # Enable background workers
  poll_interval_seconds: 5           # Polling interval (in addition to LISTEN/NOTIFY)
  max_concurrent_jobs: 1             # Concurrency limit (keep low to avoid DB contention)
  reconnect_initial_seconds: 1       # Initial reconnect backoff
  reconnect_max_seconds: 30          # Max reconnect backoff
  reconnect_jitter_ratio: 0.2        # Jitter for reconnect backoff

ui

Admin UI settings.
ui:
  enabled: true                      # Enable admin UI
  title: "FHIR Admin"                # Browser tab title
  password: null                     # Admin password (⚠️ use env: FHIR__UI__PASSWORD instead)
  session_secret: null               # Session signing key (recommended: long random value)
  session_ttl_seconds: 43200         # Session lifetime (12 hours)

auth

OIDC authentication settings.
auth:
  enabled: false                     # Enable OIDC authentication
  required: true                     # Require auth for all endpoints except public_paths
  public_paths:                      # Paths that don't require authentication
    - "/health"
    - "/"
    - "/favicon.ico"
    - "/fhir/metadata"
    - "/fhir/metadata/"
    - "/fhir/.well-known/smart-configuration"
  oidc:
    issuer_url: null                 # OIDC issuer URL (required when auth.enabled=true)
    audience: null                   # Expected audience claim (required when auth.enabled=true)
    jwks_url: null                   # JWKS URL (optional, auto-discovered from issuer)
    jwks_cache_ttl_seconds: 300      # JWKS cache TTL
    http_timeout_seconds: 5          # Timeout for OIDC requests

logging

Log verbosity, format, file output, and OpenTelemetry.
logging:
  level: "info"                      # trace, debug, info, warn, error
  json: false                        # JSON log format (true) or human-readable (false)

  # File logging
  file_enabled: false
  file_directory: "./logs"
  file_prefix: "fhir-server"
  file_rotation: "daily"             # daily, hourly, minutely, never

  # Audit logging (create/update/delete operations)
  audit_enabled: true
  audit_directory: "./logs/audit"

  # OpenTelemetry (traces and metrics)
  opentelemetry_enabled: false
  otlp_endpoint: "http://localhost:4317"
  trace_sample_ratio: 1.0            # 0.0 to 1.0 (1.0 = trace everything)
  service_name: "fhir-server"
  deployment_environment: "development"
  service_version: null
  otlp_timeout_seconds: 10

Production Checklist

  • Set FHIR__UI__PASSWORD and restrict UI access at the network level.
  • Set server.cors_origins explicitly for browser-based clients.
  • Keep fhir.fhirpath.enable_external_http=false unless you fully trust callers.
  • Use a secret manager (Kubernetes secrets, Fly secrets, etc.) instead of shipping .env files.