Skip to content

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 use settings.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): use appsettings.{env}.json β€” see appsettings.{env}.json below.
  • Frontend / static web applications (e.g. generated from saif-feature-front-end-react or saif-feature-web-standalone β€” static, Nginx-served SPAs with no .NET runtime): use infra/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 load appsettings.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 environments
  • appsettings.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

<project-root>/
β”œβ”€β”€ infra/
β”‚   └── app/
β”‚       └── settings.yml

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)

  1. Development: Define settings in settings.yml in your project
  2. Pipeline Processing: The deployment pipeline reads the settings file
  3. Environment Resolution: The pipeline applies environment-specific overrides
  4. Azure App Configuration: Final settings are pushed to Azure App Configuration
  5. 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

<project-root>/
β”œβ”€β”€ infra/
β”‚   └── web/
β”‚       └── settings.yml

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

  1. Development: Define settings in infra/web/settings.yml in your project, using an APP_-prefixed name: (e.g. APP_API_BASE_URL)
  2. Terraform Apply: The app's Terraform (web.generated.tf) parses settings.yml and 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
  3. Container Startup: An entrypoint script (env.sh, run via /docker-entrypoint.d/) reads only the environment variables whose names start with APP_ (env | grep -E '^APP_') and runs a sed substitution of each variable name against the static build output before Nginx starts serving it β€” a setting without the APP_ prefix is present in the container's environment but is silently never substituted
  4. 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 the sed expression (it's the delimiter), causing env.sh to fail rather than substitute
  • An unescaped & in the value is sed's "insert the matched text" token, so it gets replaced with the matched APP_... 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 sed expression 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:

  1. In your Azure DevOps project, go to Pipelines β†’ Library
  2. Select + Variable group
  3. Set Variable group name to match the SecretsVariableGroupName you configure in your AppHost (WithPipelineDefaults); see Aspire Publish. The name is otherwise up to you; the project ID is a common, collision-free default.
  4. Add your secrets (see Secret Configuration Requirements), marking each as Secret
  5. 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 SecretsVariableGroupName value 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, set SecretsVariableGroupName = "it-api-exp-appname" and name the library it-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 secretsVariableGroupName parameter 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 variables section of your pipeline
  • Example: If your project ID is it-api-exp-appname, use secretsVariableGroupName: 'it-api-exp-appname'

Secret Naming Convention

[ENV]<settingname>

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]DatabasePassword applies to all environments
  • [PROD]DatabasePassword overrides 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:

  1. Variable Type: Must be set to Secret (not Variable)
  2. Environment Prefix: Use uppercase environment names ([GLOBAL], [TEST], [QA], [UAT], [PROD])
  3. Consistent Naming: Use the same base name across all environments
  4. Override Hierarchy: Environment-specific secrets override global secrets for that environment

Secret Resolution Order:

  1. Check for environment-specific secret (e.g., [PROD]DatabasePassword)
  2. If not found, use global secret (e.g., [GLOBAL]DatabasePassword)
  3. If neither exists, the secret is not available

πŸš€ Secrets Deployment Process

  1. Azure DevOps Library: Define secrets with proper naming convention
  2. Variable Type: Ensure all secrets are marked as Secret type
  3. Pipeline Detection: Deployment pipeline automatically detects secrets
  4. Key Vault Storage: Secrets are securely stored in Azure Key Vault
  5. App Configuration Reference: App Configuration receives references to Key Vault secrets
  6. Application Access: Applications access secrets through App Configuration with Key Vault integration

✨ Best Practices

βš™οΈ Settings Best Practices

  • Use appsettings.{env}.json for 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, where settings.yml is 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}.json files with the lowercase short name (test, qa, uat, prod) to match ASPNETCORE_ENVIRONMENT on 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

  1. Secrets Never in Code: Never commit secrets to source control
  2. Environment Separation: Ensure complete separation between environment secrets
  3. Least Privilege: Grant minimum necessary permissions for accessing secrets
  4. Regular Review: Periodically review and cleanup unused secrets
  5. Monitoring: Enable monitoring and alerting for secret access

πŸ” Troubleshooting

⚠️ Common Issues

Settings Not Appearing in App Configuration (settings.yml path, API applications)

  • βœ… Verify settings.yml exists in infra/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.yml exists in infra/web/ folder and YAML syntax is valid
  • βœ… Confirm the setting's name: is prefixed with APP_ (e.g. APP_API_BASE_URL) β€” env.sh only 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.sh ran during container startup and that the static build output contains the literal APP_... placeholder string matching the environment variable name
  • βœ… If the substituted value looks corrupted or env.sh failed 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.sh passes the value unescaped into sed -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_ENVIRONMENT exactly, including case (Linux containers are case-sensitive) β€” e.g. appsettings.test.json, not appsettings.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:

  1. Check Pipeline Logs: Review deployment pipeline output for errors
  2. Validate Configuration: Use YAML validation tools for settings files
  3. Verify Permissions: Ensure proper access to Azure resources
  4. 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.