Why Flex Consumption fixed a problem Y1 could not

A Linux Consumption function app can only fetch its deployment package from a SAS URL. User-delegation SAS expires in seven days. That leaves one bad option — unless you move to Flex.

  • Azure
  • Azure Functions
  • Managed Identity
  • Bicep
  • Flex Consumption

I set out to build a small monitoring app on Azure Functions with no stored credentials anywhere. Cosmos and Service Bus both had disableLocalAuth: true, every binding used a managed identity, and the Bicep had exactly one connection string left in it — AzureWebJobsStorage, which I fully intended to delete on the next pass.

Then the deployment package turned out to be the thing that would not let go of its key.

The setup

Y1 Linux Consumption, deployed with az functionapp deployment source config-zip. That command uploads your zip, stores it, and points the app at it with WEBSITE_RUN_FROM_PACKAGE.

On Windows Consumption you can set WEBSITE_RUN_FROM_PACKAGE=1 and the platform manages the package itself. On Linux Consumption that value is not supported. The setting has to be a URL the host can fetch the package from.

So the host needs to authenticate to blob storage. And on Y1, its only way to do that is a SAS token embedded in the URL.

Where it falls apart

There are two kinds of SAS you can hand it.

A user-delegation SAS is signed with Entra credentials rather than the storage account key. It is the right shape — no account key involved. Try to issue one for a year and the CLI stops you:

ERROR: incorrect usage: --expiry should be within 7 days from now

That is not an az quirk. A user-delegation key has a maximum lifetime of seven days by design. So your function app works for a week and then cannot fetch its own code.

An account-key SAS has no such limit. It is also a long-lived credential derived from the storage account key, sitting in an app setting, granting read access to your deployment container until you remember to rotate it. Which is precisely the thing I had spent the afternoon removing everywhere else.

Neither is acceptable. One breaks weekly; the other is the anti-pattern.

Flex Consumption removes the choice

On Flex, the package location is not an app setting with a SAS in it. It is structured configuration on the site resource, and it takes an authentication type:

resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
  name: 'func-example'
  location: location
  kind: 'functionapp,linux'
  identity: { type: 'SystemAssigned' }
  properties: {
    serverFarmId: plan.id
    functionAppConfig: {
      deployment: {
        storage: {
          type: 'blobContainer'
          value: '${storage.properties.primaryEndpoints.blob}deployments'
          authentication: {
            type: 'SystemAssignedIdentity'
          }
        }
      }
      scaleAndConcurrency: {
        maximumInstanceCount: 40
        instanceMemoryMB: 2048
      }
      runtime: { name: 'node', version: '22' }
    }
  }
}

The app authenticates to its own package store as itself. No SAS, no expiry, nothing to rotate.

The plan is FC1 / FlexConsumption:

resource plan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: 'plan-example'
  location: location
  sku: { name: 'FC1', tier: 'FlexConsumption' }
  kind: 'functionapp'
  properties: { reserved: true }
}

Note what is not there: no linuxFxVersion. The runtime moves into functionAppConfig.runtime, and setting both is a good way to confuse yourself.

The part that made it worth doing

Once the identity was already fetching the package, the last connection string went too:

{ name: 'AzureWebJobsStorage__accountName', value: storage.name }

The __accountName suffix is how the Functions host knows to reach for the managed identity instead of parsing a connection string. Same pattern as ServiceBusConnection__fullyQualifiedNamespace and CosmosConnection__accountEndpoint.

That needs three role assignments on the storage account, not one:

  • Storage Blob Data Owner — the host’s own state, and the package
  • Storage Queue Data Contributor
  • Storage Table Data Contributor

Blob Data Contributor is not enough for the host’s internal use. That one costs people an hour.

Two things that will catch you

You cannot convert Y1 to FC1 in place. The SKUs are not interchangeable — you delete the app and the plan and redeploy. Which leads directly to the second thing.

Role assignment names are immutable in the ways that matter. The common Bicep pattern is:

name: guid(storage.id, functionApp.id, roleDefinitionId)

Delete and recreate the function app and it gets a new principal ID, but functionApp.id is unchanged — so the GUID is unchanged, so Azure tries to update the existing assignment with a different principal:

RoleAssignmentUpdateNotPermitted: Tenant ID, application ID, principal ID,
and scope are not allowed to be updated.

The instinct is to put principalId in the guid() inputs. That does not compile:

BCP120: This expression is being used in an assignment to the "name" property
of the "Microsoft.Authorization/roleAssignments" type, which requires a value
that can be calculated at the start of the deployment.

Role assignment names must be resolvable before the deployment runs, and a module output is not. So either delete the stale assignments by hand before redeploying, or derive the name from something stable that you also control — the computed app name rather than the resource’s .id.

I deleted five orphaned assignments by hand and moved on.

Was it worth it

For a hobby app, arguably not — an account-key SAS would have worked and nobody would have known.

But the whole point of the build was that a compromised repository or a leaked app setting should not hand anyone access to anything. Leaving one long-lived storage credential in place to serve the deployment package would have been a strange place to stop, and the Y1-to-Flex move took about forty minutes including the role assignment cleanup.

Flex is also where the platform is going. Y1 Linux Consumption is the older shape, and its package-fetch story is a good illustration of why.

Next step

Tell me what is not working.

A short call, no pitch deck. You describe the problem; you get a straight recommendation on how to approach it — the architecture, the sequence, and what to build first. If it is a fit, that becomes a scoped plan with a fixed price and a date.