Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
Kubernetes

Step-by-Step: Configuration Management in Kubernetes with ConfigMap and Secrets

- 19.08.26 - ErcanOPAK

Managing .NET application configuration across environments in Kubernetes? ConfigMap and Secrets are the answer.

Step 1: Create ConfigMaps

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  # Application settings
  appsettings.json: |
    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AppSettings": {
        "AppName": "OrderService",
        "Environment": "Production",
        "Version": "1.2.0"
      },
      "FeatureFlags": {
        "EnableNewCheckout": "true",
        "EnableBetaFeatures": "false"
      }
    }
  
  # Simple key-value pairs
  ASPNETCORE_ENVIRONMENT: "Production"
  ASPNETCORE_URLS: "http://*:80"
  LOG_LEVEL: "Information"
  CACHE_TTL: "300"
  MAX_RETRY_COUNT: "3"
  
  # Connection strings (non-sensitive)
  DatabaseHost: "postgres.production.svc.cluster.local"
  DatabasePort: "5432"
  DatabaseName: "ordersdb"
  RedisHost: "redis.production.svc.cluster.local"
  RedisPort: "6379"

Step 2: Create Secrets

# secret.yaml (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: production
type: Opaque
data:
  # Base64 encoded values
  DatabasePassword: cGFzc3dvcmQxMjM=  # password123
  RedisPassword: cmVkaXNwYXNz        # redispass
  ApiKey: c2VjcmV0LWtleS0xMjM=       # secret-key-123
  JwtSecret: dXByP1htR1ptQkZKT1pIUnJs   # randomsecret

---
# Or use stringData for plain text (converted to base64)
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets-plain
  namespace: production
type: Opaque
stringData:
  DatabasePassword: password123
  RedisPassword: redispass
  ApiKey: secret-key-123
  # This is easier to read and manage

Step 3: Mount ConfigMap as File

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: app
        image: myregistry/order-service:latest
        ports:
        - containerPort: 80
        
        # Mount ConfigMap as file
        volumeMounts:
        - name: app-config-volume
          mountPath: /app/appsettings.Production.json
          subPath: appsettings.json
        - name: app-secrets-volume
          mountPath: /app/secrets.json
          subPath: secrets.json
        
        # Use environment variables
        env:
        - name: ASPNETCORE_ENVIRONMENT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: ASPNETCORE_ENVIRONMENT
        - name: ConnectionStrings__Database
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DatabaseHost
        - name: ConnectionStrings__Redis
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: RedisHost
        
        # Use secrets as environment variables
        - name: DatabasePassword
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: DatabasePassword
        - name: RedisPassword
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: RedisPassword
        - name: JwtSecret
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: JwtSecret
      
      # Volumes
      volumes:
      - name: app-config-volume
        configMap:
          name: app-config
      - name: app-secrets-volume
        secret:
          name: app-secrets

Step 4: Update .NET to Use Configuration

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Load configuration from multiple sources
var configBuilder = new ConfigurationBuilder()
    .SetBasePath(builder.Environment.ContentRootPath)
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile("secrets.json", optional: true)  // Mounted from secrets
    .AddEnvironmentVariables()
    .Build();

builder.Configuration.AddConfiguration(configBuilder);

// Build connection string from ConfigMap values
var dbHost = builder.Configuration["DatabaseHost"];
var dbName = builder.Configuration["DatabaseName"];
var dbPassword = builder.Configuration["DatabasePassword"];

var connectionString = $"Host={dbHost};Database={dbName};Username=appuser;Password={dbPassword}";
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(connectionString));

// Use Redis from ConfigMap
var redisHost = builder.Configuration["RedisHost"];
var redisPassword = builder.Configuration["RedisPassword"];
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = $"{redisHost},password={redisPassword}";
});

// Use Feature Flags
var enableNewCheckout = builder.Configuration["FeatureFlags:EnableNewCheckout"] == "true";
if (enableNewCheckout)
{
    builder.Services.AddScoped<INewCheckoutService, NewCheckoutService>();
}

Step 5: Dynamic Configuration Updates

// Watch ConfigMap changes
public class ConfigMapWatcher : BackgroundService
{
    private readonly IConfiguration _configuration;
    private readonly ILogger<ConfigMapWatcher> _logger;
    private readonly IServiceProvider _services;
    private readonly FileSystemWatcher _watcher;
    
    public ConfigMapWatcher(IConfiguration configuration, ILogger<ConfigMapWatcher> logger, IServiceProvider services)
    {
        _configuration = configuration;
        _logger = logger;
        _services = services;
        
        _watcher = new FileSystemWatcher("/app")
        {
            Filter = "appsettings.*.json",
            EnableRaisingEvents = true,
            NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime
        };
        
        _watcher.Changed += OnConfigChanged;
        _watcher.Created += OnConfigChanged;
    }
    
    private async void OnConfigChanged(object sender, FileSystemEventArgs e)
    {
        _logger.LogInformation("Configuration file {FileName} changed, reloading...", e.Name);
        
        // Reload configuration
        var reload = _configuration as IConfigurationRoot;
        reload?.Reload();
        
        _logger.LogInformation("Configuration reloaded");
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Delay(Timeout.Infinite, stoppingToken);
    }
}

Step 6: Deploy and Manage

# Apply configurations
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml

# Check ConfigMaps
kubectl get configmaps -n production
kubectl describe configmap app-config -n production
kubectl get configmap app-config -n production -o yaml

# Check Secrets
kubectl get secrets -n production
kubectl describe secret app-secrets -n production
kubectl get secret app-secrets -n production -o yaml

# Update ConfigMap (without restart)
kubectl edit configmap app-config -n production

# Trigger rollout after ConfigMap change
kubectl patch deployment order-service -n production \
  -p '{"spec":{"template":{"metadata":{"annotations":{"configHash":"'$(date +%s)'"}}}}}'

# Update Secrets
kubectl create secret generic app-secrets \
  --from-literal=DatabasePassword=newpassword \
  --dry-run=client -o yaml | kubectl apply -f -

# Rollback if needed
kubectl rollout undo deployment/order-service -n production

# Debug configuration
kubectl exec -it order-service-xxxxx -n production -- cat /app/appsettings.Production.json
kubectl exec -it order-service-xxxxx -n production -- printenv

Related posts:

Step-by-Step: Kubernetes Network Policies for Secure Microservices

Kubernetes Clusters Age Without Showing Errors

Detect CrashLoopBackOff Root Cause in 10 Seconds

Post Views: 7

Post navigation

Step-by-Step: Full Stack Application with ASP.NET Core 8, React, and Docker
Step-by-Step: Real-Time Dashboard with SignalR in ASP.NET Core

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

October 2026
M T W T F S S
 1234
567891011
12131415161718
19202122232425
262728293031  
« Sep    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (973)
  • How to make theater mode the default for Youtube (952)
  • How to add default value for Entity Framework migrations for DateTime and Bool (939)
  • Get the First and Last Word from a String or Sentence in SQL (847)
  • How to select distinct rows in a datatable in C# (837)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (624)
  • Add Constraint to SQL Table to ensure email contains @ (590)
  • Average of all values in a column that are not zero in SQL (553)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (526)
  • Find numbers with more than two decimal places in SQL (468)

Recent Posts

  • CSS: Fix a prefers-color-scheme Media Query That Gets Silently Overridden by a Browser Extension’s Forced Dark Mode
  • Git: Fix a Merge Commit That Silently Drops a File Because Both Branches Deleted It Differently
  • HTML5: Fix a Native Lazy-Loading Image That Never Loads Because It Sits Inside a Hidden Tab Until the User Clicks It
  • The AI Prompt That Traces a Null Reference Exception Back to the Exact Line That First Produced the Null
  • The AI Prompt That Turns a Gym Membership Contract’s Fine Print Into a Plain-English List of Cancellation Steps
  • Photoshop: Fix a Color Profile Mismatch That Makes Printed Output Look Nothing Like What You Saw On Screen
  • WordPress: Fix Search Results That Return Pages From a Theme You Deactivated Months Ago
  • Visual Studio: Fix a Test Project That Builds Fine Alone but Fails to Discover Any Tests After a NuGet Restore
  • ASP.NET Core: Fix a File Upload That Times Out on Slow Connections Only Because Kestrel’s Minimum Data Rate Feature Kicked In
  • JavaScript: Fix an Array Destructuring Default Value That Silently Never Applies Because null Was Passed Instead of Undefined

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (973)
  • How to make theater mode the default for Youtube (952)
  • How to add default value for Entity Framework migrations for DateTime and Bool (939)
  • Get the First and Last Word from a String or Sentence in SQL (847)
  • How to select distinct rows in a datatable in C# (837)

Recent Posts

  • CSS: Fix a prefers-color-scheme Media Query That Gets Silently Overridden by a Browser Extension’s Forced Dark Mode
  • Git: Fix a Merge Commit That Silently Drops a File Because Both Branches Deleted It Differently
  • HTML5: Fix a Native Lazy-Loading Image That Never Loads Because It Sits Inside a Hidden Tab Until the User Clicks It
  • The AI Prompt That Traces a Null Reference Exception Back to the Exact Line That First Produced the Null
  • The AI Prompt That Turns a Gym Membership Contract’s Fine Print Into a Plain-English List of Cancellation Steps

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com