Storage in Azure Container Apps¶
Azure Container Apps supports three storage types for different lifecycle and sharing needs. Choosing the right type prevents data loss, reduces cost, and avoids common configuration errors such as writing persistent data to ephemeral volumes.
Main Content¶
Storage Types at a Glance¶
| Storage Type | Scope | Persistence | Typical Use |
|---|---|---|---|
| Container-scoped | Single container | Until container restarts | Local app cache, temp files |
| Replica-scoped (EmptyDir) | All containers in one replica | Until replica shuts down | Sidecar log sharing, scratch space |
| Azure Files | Shared across replicas/revisions | Persistent | File uploads, shared state |
Do not use ephemeral storage for persistent data
Container-scoped and replica-scoped (EmptyDir) storage is lost when a container or replica stops. Any data that must survive restarts or scaling events must be written to Azure Files or an external store.
flowchart TD
A[Container App Replica] --> B[Container-Scoped Storage]
A --> C[EmptyDir Volume]
A --> D[Azure Files Volume]
B --> B1["Lost on container restart\nOnly visible to that container"]
C --> C1["Shared across containers\nin the same replica\nLost when replica stops"]
D --> D1["Persisted to Azure Files\nShared across replicas\nand revisions"] Ephemeral Storage¶
Each replica receives a total ephemeral storage budget based on its vCPU allocation. This budget is shared across container-scoped and replica-scoped storage.
| vCPUs allocated | Total ephemeral storage |
|---|---|
| 0.25 or lower | 1 GiB |
| 0.5 or lower | 2 GiB |
| 1 or lower | 4 GiB |
| Over 1 | 8 GiB |
flowchart TD
subgraph Replica ["Replica (0.25 vCPU → 1 GiB total ephemeral)"]
C1[Container A\nContainer-scoped storage]
C2[Container B\nContainer-scoped storage]
E[EmptyDir Volume\nShared by A and B]
end
C1 -- shares budget --> E
C2 -- shares budget --> E You can inspect the ephemeral storage limit for a running container app:
az containerapp show \
--name $APP_NAME \
--resource-group $RG \
--query "properties.template.containers[0].resources" \
--output yaml
| Command/Code | Purpose |
|---|---|
--query "properties.template.containers[0].resources" | Extracts the CPU, memory, and ephemeralStorage fields from the first container definition |
Container-Scoped Storage¶
Every container has access to its own writable filesystem. This storage:
- Is temporary and disappears when the container shuts down or restarts.
- Is visible only to the current container — not to sidecars or other replicas.
- Requires no configuration — it is always available.
Use container-scoped storage for ephemeral operations: unpacking archives, writing intermediate computation output, or storing short-lived session data.
Replica-Scoped Storage (EmptyDir)¶
An EmptyDir volume is the equivalent of a Kubernetes EmptyDir mount — a temporary directory shared across all containers in the same replica. It persists for the lifetime of the replica, surviving individual container restarts.
When to use EmptyDir:
- Main app container writes log files, sidecar container ships them.
- Multiple init containers pass data to the main container.
- Scratch space for data processing that must survive container crashes but not replica replacement.
Configure EmptyDir via YAML¶
Export your app spec, add a volume and mount, then redeploy:
| Command/Code | Purpose |
|---|---|
--output yaml | Exports the full app spec as YAML for editing |
Edit app.yaml to add the volume and mount:
properties:
template:
containers:
- name: my-app
image: <IMAGE>
resources:
cpu: 0.25
memory: 0.5Gi
volumeMounts:
- mountPath: /tmp/cache
volumeName: cache-vol
volumes:
- name: cache-vol
storageType: EmptyDir
| Field | Purpose |
|---|---|
storageType: EmptyDir | Declares a replica-scoped ephemeral volume |
volumeName | Links the volume definition to the container mount |
mountPath | Absolute path inside the container where the volume appears |
Apply the update:
| Command/Code | Purpose |
|---|---|
--yaml app.yaml | Applies the edited spec including the new volume definition |
--output yaml | Returns the updated app spec for verification |
Verify the volume is applied:
| Command/Code | Purpose |
|---|---|
--output yaml | Returns the full app spec; inspect volumes and volumeMounts to confirm the EmptyDir is registered |
volumeMounts:
- mountPath: /tmp/cache
volumeName: cache-vol
volumes:
- name: cache-vol
storageType: EmptyDir
Verify EmptyDir Write and Read at Runtime¶
Run a Container Apps Job to write a file and read it back from the EmptyDir mount:
| Command/Code | Purpose |
|---|---|
az containerapp job start | Triggers a manual job execution; the job writes to and reads from the EmptyDir volume |
The job command writes a file to /mnt/cache, reads it back, lists the directory, and reports disk usage:
WRITTEN
hello-emptydir
total 12
drwxr-xr-x 2 root root 4096 May 1 12:30 .
drwxr-xr-x 1 root root 4096 May 1 12:30 ..
-rw-r--r-- 1 root root 15 May 1 12:30 test.txt
Filesystem Size Used Avail Use% Mounted on
overlay 148G 28G 114G 20% /mnt/cache
DONE
| Output | Meaning |
|---|---|
WRITTEN / hello-emptydir | Write and read succeeded — EmptyDir is writable |
test.txt 15 bytes | File persisted within the replica lifetime |
148G … /mnt/cache | Backed by node ephemeral disk, not a separate volume limit |
EmptyDir does not share data across replicas
Each replica has its own EmptyDir instance. Data written by one replica is not visible to another. For cross-replica sharing, use Azure Files.
Azure Files (Persistent Storage)¶
Azure Files provides persistent storage that survives container restarts, replica replacements, and revision rollbacks. Multiple containers across different replicas, revisions, or container apps can mount the same file share simultaneously.
Supported protocols:
| Protocol | storageType value | Use case |
|---|---|---|
| SMB | AzureFile | General-purpose file sharing, Windows-compatible |
| NFS | NfsAzureFile | Linux-native workloads, high-throughput scenarios |
NFS requires a custom VNet
NFS Azure Files requires the Container Apps environment to use a custom VNet. Ports 445 (SMB) and 2049 (NFS) must be open in the NSG associated with the environment subnet.
Unsupported storage backends
Azure Container Apps does not support mounting Azure NetApp Files or Azure Blob Storage as volumes.
Azure Files SMB in Consumption environments
Port 445 (SMB) outbound may be blocked in Consumption-only environments without a custom VNet. If your job or app replica stays in Activating state after adding an Azure Files volume, the mount is failing silently. Workarounds:
- Deploy into a Workload Profiles environment with a custom VNet and an NSG that allows port 445 outbound.
- Switch to NFS (
storageType: NfsAzureFile) — requires a Premium FileStorage account and a custom VNet. - Use EmptyDir for ephemeral scratch space and persist data to Azure Blob Storage via application code.
Register Azure Files with the Environment¶
Before mounting, register the file share at the environment level:
az containerapp env storage set \
--name $ENV_NAME \
--resource-group $RG \
--storage-name my-files \
--storage-type AzureFile \
--azure-file-account-name $STORAGE_ACCOUNT \
--azure-file-account-key $STORAGE_KEY \
--azure-file-share-name $SHARE_NAME \
--access-mode ReadWrite
| Command/Code | Purpose |
|---|---|
--storage-name | Logical name for the storage definition in the environment |
--storage-type AzureFile | Specifies SMB Azure Files |
--access-mode ReadWrite | Mounts with read and write permissions |
Verify the registration:
| Command/Code | Purpose |
|---|---|
az containerapp env storage list | Lists all storage definitions registered in the environment |
Mount Azure Files in a Container¶
Add the volume and mount to your app YAML:
properties:
template:
containers:
- name: my-app
image: <IMAGE>
volumeMounts:
- mountPath: /mnt/data
volumeName: files-vol
subPath: uploads
volumes:
- name: files-vol
storageType: AzureFile
storageName: my-files
| Field | Purpose |
|---|---|
storageName | References the storage definition registered in the environment |
subPath | Optional subdirectory within the share to mount (do not prefix with /) |
Storage Decision Guide¶
flowchart TD
Q1{Does data need to survive\na container restart?} -->|No| Q2{Is it shared between\ncontainers in same replica?}
Q1 -->|Yes| Q3{Is it shared across\nreplicas or revisions?}
Q2 -->|No| A1[Container-scoped storage\nNo config needed]
Q2 -->|Yes| A2[EmptyDir volume\nstorageType: EmptyDir]
Q3 -->|No| A2
Q3 -->|Yes| Q4{High-throughput\nor Linux-native?}
Q4 -->|No| A3[Azure Files SMB\nstorageType: AzureFile]
Q4 -->|Yes| A4[Azure Files NFS\nstorageType: NfsAzureFile\nRequires custom VNet] Common Mistakes¶
| Mistake | Impact | Fix |
|---|---|---|
| Writing upload files to container-scoped storage | Files lost on container restart | Mount Azure Files at the upload path |
| Using EmptyDir for cross-replica cache | Each replica has isolated storage; cache misses on every new replica | Use Redis Cache or Azure Files |
| Mounting NFS without a custom VNet | Deployment fails silently | Configure custom VNet before creating the environment |
Sub path starting with / | Container app fails to start | Use relative sub paths: uploads, not /uploads |
| Exceeding ephemeral storage budget | EphemeralStorageExceeded crash loop | Reduce write volume, add log rotation, or increase vCPU allocation |
See Also¶
- EmptyDir Disk Full — Lab Guide
- EmptyDir Disk Full — Playbook
- Azure Files Mount Failure — Playbook
- Volume Permission Denied — Playbook