App Services
Introduction
Section titled “Introduction”Azure App Service is a fully managed platform for hosting web applications, mobile backends, and RESTful APIs without infrastructure overhead. It supports multiple runtimes including .NET, Java, Node.js, Python, and PHP on both Windows and Linux, or as custom containers. For more information, see the App Service overview.
LocalStack for Azure provides a local environment for building and testing applications that make use of Azure App Services. The supported APIs are available on our API Coverage section, which provides information on the extent of Web App’s integration with LocalStack.
Getting started
Section titled “Getting started”This guide is designed for users new to Web App and assumes basic knowledge of the Azure CLI and our lstk az proxy.
Launch LocalStack using your preferred method. For more information, see Introduction to LocalStack for Azure. Once the container is running, enable Azure CLI interception by running:
lstk az start-interceptionThis command points the az CLI away from the public Azure management REST API and toward the LocalStack for Azure emulator API.
To revert this configuration, run:
lstk az stop-interceptionThis reconfigures the az CLI to send commands to the official Azure management REST API.
Create a resource group
Section titled “Create a resource group”Create a resource group for your Web App resources:
az group create \ --name rg-web-demo \ --location westeurope{ "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-web-demo", "location": "westeurope", "name": "rg-web-demo", "properties": { "provisioningState": "Succeeded" }, ...}Create an App Service plan
Section titled “Create an App Service plan”Create a Linux App Service plan on the Standard tier, the lowest tier that supports deployment slots:
az appservice plan create \ --name asp-web-doc89 \ --resource-group rg-web-demo \ --location westeurope \ --sku S1 \ --is-linux{ "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-web-demo/providers/Microsoft.Web/serverfarms/asp-web-doc89", "kind": "linux", "location": "westeurope", "name": "asp-web-doc89", "provisioningState": "Succeeded", "reserved": true, "sku": { "capacity": 1, "family": "S", "name": "S1", "size": "S1", "tier": "Standard" }, ...}Create and inspect a Web App
Section titled “Create and inspect a Web App”Create a web app using a Python runtime:
az webapp create \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --plan asp-web-doc89 \ --runtime "PYTHON:3.11"{ "defaultHostName": "ls-web-doc89.azurewebsites.azure.localhost.localstack.cloud:4566", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-web-demo/providers/Microsoft.Web/sites/ls-web-doc89", "kind": "app,linux", "name": "ls-web-doc89", "state": "Running", "type": "Microsoft.Web/sites", ...}Get the web app:
az webapp show \ --name ls-web-doc89 \ --resource-group rg-web-demoRead and update web app configuration
Section titled “Read and update web app configuration”Read the current web app configuration:
az webapp config show \ --name ls-web-doc89 \ --resource-group rg-web-demo{ "alwaysOn": true, "ftpsState": "FtpsOnly", "http20Enabled": false, "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-web-demo/providers/Microsoft.Web/sites/ls-web-doc89/config/web", "linuxFxVersion": "PYTHON|3.11", "minTlsVersion": "1.2", ...}Update the web app configuration:
az webapp config set \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --always-on false \ --http20-enabled true{ "alwaysOn": false, "http20Enabled": true, "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-web-demo/providers/Microsoft.Web/sites/ls-web-doc89", "linuxFxVersion": "PYTHON|3.11", ...}Configure application settings
Section titled “Configure application settings”Set an application setting that instructs the SCM endpoint to build the application during deployment:
az webapp config appsettings set \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --settings SCM_DO_BUILD_DURING_DEPLOYMENT="true"The command hides the values it sets:
[ { "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", "slotSetting": false, "value": null }]Verify the setting was applied:
az webapp config appsettings list \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --query "[?name=='SCM_DO_BUILD_DURING_DEPLOYMENT']"[ { "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", "slotSetting": false, "value": "true" }]Configure a connection string
Section titled “Configure a connection string”Add a connection string. The app reads it from the SQLAZURECONNSTR_AppDb environment variable:
az webapp config connection-string set \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --connection-string-type SQLAzure \ --settings AppDb="Server=tcp:sqlsrvdoc89.database.windows.net,1433;Database=sqldbdoc89;Authentication=Active Directory Default;"{ "AppDb": { "type": "SQLAzure", "value": null }}List the connection strings:
az webapp config connection-string list \ --name ls-web-doc89 \ --resource-group rg-web-demo[ { "name": "AppDb", "slotSetting": false, "type": "SQLAzure", "value": "Server=tcp:sqlsrvdoc89.database.windows.net,1433;Database=sqldbdoc89;Authentication=Active Directory Default;" }]Deploy a Python application
Section titled “Deploy a Python application”Create a minimal Flask application and package it as a zip archive for deployment:
mkdir python_webapp && cd python_webappcat > app.py << 'EOF'import jsonfrom flask import Flaskapp = Flask(__name__)@app.route("/")def index(): return json.dumps({"status": "Great Success!"})if __name__ == "__main__": app.run()EOFcat > requirements.txt << 'EOF'Flask==3.0.3gunicorn==23.0.0Werkzeug==3.0.4EOFzip -r ../python_webapp.zip .cd ..Deploy the zip package to the web app. With --async true, the command uploads the package and then polls the deployment until the app runs the new code:
az webapp deploy \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --src-path python_webapp.zip \ --type zip \ --async true{ "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-web-demo/providers/Microsoft.Web/sites/ls-web-doc89/deploymentStatus/ccda313f-edbb-4c1f-89c7-fcfa16880a87", "name": "ccda313f-edbb-4c1f-89c7-fcfa16880a87", "properties": { "numberOfInstancesFailed": 0, "numberOfInstancesInProgress": 0, "numberOfInstancesSuccessful": 1, "status": "RuntimeSuccessful", ... }, "type": "Microsoft.Web/sites/deploymentStatus", ...}Verify that the web app is in the Running state:
az webapp show \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --query "state" \ --output tsvRunningInvoke the web app
Section titled “Invoke the web app”Read the web app’s host name and send it a request:
export WEB_APP_HOST=$(az webapp show --name ls-web-doc89 --resource-group rg-web-demo --query defaultHostName --output tsv)curl "http://$WEB_APP_HOST/"{"status": "Great Success!"}Stop, start, and restart the web app
Section titled “Stop, start, and restart the web app”Stop the web app and check its state:
az webapp stop \ --name ls-web-doc89 \ --resource-group rg-web-demoaz webapp show \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --query "state" \ --output tsvStoppedA stopped web app answers 403:
curl --silent --output /dev/null --write-out "%{http_code}\n" "http://$WEB_APP_HOST/"403Start the web app again, and it serves its code:
az webapp start \ --name ls-web-doc89 \ --resource-group rg-web-democurl "http://$WEB_APP_HOST/"{"status": "Great Success!"}On Azure, the first requests after a start can answer an “unavailable” page for a minute or two while the app starts. The emulator holds the request until the app answers.
Restart the web app:
az webapp restart \ --name ls-web-doc89 \ --resource-group rg-web-demoDeploy to a staging slot and swap
Section titled “Deploy to a staging slot and swap”Create a staging slot that copies the production configuration:
az webapp deployment slot create \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --slot staging \ --configuration-source ls-web-doc89{ "defaultHostName": "ls-web-doc89-staging.azurewebsites.azure.localhost.localstack.cloud:4566", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-web-demo/providers/Microsoft.Web/sites/ls-web-doc89/slots/staging", "kind": "app,linux", "name": "staging", "repositorySiteName": "ls-web-doc89", "state": "Running", "type": "Microsoft.Web/sites/slots", ...}Create a second version of the application:
mkdir python_webapp_v2 && cd python_webapp_v2cat > app.py << 'EOF'import jsonfrom flask import Flaskapp = Flask(__name__)@app.route("/")def index(): return json.dumps({"status": "Great Success!", "version": 2})if __name__ == "__main__": app.run()EOFcp ../python_webapp/requirements.txt .zip -r ../python_webapp_v2.zip .cd ..Deploy it to the staging slot only:
az webapp deploy \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --slot staging \ --src-path python_webapp_v2.zip \ --type zip \ --async trueThe staging slot serves the new version on its own host name, while production still serves the first one:
export STAGING_HOST=$(az webapp show --name ls-web-doc89 --resource-group rg-web-demo --slot staging --query defaultHostName --output tsv)curl "http://$STAGING_HOST/"curl "http://$WEB_APP_HOST/"{"status": "Great Success!", "version": 2}{"status": "Great Success!"}Swap the staging slot into production:
az webapp deployment slot swap \ --name ls-web-doc89 \ --resource-group rg-web-demo \ --slot staging \ --target-slot productionProduction now serves the new version, and the staging slot the previous one:
curl "http://$WEB_APP_HOST/"curl "http://$STAGING_HOST/"{"status": "Great Success!", "version": 2}{"status": "Great Success!"}Delete and verify
Section titled “Delete and verify”Delete the web app, together with its slot, and verify it no longer appears:
az webapp delete \ --name ls-web-doc89 \ --resource-group rg-web-demoaz webapp list --resource-group rg-web-demo[]Features
Section titled “Features”The App Service emulator supports the following features:
- App Service plans: Create, get, update, list, and delete plans. A plan stores the SKU it was given and reports the tier, family, and worker limits of that SKU. Deleting a plan that still hosts apps fails with a conflict, as on Azure.
- Web apps: Create, get, update (PUT and PATCH), list, and delete web apps. A repeated PUT, such as
az webapp update, a secondterraform apply, or a Bicep redeployment, updates the app in place and keeps its deployment and its container. - Global host names: Site names are unique across subscriptions, as on Azure. A taken name fails with a conflict, and
checkNameAvailabilityreports it. - Code deployment:
az webapp deploy, Kudu’s zip deploy, and OneDeploy from a package URL (az webapp deploy --src-url, Bicep’sonedeployextension) build the code with Oryx and run it in a container of its own. Python, Node.js, .NET, and Java SE apps run. - Custom containers: A site can run a container image, for example from Azure Container Registry. The app is served from
WEBSITES_PORT, the image’s ownPORT, or port 80, and, as on Azure, from port 8080 when nothing else is set. - Zero-downtime redeployment: The previous container serves until the new one answers. A build that cannot run or is stopped at its time limit, or a container that fails to start, leaves the previous code serving; a build that fails while it runs is covered under Limitations.
- Stop, start, and restart: A stopped app answers
403, and a started one serves its code again. - Application settings, connection strings, and metadata: Set, list, and replace them. They reach the container as environment variables, connection strings under their documented prefixes such as
SQLAZURECONNSTR_, together with App Service’s platform variables such asWEBSITE_SITE_NAMEandWEBSITE_HOSTNAME. A change restarts the container, as on Azure. - Site configuration: The full site configuration is stored and returned, including the startup command (
appCommandLine), which reaches the container. - Deployment slots: Create, deploy to, and swap slots. Each slot runs its own container on its own host name (
<app>-<slot>), with its own configuration, identity, and sticky settings. A swap warms the source slot with the target’s settings first and fails without moving anything when the source does not start. The per-tier slot limits are Azure’s. - Kudu (SCM) endpoints: Deployment history (newest first), deployment status and logs,
/api/settings, log streaming foraz webapp log tail, and the log archive foraz webapp log download. - Function apps: Functions, function keys, and trigger sync. See Function Apps.
- Publishing credentials: Publishing credentials and the publish profile, which names the emulator’s hosts.
- Managed identity: System-assigned and user-assigned managed identities for web apps and slots.
- Stored configuration: Diagnostic log settings, Azure Storage mounts, authentication settings (v1 and v2), publishing credential policies, access restrictions, regional VNet integration, host-name bindings, and the backup configuration are stored and returned. See the limitations for which of them take effect.
- Catalog operations: Web app and function app runtime stacks per location, geographic regions, instance identifiers, and container logs.
Limitations
Section titled “Limitations”- Keys, access restrictions, authentication settings, and publishing credential policies are not enforced: They are stored and returned, and every request is allowed.
httpsOnlyis not enforced: The app is served over HTTP, where Azure redirects to HTTPS.- Custom host names are never routed: Host-name bindings are stored and listed, but the app answers on its default host names only, because the emulator routes requests by host name before they reach App Service, and a routed binding could capture another endpoint.
- VNet integration has no network effect: Regional VNet integration is stored and returned. Gateway-required VNet integration, hybrid connections, and private access are not implemented.
- Backups never run: The backup configuration is stored and returned. Backup and restore operations are not implemented.
- TLS certificates are not implemented: Certificate provisioning and binding are not available.
- Swap options: Traffic routing, swap with preview, and auto swap are not implemented.
- One instance per app: The plan’s worker count is stored, and the emulator does not scale an app out.
- Code builds need an amd64 host: The Oryx build image is published for amd64 only, so on an arm64 host, such as a Mac with Apple silicon, the build of a code deployment fails and the app starts without its dependencies. Custom container images are not affected.
- Windows apps run on Linux: A web app on a Windows plan is created and stored, and its code runs in the Linux runtime.
- Tomcat and JBoss EAP apps do not run: The app is created, and a WAR or JAR deployment to it fails.
- Builds are bounded: An Oryx build is stopped after 30 minutes. A build that fails still starts its output, so the app runs without what the build was to install, and the deployment log reports
BUILD_FAILED. - Run-from-package and MSDeploy are not supported: The package is extracted and built.
- OneDeploy options: The
path,clean,restart, andignorestackoptions are not read, and only zip, WAR, and JAR packages are accepted. A deployment from a URL to a slot fails with404, and a package URL whose SAS is signed with an account key is refused. - FTP is not served: The publish profile keeps its FTP entries, and FTP clients fail to connect. Use
az webapp deployinstead. WEBSITES_PORTreaches the container: Azure does not pass it to the app, while the emulator does.- The deployment history keeps 20 records: Older records are removed.
- No data persistence across restarts: App Service state is held in memory and is lost when the emulator stops. A restored state brings back the apps but not their containers or code, so a restored app answers its default page until you deploy it again.
Configuration
Section titled “Configuration”The behavior of the App Service emulator can be customized using the following environment variables:
| Variable | Default | Type | Description |
|---|---|---|---|
LS_AZURE_ALLOW_MULTIPLE_ORYX_DEPLOYMENTS |
"0" |
Boolean | Keep the containers of earlier deployments running instead of removing them when a new one serves. |
LS_AZURE_CONTAINER_CREATION_RETRIES |
"5" |
Integer | Number of attempts to create a container if the first attempt fails. |
LS_AZURE_DOCKER_PULL_TIMEOUT |
"120" |
Integer (seconds) | Time a request waits for an image pull before the site’s start limit applies. |
LS_AZURE_PORT_RESERVATION_DURATION |
"120" |
Integer (seconds) | Time a host port stays reserved while a container starts. |
LS_AZURE_ORYX_BUILD_IMAGE_TO_USE |
mcr.microsoft.com/oryx/build:github-actions-debian-bookworm-20260415.1 |
String | Oryx build image used to build deployed code. |
LS_AZURE_USE_LATEST_ORYX_BUILD_IMAGE |
"0" |
Boolean | Pull and use the latest Oryx build image instead. |
LS_AZURE_FUNCTIONS_CORE_TOOLS_VERSION |
"4.15.1" ("4.13.0" on arm64) |
String | Release of Azure Functions Core Tools that function app containers run. |
LS_AZURE_FUNCTION_HOST_STARTUP_TIMEOUT |
"360" |
Integer (seconds) | Time a function app deployment waits for the function host to report Running. |
The WEBSITES_CONTAINER_START_TIME_LIMIT app setting (230 seconds by default) bounds how long a request waits for a site’s container to start, as on Azure.
Samples
Section titled “Samples”The following samples demonstrate how to use App Services with LocalStack for Azure:
- Web App and App Configuration (Python)
- Web App and App Configuration (.NET)
- Web App and Cosmos DB for MongoDB API (Python)
- Web App and Cosmos DB for MongoDB API (.NET)
- Web App and Cosmos DB for NoSQL API (Python)
- Web App and Cosmos DB for NoSQL API (.NET)
- Web App with a Custom Docker Image (Python)
- Web App with a Custom Docker Image (.NET)
- Web App and Managed Identities (Python)
- Web App and Managed Identities (.NET)
- Web App and Azure Database for MySQL (Python)
- Web App and Azure Database for MySQL (.NET)
- Web App and Azure Database for PostgreSQL (Python)
- Web App and Azure Database for PostgreSQL (.NET)
- Web App and SQL Database (Python)
- Web App and SQL Database (.NET)
- URL Shortener with Web App, Functions, Storage, Key Vault, Service Bus, and PostgreSQL (Python)
API Coverage
Section titled “API Coverage”143 of 616 operations implemented
| Operation ▲ | Implemented ▼ |
|---|