Deploying Azure Functions with Pulumi
Learn how to deploy a Consumption-plan Azure Function App with Pulumi and TypeScript, including the storage account it requires
Deploying Azure Functions with Pulumi
This guide deploys an Azure Function App on the Consumption plan with Pulumi - the storage account every Function App requires internally, the plan itself, and the app settings that wire them together.
Why Functions Need a Storage Account
Unlike AWS Lambda, an Azure Function App requires a storage account for its own internal use (trigger/binding state, logs, and the deployment package itself when using zip deploy) - it’s not optional infrastructure you’re choosing to add, it’s a hard requirement the platform enforces.
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as storage from "@pulumi/azure-native/storage";
import * as web from "@pulumi/azure-native/web";
const funcStorage = new storage.StorageAccount("funcstorage", {
resourceGroupName: resourceGroup.name,
location: location,
accountName: "myfuncstorage001",
sku: { name: storage.SkuName.Standard_LRS },
kind: storage.Kind.StorageV2,
});
Building the Connection String
The Function App needs the storage account’s connection string, which requires reading back its access key - listStorageAccountKeysOutput is the Output-returning variant of the key-listing API call, letting the result flow into pulumi.interpolate alongside other resource Outputs:
const storageAccountKeys = storage.listStorageAccountKeysOutput({
resourceGroupName: resourceGroup.name,
accountName: funcStorage.name,
});
const storageConnectionString = pulumi.interpolate`DefaultEndpointsProtocol=https;AccountName=${funcStorage.name};AccountKey=${storageAccountKeys.keys[0].value};EndpointSuffix=core.windows.net`;
Consumption Plan and Function App
const plan = new web.AppServicePlan("func-plan", {
resourceGroupName: resourceGroup.name,
location: location,
name: "func-consumption-plan",
kind: "functionapp",
sku: {
name: "Y1",
tier: "Dynamic",
},
});
const functionApp = new web.WebApp("processor", {
resourceGroupName: resourceGroup.name,
location: location,
name: "app-processor-func",
serverFarmId: plan.id,
kind: "functionapp",
siteConfig: {
appSettings: [
{ name: "AzureWebJobsStorage", value: storageConnectionString },
{ name: "FUNCTIONS_EXTENSION_VERSION", value: "~4" },
{ name: "FUNCTIONS_WORKER_RUNTIME", value: "node" },
{ name: "WEBSITE_NODE_DEFAULT_VERSION", value: "~20" },
],
},
});
export const functionAppName = functionApp.name;
export const functionAppHostName = functionApp.defaultHostName;
sku: { name: "Y1", tier: "Dynamic" } is what actually selects the Consumption plan - pay-per-execution with automatic scale-to-zero, Azure’s rough equivalent of Lambda’s pricing model. A WebApp resource is used for the Function App itself (not a dedicated “FunctionApp” resource type) - kind: "functionapp" on both the plan and the app is what tells Azure to treat this WebApp as a Function App rather than a regular App Service web app.
FUNCTIONS_WORKER_RUNTIME and FUNCTIONS_EXTENSION_VERSION are required app settings, not optional configuration - without them the platform doesn’t know which language runtime to host or which Functions runtime major version to use.
Best Practices
- Never wire the storage connection string as a plain string literal - build it from the resource’s own
Outputs withpulumi.interpolate, as above, so it updates automatically if the account is ever recreated. - Use a dedicated storage account per Function App (as here) rather than sharing one across multiple apps - the internal state each app keeps can otherwise collide.
- Pin
WEBSITE_NODE_DEFAULT_VERSION/FUNCTIONS_EXTENSION_VERSIONexplicitly rather than leaving them at whatever the platform’s current default is - a platform-side default change shouldn’t be able to silently change your function’s runtime.
Conclusion
Compare this to the Lambda guide: both are “pay per execution” serverless compute, but Azure’s model requires more explicit plumbing (a storage account, a connection string, a plan resource) where Lambda’s aws.lambda.Function bundles equivalent concerns into fewer resources.
For more Pulumi topics, check out: