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):
- Built-in defaults
config.yaml (if present)
- Environment variables with the
FHIR__ prefix (e.g. FHIR__SERVER__PORT)
- 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:
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
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.