Declarative slot swap (targetBuildVersion) for Web App for Containers on Linux: behaviour with linuxFxVersion in a single Bicep deployment

George Flourentzos 0 Reputation points
2026-09-25T11:35:00.63+00:00

Hi! We currently have a Linux App Service app (not containerised). We would like to simultaneously use 2 App Service features:

The idea is to have a single idempotent bicep file/deployment that can completely describe the desired state (which docker images should be running akin to linuxFxVersion) while handling a smooth deployment (deploy to slot, warm-up, health check, swap with production)

Questions:

  1. Is the buildVersion / targetBuildVersion swap supported for container apps, and does it run the same multi-phase swap (apply sticky settings → warm-up ping → route switch) as az webapp deployment slot swap? If warm-up fails, does the ARM deployment fail?
  2. During that swap, do linuxFxVersion and buildVersion move with the slot content (i.e. after the swap production shows image B and staging shows image A)?
  3. How can we achieve an idempotent bicep file holding the container versions? If the production Microsoft.Web/sites resource in the template omits siteConfig.linuxFxVersion, similar to how a non-containerised app would omit buildVersion but define targetBuildVersion, is the current value preserved, or is it reset?
  4. Can both the slot's buildVersion and the site's targetBuildVersion be set in the same deployment, or must they be two deployments?

Thank you

Azure App Service
Azure App Service

Azure App Service is a service used to create and deploy scalable, mission-critical web apps.

0 comments No comments

1 answer

Sort by: Oldest
  1. Fabian Zankl 345 Reputation points
    2026-09-25T12:58:25.2533333+00:00

    Hi @George Flourentzos ,

    You want one idempotent Bicep deployment that pins the container image (linuxFxVersion with a digest) and moves it into production through a staging slot with warm-up, using buildVersion / targetBuildVersion instead of az webapp deployment slot swap. The documentation does not cover containers explicitly for this mechanism, so I reproduced the pattern below on an S1 Linux plan (API version 2024-04-01, nginx images pinned by digest as A, B and C).

    1. Does the declarative swap work for containers, and is it the full swap?

    Yes. The staging slots documentation says a differing targetBuildVersion triggers "the swap operation" and restricts only auto swap, a different feature, on Linux and containers. In the test, every successful swap appeared in the activity log as Apply Web App Slot Configuration, StartSlotWarmup, EndSlotWarmup and Swap Web App Slots, the same multi-phase sequence as a regular swap; failed swaps showed no EndSlotWarmup and ended with Swap Web App Slots: Failed.

    The warm-up is an HTTP request to the container, controlled by WEBSITE_SWAP_WARMUP_PING_PATH and WEBSITE_SWAP_WARMUP_PING_STATUSES; the documentation states that warm-up and swap stop when the status code is not in the list. A container that crashes or never answers fails the swap with "did not respond to http ping" (Azure OSS Developer Support). This warm-up is independent of the Health check feature (healthCheckPath); to gate the swap on your health endpoint, point the ping path at it and restrict the statuses, for example to 200.

    The failure does reach the ARM deployment. With a ping path returning 404 and statuses set to 200, the deployment failed with BadRequest: Cannot swap slots ... application initialization in 'staging' slot either took too long or failed, and production stayed on the previous image. After fixing the path, the next run completed the swap. The documentation warns that a failed swap can leave the slot blocked with "The slot cannot be changed because its configuration settings have been prepared for swap"; this did not happen in my single test, but a pipeline check for that state is a sensible precaution.

    2. Do linuxFxVersion and buildVersion move with the content?

    Yes. After swapping B into production, production had B's linuxFxVersion and buildVersion and staging had A's; the kind values in the resource JSON were exchanged as well, although kind is not on the documented list of swapped settings. This matches the documentation: language framework settings, which linuxFxVersion holds on Linux, are listed as swapped, and the ARM sample can only be idempotent if production carries the matching buildVersion after the swap.

    3. Holding the image versions in an idempotent template

    Do not declare linuxFxVersion on the production site; otherwise ARM updates production in place, without warm-up, and the swap has nothing left to do. ARM normally resets omitted properties on redeployment (deployment modes article), but the article names web app site configuration as an exception: it lives in the child resource Microsoft.Web/sites/config, which an empty siteConfig object leaves untouched. That makes siteConfig: {} a suitable way to leave the production configuration untouched in main.bicep; the swap module follows the documented swap sample, which sends no site configuration at all. In the test, neither PUT changed production's linuxFxVersion in any run without a swap, with and without the empty siteConfig object.

    Mind the kind: when the slot is created, it must equal the production site's stored kind, otherwise the deployment fails with The slot 'kind' property 'app,linux,container' must match the Production site 'kind' property 'app,linux'. A new site without linuxFxVersion was stored as app,linux although the template declared app,linux,container. Your existing non-container app should also be app,linux; check the kind in its resource JSON before the first run. After creation, a differing declared kind was accepted.

    The pattern uses two files because it writes the production site twice in deployment order, once with its normal configuration and once with targetBuildVersion after the slot. Declaring the same resource name twice in one Bicep file triggers BCP121, so the second write sits in a module; the module boundary also resolves the dependency cycle described in section 4.

    // main.bicep
    param siteName string
    // must equal the production site's stored kind when the slot is created;
    // a non-container Linux site is stored as 'app,linux'
    param siteKind string = 'app,linux'
    param location string = resourceGroup().location
    param planId string
    @description('Full image reference including the digest')
    param image string
    
    var buildVersion = uniqueString(image)
    
    resource site 'Microsoft.Web/sites@2024-04-01' = {
      name: siteName
      location: location
      kind: siteKind
      properties: {
        serverFarmId: planId
        // empty object: sites/config is left untouched, production gets its image only through the swap
        siteConfig: {}
      }
    }
    
    resource staging 'Microsoft.Web/sites/slots@2024-04-01' = {
      parent: site
      name: 'staging'
      location: location
      kind: siteKind
      properties: {
        serverFarmId: planId
        #disable-next-line BCP037
        buildVersion: buildVersion
        siteConfig: {
          linuxFxVersion: 'DOCKER|${image}'
        }
      }
    }
    
    module swap 'swap.bicep' = {
      name: 'swap-${buildVersion}'
      params: {
        siteName: siteName
        siteKind: siteKind
        location: location
        buildVersion: buildVersion
      }
      dependsOn: [
        staging
      ]
    }
    
    // swap.bicep
    param siteName string
    param siteKind string
    param location string
    param buildVersion string
    
    resource site 'Microsoft.Web/sites@2024-04-01' = {
      name: siteName
      location: location
      kind: siteKind
      properties: {
        #disable-next-line BCP037
        targetBuildVersion: buildVersion
      }
    }
    

    With image B, the first run deploys B to staging and swaps it in, leaving A in staging. The second run writes B to staging again (which should restart only staging) and finds production at the target version, so no swap happens; from the third run on, nothing changes, and the container logs showed no image pull. After the first successful swap, staging holds whatever production ran before, in your case the current code-based deployment.

    This convergence costs you the quick rollback: after the second unchanged run, staging no longer holds A, so an immediate reverse swap only works until then, and it leaves production out of line with the template, which the next run swaps forward again. A rollback consistent with the template is a run with the previous image parameter, including warm-up.

    4. Same deployment or two?

    One deployment, as in the documented sample. In Bicep, Azure/bicep#8712 describes two obstacles:

    • A dependsOn from the site to its child slot is a cycle. A maintainer suggested a segmented slot name instead of parent, which the reporter confirmed for an existing site; without parent there is no implicit dependency on the site. The module above avoids both issues: it is a nested deployment within the same az deployment group create and runs after the slot.
    • buildVersion and targetBuildVersion are missing from the Microsoft.Web REST API specification behind the Bicep types, so Bicep reports BCP037. The properties are still sent; the directives only suppress the warning.

    References


    Drafted with help from Claude, disclosed per the Q&A AI usage policy. Documented: the ARM rule for redeclared resources and its site configuration exception, the swap steps, swapped settings, warm-up settings and the ARM swap sample, the Bicep cycle and type warnings (Azure/bicep#8712, REST API specification), and the Bicep rule against declaring the same resource twice in one file (BCP121). Reproduced on an S1 Linux plan with API version 2024-04-01, starting from both a new site and an existing Linux code app: the multi-phase swap via targetBuildVersion for a container app, the failed deployment on a failed warm-up with production unchanged, linuxFxVersion, buildVersion and kind moving with the swap, linuxFxVersion staying unchanged on production across repeated runs, the convergence including the loss of the rollback copy, the previous code deployment landing in staging after the first swap, and the kind requirement at slot creation. The absence of the prepared-for-swap error is a single observation.

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.