Production configuration is not a filled-in development appsettings file. Every value needs an owner, source, change path, and failure policy. Static defaults may live in source, secrets belong in a secret store, dynamic non-secret policy belongs in controlled configuration, and the hosting platform injects infrastructure connections.
How configuration reaches runtime
Configuration ownership
| Class | Examples | Recommended source |
|---|---|---|
| Environment identity | ASPNETCORE_ENVIRONMENT | Deployment declaration |
| Provider and non-secret policy | Persistence:Provider, rate window | Versioned config/AgileConfig |
| Database, Redis, RabbitMQ | Connections and credentials | Secret store or managed injection |
| Signing and vendor credentials | JWT, SCIM, object-store access keys | Secret store/KMS |
| Telemetry destination | OTEL_EXPORTER_OTLP_ENDPOINT | Environment deployment config |
Environment variables use double underscores for hierarchy, such as ConnectionStrings__Default. Never commit a resolved secret in .env, and never distribute AgileConfig’s own authentication secret through AgileConfig.
# ① Show variable names and secret references only; the platform resolves real values at runtime.export ASPNETCORE_ENVIRONMENT=Productionexport Persistence__Provider=SqlSugarexport ConnectionStrings__Default='<secret-store:sql-connection>'export ConnectionStrings__redis='<secret-store:redis-connection>'export RabbitMq__Password='<secret-store:rabbitmq-password>'
# ② Build one Release artifact before deployment; do not compile different binaries in each environment.dotnet build BitzOrcas.Modern.slnx --configuration Release --no-restoreStartup guards
Production startup proves at least:
- Required connections, signing keys, and providers exist with valid shape and strength.
- API and JobHost select identical database, message, cache, and Data Protection semantics.
- Critical ports resolve production implementations rather than
InMemory*,Null*, orUnavailable*. - Optional file, mail, and webhook capabilities fail closed when not ready.
- Readiness reflects required dependencies without echoing connection strings or secrets.
Not every key is dynamically reloadable. Database pool, ORM provider, key-ring location, and middleware composition normally require restart. Features and rate policies may reload, but still need a version, schema validation, audit, and failure fallback.
Rolling-release checks
| Scenario | Required proof |
|---|---|
| Old and new replicas overlap | Schema, message, key-ring, and cache-key compatibility |
| Secret rotation | Overlap window, old-value revocation, rollback, and audit |
| Provider switch | Dual-ORM contracts, data consistency, migration, and rollback |
| Config center outage | Declared fail-fast, fail-closed, or last-known-good behavior |
| JobHost lag | API transaction does not require an offline background process to complete synchronously |
Complete the production security checklist, GA gate, and monitoring and alerting evidence before release.
Actual API and JobHost startup gates
Production and Staging are both production-like. API throws InvalidOperationException when database, RabbitMQ, Redis, OTLP, production file storage, webhook safety, or signed licensing is incomplete; Audit:StoreProvider=None is also rejected.
JobHost requires database, Redis, RabbitMQ, OTLP, audit storage, and signed licensing. The database backs Quartz, Workflow, and Audit, so a “small job subset” does not remove that prerequisite.
# ① Show variable names only; a secret store supplies values and the public-key array.export Licensing__Runtime__Enabled=trueexport Licensing__Runtime__ProductId=BitzOrcas.Modernexport Licensing__Runtime__DeploymentIdentityPath=/var/lib/bitzorcas/deployment-idexport Licensing__Runtime__CachePath=/var/lib/bitzorcas/license-cacheexport Licensing__Runtime__TrustedPublicKeys__0='<secret-store:license-public-key>'
# ② Start the real artifact as Staging; a guard failure must stop promotion.ASPNETCORE_ENVIRONMENT=Staging dotnet BitzOrcas.Api.dllDiagnostics report presence, length, or safe summaries rather than secret values. Passing the guard does not prove connectivity; readiness owns that evidence.
Forwarded-header topology
Behind a reverse proxy, configure KnownProxies/KnownNetworks explicitly. Direct public exposure uses the controlled ForwardedHeaders:DirectExposure mode. A wrong topology corrupts client IP, HTTPS detection, rate limiting, audit attribution, and login Cookie Origin checks (which depend on Host after middleware rewrite).
Test X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port, and WebSocket Upgrade through the real staging proxy. OpenResty/Nginx should set ingress Host and X-Forwarded-Host from $http_host so non-default ports survive. OpenResty → Gateway chains usually need ForwardLimit=2. Never trust every proxy just to make startup pass.
Production documentation boundary
Outside Development, /openapi/v1.json and /scalar/v1 are not mapped by default. If an internal preview needs the surface, set both OpenApi__Enabled=true and OpenApi__RequireAuthentication=true: the first maps routes, while the second requires a formal account to establish an HttpOnly documentation session. The application gate is one defense layer; the entry still needs HTTPS and, according to environment risk, a VPN, IP allowlist, or gateway SSO.
# Non-secret configuration only; the environment inventory owns the public origin.export OpenApi__Enabled=trueexport OpenApi__RequireAuthentication=trueexport OpenApi__Servers__0__Url=https://api-preview.example.comexport OpenApi__Servers__0__Description='Protected preview gateway'export OpenApi__PersistAuthentication=falseWhen interactive documentation is unnecessary in Production, leave it disabled and prove both routes return 404. Shared previews keep Scalar authentication persistence off so debugging tokens do not remain in browser storage; the documentation-access session itself is an HttpOnly cookie and is not embedded in page scripts. Never configure usernames, passwords, API keys, HMAC secrets, or tokens. See OpenAPI and Scalar documentation surface for Server, login-route, and trusted-proxy details.
Cache startup warmup (Cache:Warmup)
Business read paths default to cache-aside and do not require startup warmup. To reduce first-hit latency for dictionaries, translations, settings, and similar areas, enable Cache:Warmup (binds CacheWarmupOptions, off by default).
| Key | Default | Purpose |
|---|---|---|
Cache:Warmup:EnabledOnStartup | false | When true, Host runs one background warmup after start |
Cache:Warmup:AreaKeys | [] | Empty = every SupportsWarmup area; prefer an explicit list |
Cache:Warmup:TenantIds | [] | Required for identity-* tenant areas; empty is only safe for platform-key areas |
Cache:Warmup:StartupMode | FillMissing | Prefer fill-missing on startup; full rebuild via Host API |
Cache:Warmup:MaxDegreeOfParallelism | 2 | Parallel areas |
Cache:Warmup:AreaTimeout | 00:02:00 | Per-area timeout |
# Example: warm platform-key areas only; do not scan every tenant.export Cache__Warmup__EnabledOnStartup=trueexport Cache__Warmup__AreaKeys__0=master-dataexport Cache__Warmup__AreaKeys__1=translationsexport Cache__Warmup__AreaKeys__2=settingsexport Cache__Warmup__AreaKeys__3=deliveryexport Cache__Warmup__StartupMode=FillMissingOperators can also run per-area WARM / REBUILD from the Host console (POST /api/operations/cache/rebuild) without a restart. Details: caching guide and Operations cache governance.
Configuration change workflow
- Assign owner, sensitivity, default, validation, and restart semantics.
- Use the production injection mechanism in Staging without pasting secrets.
- Run startup gates, readiness, diagnostics, and core smoke.
- Keep the shortest necessary overlap during rotation, then revoke old values.
- Retain configuration version and approval evidence, never plaintext.
Completion checklist
- API and JobHost configuration matrices are tested separately;
- Production/Staging cannot fall into Shell, Null, or None implementations;
- License DeploymentId and cache use persistent restricted paths;
- OTLP, proxy, object storage, and webhook policy have environment evidence;
- OpenAPI/Scalar is disabled in production, or is externally protected with authentication persistence off;
- secret rotation, config-center outage, and mixed replicas have rollback paths;
- if
Cache:Warmupis enabled: AreaKeys / TenantIds are bounded, and Staging verified startup latency and database load.
Configuration inventory template
Maintain a value-free inventory per environment: key, host, required/optional, source, secret flag, reload/restart, owner, and last verification. Validate shared API/JobHost keys in both hosts so injection into one does not hide an omission.
For booleans, document missing, false, and true. For providers, list accepted values and casing. For arrays, document index binding. Renames need a compatibility window or migration check.
Release review compares inventory and version without exporting all environment values. Diagnostics should block unknown, missing, or dangerous combinations instead of guessing defaults.
- prove every replica stopped reading a key before removal;
- roll back secret and application versions independently;
- audit every configuration-center change;
- backfill review and rehearsal after an emergency change.