Skip to content
bitzorcas
中EN

Guide

Production configuration and startup guards

Organize non-secret configuration, secrets, provider selection, cache warmup, validation, and rolling-release evidence for API and JobHost.

Last updated

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

Repository defaults

Configuration

Environment / Secret Store

Hosting connection injection

AgileConfig non-secret policy

Startup validation + provider choice

API

JobHost

Operations / Health evidence

Configuration ownership

ClassExamplesRecommended source
Environment identityASPNETCORE_ENVIRONMENTDeployment declaration
Provider and non-secret policyPersistence:Provider, rate windowVersioned config/AgileConfig
Database, Redis, RabbitMQConnections and credentialsSecret store or managed injection
Signing and vendor credentialsJWT, SCIM, object-store access keysSecret store/KMS
Telemetry destinationOTEL_EXPORTER_OTLP_ENDPOINTEnvironment 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.

Terminal window
# ① Show variable names and secret references only; the platform resolves real values at runtime.
export ASPNETCORE_ENVIRONMENT=Production
export Persistence__Provider=SqlSugar
export 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-restore

Startup guards

Production startup proves at least:

  1. Required connections, signing keys, and providers exist with valid shape and strength.
  2. API and JobHost select identical database, message, cache, and Data Protection semantics.
  3. Critical ports resolve production implementations rather than InMemory*, Null*, or Unavailable*.
  4. Optional file, mail, and webhook capabilities fail closed when not ready.
  5. 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

ScenarioRequired proof
Old and new replicas overlapSchema, message, key-ring, and cache-key compatibility
Secret rotationOverlap window, old-value revocation, rollback, and audit
Provider switchDual-ORM contracts, data consistency, migration, and rollback
Config center outageDeclared fail-fast, fail-closed, or last-known-good behavior
JobHost lagAPI 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.

Terminal window
# ① Show variable names only; a secret store supplies values and the public-key array.
export Licensing__Runtime__Enabled=true
export Licensing__Runtime__ProductId=BitzOrcas.Modern
export Licensing__Runtime__DeploymentIdentityPath=/var/lib/bitzorcas/deployment-id
export Licensing__Runtime__CachePath=/var/lib/bitzorcas/license-cache
export 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.dll

Diagnostics 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.

Terminal window
# Non-secret configuration only; the environment inventory owns the public origin.
export OpenApi__Enabled=true
export OpenApi__RequireAuthentication=true
export OpenApi__Servers__0__Url=https://api-preview.example.com
export OpenApi__Servers__0__Description='Protected preview gateway'
export OpenApi__PersistAuthentication=false

When 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).

KeyDefaultPurpose
Cache:Warmup:EnabledOnStartupfalseWhen 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:StartupModeFillMissingPrefer fill-missing on startup; full rebuild via Host API
Cache:Warmup:MaxDegreeOfParallelism2Parallel areas
Cache:Warmup:AreaTimeout00:02:00Per-area timeout
Terminal window
# Example: warm platform-key areas only; do not scan every tenant.
export Cache__Warmup__EnabledOnStartup=true
export Cache__Warmup__AreaKeys__0=master-data
export Cache__Warmup__AreaKeys__1=translations
export Cache__Warmup__AreaKeys__2=settings
export Cache__Warmup__AreaKeys__3=delivery
export Cache__Warmup__StartupMode=FillMissing

Operators 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

  1. Assign owner, sensitivity, default, validation, and restart semantics.
  2. Use the production injection mechanism in Staging without pasting secrets.
  3. Run startup gates, readiness, diagnostics, and core smoke.
  4. Keep the shortest necessary overlap during rotation, then revoke old values.
  5. 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:Warmup is 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.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%