AZURE APP SERVICE AND FUNCTIONS → STACKIT · ARCHITECTURE

Azure App Service and Functions on STACKIT

STACKIT has no App Service plan and, as the run states, no generally available function-as-a-service. A code web app moves to Cloud Foundry, a container web app moves to SKE, and a function app becomes a Cloud Foundry app or a container. This page shows how StackToIT classifies each case on a demo estate and how to choose the target for your own apps.

A code web app and a function app, assessed

A demo estate written for this page and assessed by StackToIT on 4 November 2026: a Linux App Service plan with a Node.js web app, a container web app and a Python function app with its host storage account. Left: two of the input blocks. Right: the only resource block the engine writes for both of them; the review comments above it in the file are left out here. The input blocks are not adjacent in main.tf, so each has its own pane, verbatim.

Input: azurerm_linux_web_app.portalmain.tf
resource "azurerm_linux_web_app" "portal" {
  name                = "app-portal-gwc"
  resource_group_name = azurerm_resource_group.apps.name
  location            = azurerm_resource_group.apps.location
  service_plan_id     = azurerm_service_plan.apps.id
  https_only          = true

  site_config {
    always_on = true

    application_stack {
      node_version = "20-lts"
    }
  }

  app_settings = {
    "WEBSITE_RUN_FROM_PACKAGE" = "1"
    "ORDERS_API_URL"           = "https://orders.example.internal"
  }
}
Input: azurerm_linux_function_app.jobsmain.tf
resource "azurerm_linux_function_app" "jobs" {
  name                       = "func-jobs-gwc"
  resource_group_name        = azurerm_resource_group.apps.name
  location                   = azurerm_resource_group.apps.location
  service_plan_id            = azurerm_service_plan.apps.id
  storage_account_name       = azurerm_storage_account.func.name
  storage_account_access_key = azurerm_storage_account.func.primary_access_key

  site_config {
    application_stack {
      python_version = "3.11"
    }
  }

  app_settings = {
    "QUEUE_CONNECTION" = "stfuncappsgwc01"
  }
}
STACKIT Terraform by StackToITterraform/apps.tf
resource "stackit_scf_organization" "migrated_apps" {
  project_id = var.project_id
  name       = "migrated-apps"
}
  • An organization, not an app

    stackit_scf_organization is a Cloud Foundry organization. The apps are deployed into a space of it with cf push, using a manifest, a buildpack and environment variables, none of which the draft generates. The NOT CARRIED comment names the settings of the demo (app settings, HTTPS only, always on, the plan reference and the runtime version) and states that no App Service setting is generated: plan, runtime stack, app settings, site configuration, authentication, identity, backup, logs, slots.

  • Two sources, one organization

    The portal web app and the function app share the block, because both land in the same Cloud Foundry organization. The comment above the block names both under replaces.

  • Review items the run raises

    For the web app the run asks you to check that a Cloud Foundry buildpack covers the runtime stack and version. STACKIT's Cloud Foundry FAQ says that cf buildpacks lists the current built-in buildpacks and that full support covers only those; a community buildpack can be named by its GitHub URL and version tag, but only as a temporary measure. For the function app it asks you to list the triggers and bindings from host.json or function.json, because the Terraform does not show them.

A container web app, assessed

From the same run. The legacy web app already runs a container image, so the run classifies it as CONTAINERIZE and drafts a cluster for it, not the workload.

Example inputmain.tf
resource "azurerm_linux_web_app" "legacy" {
  name                = "app-legacy-gwc"
  resource_group_name = azurerm_resource_group.apps.name
  location            = azurerm_resource_group.apps.location
  service_plan_id     = azurerm_service_plan.apps.id
  https_only          = true

  site_config {
    always_on = true

    application_stack {
      docker_image_name   = "shop/legacy-api:2.4"
      docker_registry_url = "https://registry.example.com"
    }
  }
}
STACKIT Terraform by StackToITterraform/kubernetes.tf
resource "stackit_ske_cluster" "containers" {
  project_id = var.project_id
  name       = "containers"
  node_pools = [{
    name               = "workloads"
    machine_type       = var.containers_machine_type
    minimum            = var.containers_nodes_min
    maximum            = var.containers_nodes_max
    availability_zones = var.availability_zones
  }]
}
  • The cluster, without its manifests

    The draft creates one SKE cluster with one node pool. Deployments, Services, Ingress and Secrets for the workload are not generated; the comment says the image, CPU, memory, environment, probes, scaling rules and registry credentials are not set anywhere in the draft.

  • Values only you can give

    The machine type and the node limits have no default. The maximum node count must be at least the number of zones, because the SKE API refuses a pool whose maximum is lower; the draft enforces that in a validation.

  • One cluster for all container apps

    If your draft already has an SKE cluster, the comment says the architect may run the workloads there and drop this one.

The validation that protects the node pool

Copied from terraform/variables.tf of the run. It shows how the draft turns a platform limit into a check before anything is applied.

STACKIT Terraform by StackToITterraform/variables.tf
variable "containers_nodes_max" {
  type        = number
  description = "Maximum nodes of the node pool hosting the migrated container workloads."
  validation {
    condition     = var.containers_nodes_max >= length(var.availability_zones)
    error_message = "At least the number of zones in var.availability_zones: the SKE API refuses a node pool whose maximum node count is below the number of its zones (\"maximum node count should be greater than or equal to the number of zones specified for this pool\"). Set the maximum to at least that number, or list fewer zones."
  }
}

How the four units were classified

Azure resourceSTACKIT target and classWhat the run says
Linux web app, Node.js code (portal)Cloud Foundry, REPLATFORMcf push builds the app with a buildpack. App settings become environment variables and connection strings become service bindings. Containerising it for SKE is the alternative.
Linux web app, container image (legacy)SKE workload or Cloud Foundry Docker app, CONTAINERIZEThe image already exists, so it moves as a Deployment, or as a Cloud Foundry app from the image. App settings become environment variables and secrets.
Linux function app, Python (jobs)Cloud Foundry, CONTAINERIZESTACKIT has no general-availability function-as-a-service. Triggers and bindings to Azure services are rewritten against the migrated services. The run notes that Consumption-plan scale-to-zero and per-execution billing go away; this demo function already runs on the shared P1v3 plan, so it never had them.
Storage account behind the function appImplicit, RECONFIGUREIt only backs the Functions host (triggers, locks, deployment packages) and goes away with it. Check that no application data is stored in it.
App Service plan, resource groupNo resource, reviewed as supporting settingsThe run reviews the plan's location, operating system and resource group as supporting settings, and the location of the resource group stays open for a person; the plan itself has no STACKIT counterpart.

Classes as the run decided them, from assessment.json. Confidence is MEDIUM for all four units, because the provider version is not pinned by a lock file.

Choosing the target: Cloud Foundry or SKE

The engine proposes one target per app. The choice between the two platforms is yours, and it depends on the app, not on the Azure service it came from.

TargetFits whenTrade-off
Cloud FoundryThe app is source code in a language a buildpack supports, has few moving parts and talks to managed services.You adopt cf push, manifests and service bindings. The STACKIT page names Java, Node.js, Python and Ruby only as examples, so check your exact runtime and version.
SKE with containersThe app already ships as an image, needs sidecars or custom runtimes, or runs next to other Kubernetes workloads.You write and maintain Kubernetes manifests, ingress and secrets. The control plane is managed; the workload design is yours.
Function app as Cloud Foundry app or containerTimers and queue triggers whose logic is plain code; the trigger can be replaced by a scheduler or a consumer.The Azure Functions programming model, bindings and scale-to-zero billing have no equivalent; each trigger is rewritten.

How to decide for each app

  1. Start from the hosting plan

    On Azure, the function hosting plan tells you how the app behaves: Flex Consumption, Premium, Dedicated, Container Apps, or the legacy Consumption plan. An app on a Dedicated plan already runs on App Service infrastructure at fixed cost; an app that depended on scale-to-zero loses that property.

  2. Look for a container image

    App Service can deploy custom containers. If the app already is an image, SKE or a Cloud Foundry Docker app avoids a rebuild; if it is code, Cloud Foundry is the shorter path.

  3. List the triggers and bindings

    The run says the Terraform does not show them. Read host.json and function.json and write a table: trigger, source service, replacement on STACKIT, for example RabbitMQ for a queue.

  4. Move identity and secrets explicitly

    Managed identity and Key Vault references do not carry over. Each use becomes explicit credentials, which is a code and configuration change.

  5. Pick the order

    The run puts both web apps in wave 1 and the function app in wave 2, after its storage account. Start with the app that has the fewest bindings.

What no setting carries over

  • Deployment slots

    Slots are part of the App Service model; Cloud Foundry and SKE use their own deployment strategies, so plan the release process again.

  • Always on, plan size, scale rules

    The plan size and rules do not map. Size the Cloud Foundry quota or the SKE node pool from measured load.

  • Registry credentials

    For the container app the run opens the registry credentials as a review item: the user and password, or the managed identity, become cf push --docker-username for Cloud Foundry or an image pull secret on SKE, pointing at the migrated registry.

  • Authentication and logs

    Built-in authentication, log settings and backups of the web app are not generated and are rebuilt in the application or the platform.

Questions application teams ask

Does STACKIT have a function-as-a-service?

The run states that STACKIT has no general-availability function-as-a-service. A function app runs as a Cloud Foundry app or as a container on SKE, and its triggers and bindings are rewritten.

Should a web app go to Cloud Foundry or to SKE?

A code deployment maps to Cloud Foundry, a container image to SKE or to a Cloud Foundry Docker app. The table above lists when each fits. The draft only creates the organization or the cluster; the apps are deployed afterwards.

Why is the function app CONTAINERIZE and not REPLATFORM?

The run's reason is that the function app runs as a Cloud Foundry app or as a container on SKE, because the Azure Functions host itself runs in a container. Its target names the Cloud Foundry organization, the same one the code web app gets as REPLATFORM, so read the class as the packaging work, not as a different platform. If you choose SKE instead, the function is built into an image.

What do I do with the App Service plan?

Nothing is created for it. Use the plan size and the instance count as input when you size Cloud Foundry quota or the SKE node pool, and treat its location and resource group as settings to decide.

Sources

Your next move: STACKIT.

Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.