Settings and Secrets¶
This guide explains how to manage application settings and secrets in the Developer Platform.
π Overview¶
The platform uses a two-tier approach for configuration management:
- Settings: Non-sensitive configuration values. ASP.NET / API applications define these via
appsettings.{env}.json; frontend/static web applications usesettings.yml, wired directly into App Service environment variables by Terraform (no Azure App Configuration involved) β see Settings Management βοΈ - Secrets: Sensitive values stored in Azure DevOps Libraries and deployed to Azure Key Vault π
βοΈ Settings Management¶
This section covers two different app types with two different mechanisms β pick the one that matches what you're deploying:
- ASP.NET / API applications (e.g. generated from
saif-feature-api, or any project with a real ASP.NET Core runtime): useappsettings.{env}.jsonβ seeappsettings.{env}.jsonbelow. - Frontend / static web applications (e.g. generated from
saif-feature-front-end-reactorsaif-feature-web-standaloneβ static, Nginx-served SPAs with no .NET runtime): useinfra/web/settings.ymlβ see Frontend / Static Web Applications below. This is not deprecated for this app type; it's the only mechanism available, since there's no ASP.NET Core process to loadappsettings.json.
For ASP.NET / API applications, define non-sensitive settings in appsettings.{env}.json in the app project being deployed (not the AppHost). This is the standard ASP.NET Core configuration pattern and requires no separate pipeline step.
settings.yml is deprecated for ASP.NET / API applications
Some older API projects push settings via settings.yml (infra/api/) to Azure App Configuration. For ASP.NET / API applications, this mechanism still runs but is deprecated. Azure App Configuration can technically refresh registered settings at runtime (via ConfigureRefresh(...).RegisterAll() and UseAzureDefaults() in SAIF.Platform.Azure), but the platform doesn't rely on or encourage changing settings at runtime, so that capability isn't a reason to keep using it β it just adds a pipeline step and an extra config source over appsettings.{env}.json. Don't use it for new API settings; migrate existing settings.yml values to appsettings.{env}.json when you touch a project.
This deprecation does not apply to frontend/static web applications β infra/web/settings.yml remains the correct, current mechanism for those. See Frontend / Static Web Applications.
appsettings.{env}.json¶
Define settings directly in the app project being deployed (the one that gets containerized), not in the AppHost. This follows standard ASP.NET Core layered configuration:
appsettings.jsonβ default values for all environmentsappsettings.Development.jsonβ local development overrides (already present in generated templates)appsettings.{env}.jsonβ per-environment overrides for deployed environments
ASP.NET Core selects the environment-specific file using ASPNETCORE_ENVIRONMENT, which the platform's Terraform sets to the environment's lowercase short name (test, qa, uat, prod β see environment_short_name in saif-resources/modules/environment/local.tf). Because deployed containers run on Linux (case-sensitive filesystem), name the files to match exactly:
<project-root>/
βββ src/
β βββ SAIF.App1/
β βββ appsettings.json # defaults
β βββ appsettings.Development.json # local dev
β βββ appsettings.test.json # Test environment
β βββ appsettings.qa.json # QA environment
β βββ appsettings.uat.json # UAT environment
β βββ appsettings.prod.json # Production
No pipeline configuration or App Configuration setup is required β the values ship inside the container image and ASP.NET Core loads the right file at startup based on the deployed environment.
ποΈ Legacy: settings.yml + Azure App Configuration (deprecated for API applications)¶
Note
This section applies to ASP.NET / API applications only. If you're looking for frontend/static web app settings, see Frontend / Static Web Applications β the file format looks similar, but frontend apps' settings.yml values go straight into App Service environment variables via Terraform, never through Azure App Configuration, so that mechanism is not deprecated for that app type.
πSettings File Structure¶
Settings are defined in a settings.yml file located in the infra/api/ folder of each project. The file uses a structured format that supports environment-specific overrides.
File Location¶
Settings File Format¶
# This is the settings.yml file used for configuration settings in the application.
# The settings are defined as a list of dictionaries, where each dictionary represents a setting.
# Example of a setting:
# - name: setting1 # The name of the setting
# value: defaultvalue # The default value of the setting
# overrides: # Optional overrides for specific environments
# - env: test # The environment where the override applies
# value: testvalue # The value of the setting for the specified environment
# Overrides allow you to specify different values for settings based on the environment.
# For example, you might have a default value for a setting that applies to all environments,
# but you can override this value for specific environments like 'test', 'qa', 'uat', or 'prod'.
# This is useful for managing environment-specific configurations without duplicating the entire settings structure.
- name: DatabaseConnectionTimeout
value: '30'
overrides:
- env: test
value: '10'
- env: prod
value: '60'
- name: ApiBaseUrl
value: 'https://api.dev.example.com'
overrides:
- env: test
value: 'https://api.test.example.com'
- env: qa
value: 'https://api.qa.example.com'
- env: uat
value: 'https://api.uat.example.com'
- env: prod
value: 'https://api.prod.example.com'
- name: LogLevel
value: 'Debug'
overrides:
- env: prod
value: 'Information'
Environment-Specific Overrides¶
The override system allows you to:
- Define a default value that applies to all environments
- Override specific values for targeted environments (
test,qa,uat,prod) - Avoid duplicating configuration across environments
π Settings Deployment Process (settings.yml path, API applications)¶
- Development: Define settings in
settings.ymlin your project - Pipeline Processing: The deployment pipeline reads the settings file
- Environment Resolution: The pipeline applies environment-specific overrides
- Azure App Configuration: Final settings are pushed to Azure App Configuration
- Application Access: Applications retrieve settings from App Configuration
π Frontend / Static Web Applications: settings.yml¶
Frontend/static web applications (e.g. generated from saif-feature-front-end-react or saif-feature-web-standalone) are static, Nginx-served SPAs with no ASP.NET Core runtime β they cannot load appsettings.json. For these apps, settings.yml is the correct, current mechanism, not a deprecated legacy path.
Unlike the API path above, frontend settings.yml values never pass through Azure App Configuration. The app's own Terraform (web.generated.tf) parses infra/web/settings.yml directly and wires the resolved values straight into App Service application settings (environment variables) β there's no azurerm_app_configuration_key resource in the frontend path at all.
File Location¶
The file format is the same as the API settings.yml format above, but two things differ: the name: value must be prefixed with APP_ (e.g. APP_API_BASE_URL) β see below for why β and environment-override matching is narrower (see Environment-Specific Overrides below).
- name: APP_API_BASE_URL
value: 'https://api.dev.example.com'
overrides:
- env: test
value: 'https://api.test.example.com'
- env: prod
value: 'https://api.prod.example.com'
Environment-Specific Overrides¶
Frontend override matching is narrower than the API path above: it compares each override's env (case-insensitive) against a single name β environment_short_name when the app defines one, otherwise environment β not both. The API path matches against either name. For example, with environment = "Production" and environment_short_name = "prod", an override written as env: Production matches on the API side but is silently ignored on the frontend side (the frontend only matches env: prod here). Always write frontend overrides using the short environment name (test, qa, uat, prod) to avoid this mismatch.
How settings reach the running app¶
- Development: Define settings in
infra/web/settings.ymlin your project, using anAPP_-prefixedname:(e.g.APP_API_BASE_URL) - Terraform Apply: The app's Terraform (
web.generated.tf) parsessettings.ymland resolves environment-specific overrides at plan/apply time, then wires the resulting values in directly as App Service application settings (environment variables) on the container β no Azure App Configuration involved - Container Startup: An entrypoint script (
env.sh, run via/docker-entrypoint.d/) reads only the environment variables whose names start withAPP_(env | grep -E '^APP_') and runs asedsubstitution of each variable name against the static build output before Nginx starts serving it β a setting without theAPP_prefix is present in the container's environment but is silently never substituted - Application Access: The static assets are served with the environment-specific values already baked in β there is no runtime configuration reload
Because step 3 does a sed-based text substitution rather than a templating pass, the frontend source must contain the literal placeholder text matching the env var name (e.g. APP_API_BASE_URL, not {{ApiBaseUrl}} or similar) wherever the value should be injected, so the sed replacement in env.sh can find it in the built output.
β οΈ This substitution is not a safe, exact find/replace of arbitrary text: env.sh runs sed -i "s|${key}|${value}|g", inserting the value unescaped into both the sed command line and its replacement text. Avoid |, &, and backslash-digit sequences (e.g. \1) in frontend settings.yml values:
- A literal
|in the value breaks thesedexpression (it's the delimiter), causingenv.shto fail rather than substitute - An unescaped
&in the value issed's "insert the matched text" token, so it gets replaced with the matchedAPP_...key instead of your intended text β this silently corrupts the substituted value (e.g. a URL query string like?a=1&b=2) - A backslash can alter or invalidate the
sedexpression beyond just backreferences (e.g.\1-style sequences, or a trailing backslash producing a malformed command)
These are the most common failure cases, not an exhaustive list of safe characters β env.sh also doesn't escape values for the destination file's format (JavaScript, JSON, HTML, CSS), so a value containing a quote, newline, or other syntax-sensitive character can produce invalid output even without using |, &, or \. Prefer simple values (URLs without query-string &, GUIDs, hostnames, plain strings) and verify the substituted output for anything less predictable.
Unlike the ASP.NET path above, this substitution happens once at container startup with no request-driven refresh at all, so there's no equivalent capability to weigh against appsettings.{env}.json β for frontend apps, settings.yml is simply the only available mechanism, so it stays current regardless.
π Secrets Management¶
π Azure DevOps Library Setup¶
Secrets are managed through Azure DevOps Variable Groups (Libraries) with the following structure:
Create the Variable Group¶
A variable group is not created for you β create it once per project before your pipeline needs secrets:
- In your Azure DevOps project, go to Pipelines β Library
- Select + Variable group
- Set Variable group name to match the
SecretsVariableGroupNameyou configure in your AppHost (WithPipelineDefaults); see Aspire Publish. The name is otherwise up to you; the project ID is a common, collision-free default. - Add your secrets (see Secret Configuration Requirements), marking each as Secret
- Save, then grant pipeline access to the group if prompted (Pipeline permissions β allow your project's pipelines)
Library Naming Convention¶
- Library Name: Must exactly match the
SecretsVariableGroupNamevalue configured in your AppHost β the project ID is recommended if you have no other convention to follow - Example: If your project ID is
it-api-exp-appname, setSecretsVariableGroupName = "it-api-exp-appname"and name the libraryit-api-exp-appname
Pipeline Configuration¶
To pass the Azure DevOps Library to your pipeline templates, add the secretsVariableGroupName parameter to both your main pipeline and PR pipeline:
extends:
template: azure-dotnet-api-v3.yml@templates
parameters:
applicationName: ${{ variables.ApplicationName }}
projectId: ${{ variables.ProjectId }}
openAPIFileName: ${{ variables.OpenAPIFileName }}
dotNetVersion: '10.x'
secretsVariableGroupName: 'ProjectId1' # π Azure DevOps Library name
Key Points:
- The
secretsVariableGroupNameparameter tells the pipeline template which Azure DevOps Library contains your secrets - Add this parameter to both pipelines: Configure it in your main deployment pipeline (
azure-pipelines-api.yml) and your PR validation pipeline (azure-pipelines-api-pr.yml) - It is recommended that the value matches your Project ID and the variable group name defined in the
variablessection of your pipeline - Example: If your project ID is
it-api-exp-appname, usesecretsVariableGroupName: 'it-api-exp-appname'
Secret Naming Convention¶
Where:
- ENV: Environment prefix (
[GLOBAL],[TEST],[QA],[UAT],[PROD]) - settingname: The name of the secret setting
Environment Prefix Options:
[GLOBAL]: Applied to all environments by default (can be overridden)[TEST]: Test environment specific[QA]: QA environment specific[UAT]: UAT environment specific[PROD]: Production environment specific
Override Behavior: Environment-specific secrets take precedence over global secrets. For example:
[GLOBAL]DatabasePasswordapplies to all environments[PROD]DatabasePasswordoverrides the global setting for production only- Other environments (TEST, QA, UAT) continue using the global value
Examples¶
# Global secrets (applied to all environments)
[GLOBAL]DatabasePassword
[GLOBAL]ApiKey
[GLOBAL]ServiceUrl
# Environment-specific secrets (override global if present)
[TEST]DatabasePassword # Overrides global for TEST
[QA]DatabasePassword # Overrides global for QA
[UAT]DatabasePassword # Overrides global for UAT
[PROD]DatabasePassword # Overrides global for PROD
# Mixed example - ApiKey uses global, DatabasePassword has env-specific overrides
[GLOBAL]ApiKey # Used by all environments
[GLOBAL]DatabasePassword # Used by QA and UAT
[TEST]DatabasePassword # Overrides global for TEST
[PROD]DatabasePassword # Overrides global for PROD
Secret Configuration Requirements¶
For each secret in the Azure DevOps Library:
- Variable Type: Must be set to
Secret(notVariable) - Environment Prefix: Use uppercase environment names (
[GLOBAL],[TEST],[QA],[UAT],[PROD]) - Consistent Naming: Use the same base name across all environments
- Override Hierarchy: Environment-specific secrets override global secrets for that environment
Secret Resolution Order:
- Check for environment-specific secret (e.g.,
[PROD]DatabasePassword) - If not found, use global secret (e.g.,
[GLOBAL]DatabasePassword) - If neither exists, the secret is not available
π Secrets Deployment Process¶
- Azure DevOps Library: Define secrets with proper naming convention
- Variable Type: Ensure all secrets are marked as
Secrettype - Pipeline Detection: Deployment pipeline automatically detects secrets
- Key Vault Storage: Secrets are securely stored in Azure Key Vault
- App Configuration Reference: App Configuration receives references to Key Vault secrets
- Application Access: Applications access secrets through App Configuration with Key Vault integration
β¨ Best Practices¶
βοΈ Settings Best Practices¶
- Use
appsettings.{env}.jsonfor ASP.NET / API apps:settings.yml+ App Configuration is deprecated for this app type β don't use it for new API settings. (Frontend/static web apps are a different case β see Frontend / Static Web Applications, wheresettings.ymlis still current and doesn't use App Configuration at all.) - Use Descriptive Names: Choose clear, self-explanatory setting names
- Default Values: Always provide sensible default values in
appsettings.json - Environment Consistency: Maintain consistent setting names across environments
- Match environment file casing: Name
appsettings.{env}.jsonfiles with the lowercase short name (test,qa,uat,prod) to matchASPNETCORE_ENVIRONMENTon Linux containers - Validation: Test settings in lower environments before production
π Secrets Best Practices¶
- Minimal Exposure: Only store truly sensitive data as secrets
- Rotation: Regularly rotate secrets, especially API keys and passwords
- Access Control: Limit access to Azure DevOps Libraries containing secrets
- Audit Trail: Monitor access to secrets in Azure Key Vault
- Naming Convention: Strictly follow the
[ENV]<settingname>format
π‘οΈ Security Considerations¶
- Secrets Never in Code: Never commit secrets to source control
- Environment Separation: Ensure complete separation between environment secrets
- Least Privilege: Grant minimum necessary permissions for accessing secrets
- Regular Review: Periodically review and cleanup unused secrets
- Monitoring: Enable monitoring and alerting for secret access
π Troubleshooting¶
β οΈ Common Issues¶
Settings Not Appearing in App Configuration (settings.yml path, API applications)¶
- β
Verify
settings.ymlexists ininfra/api/folder - β Check YAML syntax is valid
- β Ensure pipeline has permissions to App Configuration
- β Confirm environment name matches expected values
Frontend Settings Not Applying (settings.yml path, frontend/static web applications)¶
- β
Verify
settings.ymlexists ininfra/web/folder and YAML syntax is valid - β
Confirm the setting's
name:is prefixed withAPP_(e.g.APP_API_BASE_URL) βenv.shonly picks up environment variables matching^APP_, so an unprefixed name is silently never substituted - β Confirm environment name in overrides matches expected values
- β Check the App Service application settings (environment variables) in the Azure portal to confirm Terraform wired the value through β this path doesn't use Azure App Configuration, so App Config checks don't apply here
- β
Verify
env.shran during container startup and that the static build output contains the literalAPP_...placeholder string matching the environment variable name - β
If the substituted value looks corrupted or
env.shfailed outright, check whether the setting's value contains|, an unescaped&, a backslash, or characters that need escaping in the destination file's format (quotes, newlines) βenv.shpasses the value unescaped intosed -i "s|${key}|${value}|g"with no destination-format escaping, so these can break the substitution, corrupt it, or produce invalid output
appsettings.{env}.json values not applying in a deployed environment¶
- β
Confirm the file name matches
ASPNETCORE_ENVIRONMENTexactly, including case (Linux containers are case-sensitive) β e.g.appsettings.test.json, notappsettings.Test.json - β Verify the file is included in the build output / container image
- β Check for a higher-precedence source (environment variables, App Configuration) overriding the value
Secrets Not Accessible¶
- β
Verify Azure DevOps Library name matches what is specified in
secretsVariableGroupName - β
Check secret naming follows
[ENV]<settingname>format - β
Ensure variable type is set to
Secret - β Confirm pipeline has access to the variable group
- β Verify Key Vault permissions are correctly configured
Environment-Specific Values Not Working¶
- β Check environment name spelling in overrides
- β Verify deployment pipeline is using correct environment parameter
- β Ensure override structure follows the correct YAML format
π¬ Getting Help¶
If you encounter issues with settings or secrets configuration:
- Check Pipeline Logs: Review deployment pipeline output for errors
- Validate Configuration: Use YAML validation tools for settings files
- Verify Permissions: Ensure proper access to Azure resources
- Contact Platform Team: Reach out for assistance with complex configurations
π Example Project Structure¶
The following shows an ASP.NET / API project structure; frontend/static web app projects instead have infra/web/settings.yml and no appsettings.*.json (see Frontend / Static Web Applications):
AppnameApplication/
βββ src/
β βββ SAIF.App1/
β βββ appsettings.json # Default settings (all environments)
β βββ appsettings.Development.json
β βββ appsettings.test.json
β βββ appsettings.qa.json
β βββ appsettings.uat.json
β βββ appsettings.prod.json
βββ infra/
β βββ app/
β β βββ settings.yml # Legacy, deprecated for API apps β only needed for the App Configuration alternative
β βββ [infrastructure code]
βββ azure-pipelines.yml # Pipeline configuration
Azure DevOps Library: it-api-exp-appname (matching project ID)
# Global secrets (used by all environments unless overridden)
[GLOBAL]ApiKey = [secret]
[GLOBAL]ServiceUrl = [secret]
# Environment-specific secrets (override global when present)
[TEST]DatabasePassword = [secret]
[QA]DatabasePassword = [secret]
[UAT]DatabasePassword = [secret]
[PROD]DatabasePassword = [secret]
# Mixed example: EmailApiKey uses global for all environments
[GLOBAL]EmailApiKey = [secret]
This approach ensures secure, manageable, and environment-appropriate configuration for all applications in the Developer Platform.
π» Using These Settings in Applications¶
π΅ .NET Applications¶
For detailed information on how to consume these settings in your .NET applications, see the official Microsoft documentation: Configuration in .NET
This guide covers:
- How to read configuration values in your application
- Working with Azure App Configuration provider
- Integrating with dependency injection
- Best practices for configuration management in .NET
π Frontend / Static Web Applications¶
Frontend/static web apps don't read configuration at runtime the way ASP.NET apps do β there's no process to inject values into. Instead, values from infra/web/settings.yml are wired directly into the App Service as environment variables by the app's Terraform (no Azure App Configuration involved), and then consumed at container startup by an entrypoint script (env.sh) that substitutes them into the static build output before Nginx starts serving it. This only works for settings whose name: is prefixed with APP_ (e.g. APP_API_BASE_URL), and the frontend source must contain that same string as a literal placeholder β env.sh runs a sed substitution, not templated substitution. This sed substitution is not a safe find/replace for arbitrary text: values containing |, an unescaped &, or a backslash can break or corrupt it, and values aren't escaped for the destination file's format either. Avoid those characters in frontend settings.yml values and verify the substituted output. See Frontend / Static Web Applications above for the full flow, including the specific unsafe characters.
π Related Documentation¶
- Aspire Config Example - Working example of publish-time configuration generation with Aspire