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:
- Azure Files – requires a storage account, file share, and preloading of files.
- 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:
& then if we browse to the URL:
You can also confirm the mount by attaching to the container via the Azure Portal:
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