After deploying 15+ microservices, I developed a structured approach that solves these problems through careful YAML organization and dynamic parsing.After deploying 15+ microservices, I developed a structured approach that solves these problems through careful YAML organization and dynamic parsing.

How I Built a Config Framework That Scales Across 15+ Microservices

2025/09/10 15:23
6 min read
For feedback or concerns regarding this content, please contact us at [email protected]

How I built a configuration system that mirrors .NET appsettings structure while enabling team-wide consistency across microservices.


The Configuration Challenge

When managing multiple microservices with Pulumi, you quickly run into configuration sprawl. Each service has different ways of organizing settings, secrets are scattered, and onboarding new team members becomes a documentation nightmare.

After deploying 15+ microservices, I developed a structured approach that solves these problems through careful YAML organization and dynamic parsing.

The Core Design: Mirroring .NET Configuration Structure

YAML Structure That Mirrors appsettings.json

Instead of arbitrary configuration, I designed the YAML to mirror the familiar .NET configuration structure:

# Pulumi.dev.yaml - Mirrors your appsettings.json structure ServiceName:ApiAppSettings:   ExternalServices:     PaymentAPI:       BaseUrl: https://api.payments.com       Timeout: 30       RetryAttempts: 3   Features:     EnableCaching: true     EnableLogging: false   Business:     Currency: USD     MaxFileSize: 10485760  ServiceName:FnAppSettings:   Values:  # Maps directly to local.settings.json "Values" section     ProcessingBatchSize: 10     RetryAttempts: 3   ConnectionStrings:  # Maps to ConnectionStrings section     EventGridEndpoint: https://events.azure.net/api/events 

Common Infrastructure Patterns

Every service shares the same foundational structure:

# These sections are identical across all microservices ServiceName:Tags:   Environment: "Development"   Owner: "DevTeam"    Project: "ServiceName"   ManagedBy: "Pulumi"  ServiceName:PlanSku:   Capacity: 1   Family: B   Name: B1   Size: B1   Tier: Basic  ServiceName:DockerSettings:   DockerRegistryUrl: myregistry.azurecr.io   DockerRegistryUserName: myregistry   DockerApiImageName: service-api   DockerApiImageTag: latest 

Why this matters: New developers see a payment service config and instantly understand the user service config. Same sections, same patterns, same structure.

The Modular Architecture

1. DeploymentConfigs Record – The Configuration Hub

public record DeploymentConfigs {     // Basic infrastructure (same across all services)     public string Location { get; init; }     public string Environment { get; init; }     public Dictionary<string, string> CommonTags { get; init; }     public Dictionary<string, object> PlanSku { get; init; }     public Dictionary<string, string> DockerSettings { get; init; }      // Service-specific app settings (dynamic structure)     public Dictionary<string, object> ApiAppSettings { get; init; }     public Dictionary<string, object> FnAppSettings { get; init; }      // Specialized secret handling     public SecretAccess Secrets { get; init; }      public DeploymentConfigs(Config config)     {         // Standard configs - same parsing for every service         Location = config.Require("Location");         CommonTags = config.RequireObject<Dictionary<string, string>>("Tags");          // Dynamic configs - handle any nested structure         var apiSettingsRaw = config.RequireObject<JsonElement>("ApiAppSettings");         ApiAppSettings = ConfigParser.ConvertJsonElementToDictionary(apiSettingsRaw);          // Specialized secret handling         Secrets = new SecretAccess(config);     } } 

2. SecretAccess Class – Functional Secret Management

Rather than just storing secrets, this class provides functionality:

public class SecretAccess {     private readonly Config _config;      // Direct secret access     public Output<string> SqlPassword => _config.RequireSecret("SqlPassword");     public Output<string> DockerRegistryPassword => _config.RequireSecret("DockerRegistryPassword");      // Functional secret handling - builds connection strings dynamically     public Output<string> BuildSqlConnectionString(string database)     {         var databaseConfig = _config.RequireObject<Dictionary<string, string>>("Database");         var server = databaseConfig["SqlServer"];         var userId = databaseConfig["SqlUserId"];          return SqlPassword.Apply(pwd =>             $"server=tcp:{server};User ID={userId};Password={pwd};database={database}");     }      public Output<string> BuildBlobConnectionString(string accountName, Output<string> accountKey)     {         return accountKey.Apply(key =>             $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={key};EndpointSuffix=core.windows.net");     } } 

Key insight: Secrets aren’t just values – they’re building blocks for dynamic configuration assembly.

3. ConfigParser – Dynamic YAML to Dictionary Conversion

The magic happens in the parser. Pulumi gives us a JsonElement, but we need flexible dictionaries:

public static Dictionary<string, object> ConvertJsonElementToDictionary(JsonElement element) {     var dictionary = new Dictionary<string, object>();      foreach (var property in element.EnumerateObject())     {         switch (property.Value.ValueKind)         {             case JsonValueKind.Object:                 // Recursively handle nested objects                 dictionary[property.Name] = ConvertJsonElementToDictionary(property.Value);                 break;             case JsonValueKind.Array:                 dictionary[property.Name] = ConvertJsonElementToArray(property.Value);                 break;             case JsonValueKind.String:                 dictionary[property.Name] = property.Value.GetString() ?? string.Empty;                 break;             // Handle numbers, booleans, nulls...         }     }     return dictionary; } 

The payoff: Add any nested YAML structure without touching C# code:

# Add this to YAML... ServiceName:ApiAppSettings:   NewFeature:     ComplexNesting:       DeepValue: "works automatically"       AnotherLevel:         EvenDeeper: true   // ...and access it immediately in C# if (apiSettings.TryGetValue("NewFeature", out var newFeatureObj) &&      newFeatureObj is Dictionary<string, object> newFeatureDict) {     // Dynamic access to any depth     ConfigureNewFeature(newFeatureDict); } 

Stack Organization: The Assembly Line

Organized Resource Creation

The main stack follows a clear pattern:

public class ContainerizedStack : Pulumi.Stack {     public ContainerizedStack()     {         var config = new Config("ServiceName");         var deploymentConfigs = new DeploymentConfigs(config);          // 1. Foundation         var resourceGroup = CreateResourceGroup(deploymentConfigs);          // 2. Core Services           var apiAppService = CreateApiAppService(deploymentConfigs, resourceGroup);         var functionApp = CreateFunctionApp(deploymentConfigs, resourceGroup);          // 3. Supporting Services         var signalRService = CreateSignalRService(deploymentConfigs, resourceGroup);         var eventGridTopic = CreateEventGridTopic(deploymentConfigs, resourceGroup);          // 4. Outputs         this.ApiUrl = apiAppService.DefaultHostName.Apply(hostname => $"https://{hostname}");         this.FunctionUrl = functionApp.DefaultHostName.Apply(hostname => $"https://{hostname}");     } } 

Modular App Settings Assembly

Each resource type has its own settings assembly pattern:

private static NameValuePairArgs[] GetApiAppSettings(DeploymentConfigs config, Component appInsights) {     var settings = new List<NameValuePairArgs>     {         // Standard settings (same for every service)         new() { Name = "WEBSITE_RUN_FROM_PACKAGE", Value = "1" },         new() { Name = "APPLICATIONINSIGHTS_CONNECTION_STRING", Value = appInsights.ConnectionString },     };      // Dynamic settings assembly     AddExternalServiceSettings(settings, config);     AddFeatureFlagSettings(settings, config);     AddBusinessSettings(settings, config);      return settings.ToArray(); }  private static void AddExternalServiceSettings(List<NameValuePairArgs> settings, DeploymentConfigs config) {     if (config.ApiAppSettings.TryGetValue("ExternalServices", out var servicesObj) &&          servicesObj is Dictionary<string, object> servicesDict)     {         foreach (var service in servicesDict)         {             if (service.Value is Dictionary<string, object> serviceConfig)             {                 foreach (var setting in serviceConfig)                 {                     // Dynamic setting name: ExternalServices__PaymentAPI__BaseUrl                     var settingName = $"ExternalServices__{service.Key}__{setting.Key}";                     settings.Add(new() { Name = settingName, Value = setting.Value?.ToString() ?? "" });                 }             }         }     } } 

Real-World Benefits

Configuration Consistency

# Payment Service PaymentService:ApiAppSettings:   ExternalServices:     BankAPI: { BaseUrl: "...", Timeout: 30 }  # User Service   UserService:ApiAppSettings:   ExternalServices:     AuthAPI: { BaseUrl: "...", Timeout: 30 }  # Same structure, different content 

Zero Code Changes for New Config

# Add this to any service YAML ServiceName:ApiAppSettings:   Monitoring:     EnableDetailedLogging: true     LogLevel: "Information"     CustomMetrics:       TrackUserActions: true       TrackPerformance: false 

The parser handles it automatically. No C# compilation required.

Functional Secret Management

// Instead of storing complete connection strings in config: connectionStrings.Add(new ConnStringInfoArgs {     Name = "DefaultConnection",     ConnectionString = secrets.BuildSqlConnectionString("MyDatabase"),  // Built at runtime     Type = ConnectionStringType.SQLAzure }); 

Implementation in Action

Service Creation Workflow

  1. Copy template → Update service name in files
  2. Customize YAML → Add service-specific sections
  3. Run secrets script → Set encrypted values
  4. Deploypulumi up

Team Consistency Results

  • New developer onboarding: 2 hours instead of 2 days
  • Configuration reviews: Instant pattern recognition
  • Debugging: Same structure across all services
  • Infrastructure updates: Change YAML, redeploy

The Technical Foundation

This approach combines:

  • Structured YAML that mirrors familiar .NET patterns
  • Dynamic parsing that adapts to any configuration structure
  • Functional secret management that builds configurations at runtime
  • Modular stack organization that separates concerns cleanly

The result is infrastructure code that scales with your team rather than fighting against it.

Key Implementation Files

├── Pulumi.dev.yaml          # Structured configuration (mirrors appsettings.json) ├── DeploymentConfigs.cs     # Configuration hub and parser integration ├── SecretAccess.cs          # Functional secret management   ├── ConfigParser.cs          # Dynamic YAML→Dictionary conversion ├── ContainerizedStack.cs    # Organized resource assembly └── add-secrets.ps1         # Standardized secret setup 

Conclusion

Configuration management in infrastructure code doesn’t have to be chaotic. By mirroring familiar .NET patterns, parsing configurations dynamically, and organizing functionality modularly, you can build systems that grow with your team instead of slowing them down.

The patterns shown here have been battle-tested across 15+ production microservices. They work because they solve real team problems with thoughtful technical design.


Repository: pulumi-azure-infrastructure-template

The code speaks for itself. The patterns scale with your team.

\

Disclaimer: The articles reposted on this site are sourced from public platforms and are provided for informational purposes only. They do not necessarily reflect the views of MEXC. All rights remain with the original authors. If you believe any content infringes on third-party rights, please contact [email protected] for removal. MEXC makes no guarantees regarding the accuracy, completeness, or timeliness of the content and is not responsible for any actions taken based on the information provided. The content does not constitute financial, legal, or other professional advice, nor should it be considered a recommendation or endorsement by MEXC.
Tags:

You May Also Like

BullZilla, Shiba Inu, and Goatseus Maximus Take the Spotlight

BullZilla, Shiba Inu, and Goatseus Maximus Take the Spotlight

The post BullZilla, Shiba Inu, and Goatseus Maximus Take the Spotlight appeared on BitcoinEthereumNews.com. Crypto News 17 September 2025 | 20:15 Discover why BullZilla, Shiba Inu, and Goatseus Maximus rank among the best meme coin presales in September 2025. September 2025 has reignited interest in meme coins. While traditional altcoins focus on fundamentals, meme coins thrive on energy, community, and clever narratives. Among the best meme coin presales in September 2025, three stand out for their momentum and market impact: Bull Zilla, Shiba Inu, and Goatseus Maximus. Each offers a unique route for traders and students of finance alike, blending community-driven hype with structured tokenomics. BullZilla continues to command headlines with its presale math and massive ROI potential. Shiba Inu, the veteran of meme mania, still finds ways to reinvent itself. Goatseus Maximus, the fresh arrival, builds on humor and meme storytelling while aiming for short-term gains. Together, they define what meme coin culture looks like heading into Q4 2025. BullZilla: Presale Math Meets Meme Culture BullZilla is not just another viral project. It has crafted a presale model with baked-in returns that investors can map out before listings. The token’s early stages already demonstrate what makes it one of the best meme coin presales in September 2025. BullZilla ROI Table Stage Price ($) ROI Until Listing ($0.00527) $1,000 Investment (Tokens) Value at Listing ($) 3B 0.00006574 7918.57% 15.21M 80,185.73 3C 0.00007241 7169.38% 13.80M 72,703.40 Early Joiners 0.000503 1043.30% 1.99M 20,783.70 This table reflects how even small contributions multiply once BullZilla lists at its projected $0.00527. Unlike meme tokens that rely solely on narrative, BullZilla ($BZIL) merges narrative with math. For anyone who missed Shiba Inu or Dogecoin’s breakout, this structure makes it easy to calculate possible gains. Beyond ROI, the presale’s branding of “Whale Signal Detected” during stage 3rd builds psychological urgency. It cleverly ties meme energy with professional-grade tokenomics. For these reasons,…
Share
BitcoinEthereumNews2025/09/18 03:20
Zoom (ZM) Stock Slides as Investors Fear Anthropic and OpenAI AI Agents

Zoom (ZM) Stock Slides as Investors Fear Anthropic and OpenAI AI Agents

TLDR Zoom (ZM) closed down 5.7% at $79.24, underperforming the S&P 500 which fell just 0.11% The drop was driven by investor fears that AI agents from Anthropic
Share
Coincentral2026/04/11 20:07
WordPress Development Best Practices: Tips for Building High-Performance Websites

WordPress Development Best Practices: Tips for Building High-Performance Websites

Learn WordPress development best practices to build fast, secure, and scalable websites. Discover expert tips, hosting strategies, and optimization techniques.
Share
Techbullion2026/04/11 19:51

USD1 Genesis: 0 Fees + 12% APR

USD1 Genesis: 0 Fees + 12% APRUSD1 Genesis: 0 Fees + 12% APR

New users: stake for up to 600% APR. Limited time!