Injecting Configuration Files into Azure Container Apps

Injecting Configuration Files into Azure Container Apps

...using secret volume mounts

Injecting Configuration Files into Azure Container Apps

Azure Container Apps (ACA) is Microsoft’s serverless container platform that makes it easy to deploy and scale containerised applications.

Unlike Kubernetes, ACA doesn’t support ConfigMaps for injecting configuration files.

At first glance, this appears to leave you with two options:

  1. Azure Files – requires a storage account, file share, and preloading of files.
  2. Baked-in configs – requires rebuilding container images whenever configuration changes

However, there is another way!

In this post, I’ll show you how to use secret volume mounts to inject configuration files — without storage accounts or image rebuilds.


Step 1 - Create the secret

We start by reading the configuration file in Terraform:

1
2
3
locals {
  test_nginx_config = file("${path.module}/nginx.conf")
}

In this, we put a very basic config file to illustrate this example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
server {
    listen 80;
    server_name _;
    
    location / {
        return 200 'Nginx config loaded from secret!\n';
        add_header Content-Type text/plain;
    }
    
    location /health {
        return 200 'OK';
        add_header Content-Type text/plain;
    }
}

Then we load the content into the secrets block of the container app resource:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
resource "azapi_resource" "test_nginx_app" {
  type      = "Microsoft.App/containerApps@2025-02-02-preview"
  name      = "ca-test-nginx-config"
  parent_id = azurerm_resource_group.test.id
  location  = azurerm_resource_group.test.location

  body = {
    properties = {
      environmentId = module.test_container_app_environment.resource_id
      configuration = {
        activeRevisionsMode = "Single"

        # here - we load the secret
        secrets = [
          {
            name  = "nginx-config-content"
            value = local.test_nginx_config
          }
        ]
        
        # <snip>
      }
      template = {
        # snip - will show this below
      }
    }
  }
}

Step 2 - Mount the secret as a file

Within the template, define a volume using the secret:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template = {
  volumes = [
    {
      name        = "nginx-config"
      storageType = "Secret"
      secrets = [
        {
          secretRef = "nginx-config-content"
          path      = "default.conf"
        }
      ]
    }
  ]
}

The secretRef refers to the secret name created in step 1, with the path being the name of the file that will be created.

Then mount that volume in your container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
template {
  containers = [
    {
      name  = "nginx"
      image = "nginx:alpine"
      resources = {
        cpu    = 0.25
        memory = "0.5Gi"
      }
      volumeMounts = [
        {
          volumeName = "nginx-config"
          mountPath  = "/etc/nginx/conf.d"
        }
      ]
    }
  ]
}

The volumeMount specifies the directory in the container where the file will be created.

So, in our example, it will create a file here:

/etc/nginx/conf.d/default.conf

Step 3 - Verify the output

We’ll add an output variable to provide the URL:

1
2
3
output "test_app_url" {
  value = "https://${azapi_resource.test_nginx_app.output.properties.configuration.ingress.fqdn}"
}

Run terraform apply, and you should see something like this:

A happy Terraform Apply

& then if we browse to the URL:

Nginx serving the injected configuration

You can also confirm the mount by attaching to the container via the Azure Portal:

exec into the container

An Azure Verified Modules approach

I have an open Pull Request for the Azure Container Apps AVM module, which wraps this functionality up nicely:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
module "container_app" {
  source = "https://github.com/kewalaka/terraform-azurerm-avm-res-app-containerapp?ref=feat/azapi" # not yet released!

  container_app_environment_resource_id = azurerm_container_app_environment.this.id
  location                              = azurerm_resource_group.this.location

  name                = module.naming.container_app.name_unique
  resource_group_name = azurerm_resource_group.this.name
  template = {
    containers = [{
      image  = "nginx:alpine"
      name   = "nginx"
      cpu    = "0.25"
      memory = "0.5Gi"
      readiness_probes = [{
        initial_delay_seconds = 5
        path                  = "/health"
        period_seconds        = 10
        port                  = 80
        transport             = "HTTP"
      }]
      volume_mounts = [{
        name = "nginx-config"
        path = "/etc/nginx/conf.d"
      }]
    }]
    volumes = [{
      name         = "nginx-config"
      storage_type = "Secret"
      secrets = [{
        secret_name = "nginx-config"
        path        = "default.conf"
      }]
    }]
  }
  ingress = {
    external_enabled = true
    target_port      = 80
  }
  secrets = {
    nginx_config = {
      name  = "nginx-config"
      value = local.test_nginx_config
    }
  }
}

The above example also illustrates a readiness probe, which checks the status of the /health endpoint within the Nginx example config.

Wrapping up

This pattern gives you Kubernetes-style config injection — without needing storage accounts or image rebuilds.

Want to explore the full example?

Check out the GitHub repo here: https://github.com/kewalaka/aca-configmount

This post is licensed under CC BY 4.0 by the author.