Blob Storage Integration (Managed Identity)¶
Use this recipe to connect a Node.js Container App to Azure Blob Storage with managed identity first and a connection string fallback when you still depend on shared keys.
Architecture¶
flowchart TD
C[Client] --> I[Container Apps Ingress]
I --> APP[Node.js Container App]
APP --> BLOB[Azure Blob Storage]
APP -.-> MI[Managed Identity]
MI -.-> ENTRA[Microsoft Entra ID]
MI -.-> BLOB Solid arrows show runtime data flow. Dashed arrows show identity and authentication.
Prerequisites¶
- Existing Container App:
$APP_NAMEin$RG - Existing storage account and blob container
- Azure CLI with the Container Apps extension
Step 1: Enable managed identity on the Container App¶
az containerapp identity assign \
--name "$APP_NAME" \
--resource-group "$RG" \
--system-assigned
export PRINCIPAL_ID=$(az containerapp show \
--name "$APP_NAME" \
--resource-group "$RG" \
--query "identity.principalId" \
--output tsv)
Step 2: Grant Blob data access¶
export STORAGE_ID=$(az storage account show \
--name "$STORAGE_ACCOUNT" \
--resource-group "$RG" \
--query "id" \
--output tsv)
az role assignment create \
--assignee-object-id "$PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Contributor" \
--scope "$STORAGE_ID"
Step 3: Configure non-secret settings¶
Azure Container Apps does not inject Blob Storage account URLs automatically.
az containerapp update \
--name "$APP_NAME" \
--resource-group "$RG" \
--set-env-vars STORAGE_ACCOUNT_URL="https://$STORAGE_ACCOUNT.blob.core.windows.net" STORAGE_CONTAINER="$STORAGE_CONTAINER"
Step 4: Node.js code (managed identity)¶
Install dependencies:
Upload and download a blob with DefaultAzureCredential:
const { DefaultAzureCredential } = require("@azure/identity");
const { BlobServiceClient } = require("@azure/storage-blob");
async function streamToBuffer(readable) {
const chunks = [];
for await (const chunk of readable) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
return Buffer.concat(chunks);
}
function createBlobServiceClient() {
if (process.env.AZURE_STORAGE_CONNECTION_STRING) {
return BlobServiceClient.fromConnectionString(process.env.AZURE_STORAGE_CONNECTION_STRING);
}
return new BlobServiceClient(
process.env.STORAGE_ACCOUNT_URL,
new DefaultAzureCredential(),
);
}
async function run() {
const service = createBlobServiceClient();
const container = service.getContainerClient(process.env.STORAGE_CONTAINER);
const blob = container.getBlockBlobClient("hello.txt");
await blob.uploadData(Buffer.from("hello from aca"), { overwrite: true });
const response = await blob.download();
const content = await streamToBuffer(response.readableStreamBody);
console.log(content.toString("utf8"));
}
run().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Step 5: Connection string fallback¶
az containerapp secret set \
--name "$APP_NAME" \
--resource-group "$RG" \
--secrets storage-connection-string="DefaultEndpointsProtocol=https;AccountName=$STORAGE_ACCOUNT;AccountKey=<storage-account-key>;EndpointSuffix=core.windows.net"
az containerapp update \
--name "$APP_NAME" \
--resource-group "$RG" \
--set-env-vars AZURE_STORAGE_CONNECTION_STRING=secretref:storage-connection-string STORAGE_CONTAINER="$STORAGE_CONTAINER"
Verification¶
- Confirm RBAC assignment:
- Confirm the uploaded blob exists:
az storage blob list \
--account-name "$STORAGE_ACCOUNT" \
--container-name "$STORAGE_CONTAINER" \
--auth-mode login \
--output table
- Check app logs for successful upload and download operations: