-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
Configuration is built on the standard Microsoft.Extensions.Configuration pipeline. Both the WebApp and the Worker use the same override chain. Later providers win:
appsettings.json typed defaults, checked in
-> .env Docker/default developer stack values
-> .env.local host-local overrides, only outside containers
-> process environment CI/CD, Docker Compose, shell variables
-> Azure Key Vault production secrets, optional
-> HarpyxConfigurationComposer derived composite keys, if still empty
The wiring happens in WebApplicationBuilderExtensions.ConfigureHarpyxHost and HostApplicationBuilderExtensions.ConfigureHarpyxHost. See src/Harpyx.WebApp/Extensions/WebApplicationBuilderExtensions.cs and src/Harpyx.Worker/Extensions/HostApplicationBuilderExtensions.cs.
All layers target the same logical key path as appsettings.json:
| Layer | Separator | Example |
|---|---|---|
appsettings.json |
: |
Database:Host, EntraId:TenantId
|
| Environment variables | __ |
Database__Host, EntraId__TenantId
|
| Azure Key Vault | -- |
Database--Host, EntraId--TenantId
|
There are no ${...} placeholders in appsettings.json. Environment values directly override the same appsettings key through the native __ to : mapping performed by AddEnvironmentVariables(). Azure Key Vault uses the native -- to : mapping.
IConfiguration lookups are case-insensitive, but Harpyx standardizes on PascalCase environment names so they visually mirror the JSON structure.
- Azure Key Vault secret names can contain dashes but not underscores, so the official convention maps
:to--. - POSIX environment variables can contain underscores but not dashes, so .NET maps
:to__.
appsettings.json contains structure and safe typed defaults. Environment-specific addresses and secrets are intentionally blank there and supplied by .env, .env.local, process environment variables, or Key Vault.
This keeps appsettings portable:
"Database": {
"Host": "",
"Port": 0,
"Name": "",
"Username": "",
"Password": "",
"Encrypt": true,
"TrustServerCertificate": true
}The actual values live in environment-shaped files:
Database__Host=sqlserver
Database__Port=1433
Database__Encrypt=trueDatabase:ApplyMigrationsOnStartup is intentionally omitted from appsettings.json. When omitted, the WebApp applies migrations automatically in Development and leaves them disabled in other environments. Production deployments should still set Database__ApplyMigrationsOnStartup=false explicitly and apply migrations out-of-band.
SeedOptions controls bootstrap data that must exist before an administrator can use the UI. Admin users are configured as an array under SeedOptions:AdminUsers:
"SeedOptions": {
"AdminUsers": [
{
"Provider": "EntraId",
"UniqueId": "8f8c9e0d-56af-4fef-aef6-0867b95900a3",
"Email": "azure@ryadel.com"
},
{
"Provider": "Google",
"UniqueId": "google-sub-value",
"Email": "admin@example.com"
}
]
}The environment-variable form uses normal .NET array binding:
SeedOptions__AdminUsers__0__Provider=EntraId
SeedOptions__AdminUsers__0__UniqueId=8f8c9e0d-56af-4fef-aef6-0867b95900a3
SeedOptions__AdminUsers__0__Email=azure@ryadel.com
SeedOptions__AdminUsers__1__Provider=Google
SeedOptions__AdminUsers__1__UniqueId=google-sub-value
SeedOptions__AdminUsers__1__Email=admin@example.comEach admin entry has three fields:
| Field | Purpose |
|---|---|
Provider |
Identity provider family. EntraId maps UniqueId to User.ObjectId; Google maps it to User.SubjectId. |
UniqueId |
Stable provider-specific identifier. For Entra ID this is the object id; for Google this is the sub claim. |
Email |
Human-readable address and fallback lookup key. It is not the primary identity anchor. |
At WebApp startup, InitializeHarpyxDatabaseAndSeedAsync reads SeedOptions:AdminUsers, creates missing users, and ensures configured seed users are active platform admins. The database still stores provider-specific identifiers separately as ObjectId and SubjectId; UniqueId is only the provider-neutral configuration name.
Legacy SeedAdmin:ObjectId / SeedAdmin:Email values are still accepted as an Entra ID admin entry, but new configuration should use SeedOptions:AdminUsers.
Two keys are derived at startup from simpler components by HarpyxConfigurationComposer:
| Composite key | Derived from |
|---|---|
ConnectionStrings:Harpyx |
Database:Host, Database:Port, Database:Name, Database:Username, Database:Password, Database:Encrypt, Database:TrustServerCertificate
|
EntraId:Authority |
EntraId:Instance, EntraId:TenantId
|
HarpyxConfigurationComposer.ComposeDerivedValues runs after environment variables and Key Vault have been added, and writes a composite key only when that key is still empty. Operators can bypass composition by supplying a full value such as ConnectionStrings__Harpyx.
Database:Encrypt defaults to true, so SQL Server connections are encrypted whenever the composed connection string is used. Development stacks can keep Database:TrustServerCertificate=true for the self-signed SQL Server container certificate. Production deployments should use a valid SQL Server TLS certificate and set Database__TrustServerCertificate=false. See Data Encryption.
Copy .env.example to .env. This is the Docker/default developer stack configuration. Docker Compose reads it automatically, so docker compose up works without extra --env-file arguments.
Copy .env.local.example to .env.local when running the WebApp or Worker from the IDE / dotnet run while dependencies run in Docker. .env.local contains only host-local overrides, such as localhost endpoints and published ports:
# .env
COMPOSE_PROJECT_NAME=harpyx
DOTNET_ENVIRONMENT=Development
ASPNETCORE_URLS=http://+:8080
# .env.local
Database__Host=localhost
Database__Port=14330
Minio__Endpoint=localhost:9000
RabbitMQ__HostName=localhost
Redis__ConnectionString=localhost:6379
OpenSearch__Endpoint=https://localhost:9200
MalwareScan__Host=localhostHarpyxEnvironmentFileLoader always loads .env first. When DOTNET_RUNNING_IN_CONTAINER is not true, it then loads .env.local as an optional overlay. Containers never load .env.local.
Internally, HarpyxEnvironmentFileLoader parses those files with DotNetEnv before AddEnvironmentVariables() runs. Values from the files enter the normal .NET environment-variable configuration path, so __ maps to : exactly like process environment variables.
docker-compose.yml reads .env for interpolation and forwards the app-level variables into each service's environment: block. Docker-specific host ports and container knobs are also configured through .env using Docker__... variables:
COMPOSE_PROJECT_NAME=harpyx
DOTNET_ENVIRONMENT=Development
ASPNETCORE_URLS=http://+:8080COMPOSE_PROJECT_NAME fixes the Docker Compose project name, so generated container, network, and volume names stay stable even if the repository folder is renamed. DOTNET_ENVIRONMENT selects the runtime environment for both WebApp and Worker, and ASPNETCORE_URLS binds Kestrel to the WebApp container port.
Docker__WebAppPort=8080
Docker__SqlServerPublicPort=14330
Docker__SqlServerContainerPort=1433Container images sometimes require fixed uppercase variable names, such as MSSQL_SA_PASSWORD or OPENSEARCH_INITIAL_ADMIN_PASSWORD. Those names remain uppercase in docker-compose.yml because they are imposed by upstream images, but their values are still drawn from .env.
To move production secrets to Azure Key Vault, add the provider after AddEnvironmentVariables() and before ComposeDerivedValues:
builder.Configuration.AddAzureKeyVault(
new Uri(builder.Configuration["KeyVault:Uri"]!),
new DefaultAzureCredential());Store secrets using the -- separator:
| Key Vault secret name | Maps to config key |
|---|---|
Database--Password |
Database:Password |
EntraId--ClientSecret |
EntraId:ClientSecret |
Minio--SecretKey |
Minio:SecretKey |
Encryption--MasterKey |
Encryption:MasterKey |
Because appsettings, env files, process variables, and Key Vault all target the same section path, application code is identical in every environment.
| Section | Purpose |
|---|---|
Database |
SQL Server connection components and migration toggle |
EntraId |
Microsoft Entra ID OIDC parameters |
Google |
Google OAuth client id/secret/callback |
SeedOptions |
Bootstrap data, including configured platform admin users |
Minio |
Object storage endpoint and credentials |
RabbitMQ |
Job queue broker |
Redis |
Idempotency/cache connection string |
OpenSearch |
Chunk index host, credentials, index name, and timeouts |
Rag |
Chunking and embedding parameters |
MalwareScan |
ClamAV host/port and fail-closed policy |
UploadSecurity |
Allowed extensions and MIME types |
ReverseProxy |
Forwarded headers configuration |
DataProtection |
ASP.NET Data Protection keys location |
Encryption |
AES-256-GCM master key for user LLM API keys |
Email |
SMTP settings |
OpenTelemetry |
OTLP exporter endpoint |
Serilog |
Structured logging levels |