Skip to content
Get Started for Free

App Services

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.

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:

Terminal window
lstk az start-interception

This 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:

Terminal window
lstk az stop-interception

This reconfigures the az CLI to send commands to the official Azure management REST API.

Create a resource group for your Web App resources:

Terminal window
az group create \
--name rg-web-demo \
--location westeurope
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-web-demo",
"location": "westeurope",
"name": "rg-web-demo",
"properties": {
"provisioningState": "Succeeded"
},
...
}

Create a Linux App Service plan on the Standard tier, the lowest tier that supports deployment slots:

Terminal window
az appservice plan create \
--name asp-web-doc89 \
--resource-group rg-web-demo \
--location westeurope \
--sku S1 \
--is-linux
Output
{
"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 a web app using a Python runtime:

Terminal window
az webapp create \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--plan asp-web-doc89 \
--runtime "PYTHON:3.11"
Output
{
"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:

Terminal window
az webapp show \
--name ls-web-doc89 \
--resource-group rg-web-demo

Read the current web app configuration:

Terminal window
az webapp config show \
--name ls-web-doc89 \
--resource-group rg-web-demo
Output
{
"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:

Terminal window
az webapp config set \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--always-on false \
--http20-enabled true
Output
{
"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",
...
}

Set an application setting that instructs the SCM endpoint to build the application during deployment:

Terminal window
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:

Output
[
{
"name": "SCM_DO_BUILD_DURING_DEPLOYMENT",
"slotSetting": false,
"value": null
}
]

Verify the setting was applied:

Terminal window
az webapp config appsettings list \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--query "[?name=='SCM_DO_BUILD_DURING_DEPLOYMENT']"
Output
[
{
"name": "SCM_DO_BUILD_DURING_DEPLOYMENT",
"slotSetting": false,
"value": "true"
}
]

Add a connection string. The app reads it from the SQLAZURECONNSTR_AppDb environment variable:

Terminal window
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;"
Output
{
"AppDb": {
"type": "SQLAzure",
"value": null
}
}

List the connection strings:

Terminal window
az webapp config connection-string list \
--name ls-web-doc89 \
--resource-group rg-web-demo
Output
[
{
"name": "AppDb",
"slotSetting": false,
"type": "SQLAzure",
"value": "Server=tcp:sqlsrvdoc89.database.windows.net,1433;Database=sqldbdoc89;Authentication=Active Directory Default;"
}
]

Create a minimal Flask application and package it as a zip archive for deployment:

Terminal window
mkdir python_webapp && cd python_webapp
cat > app.py << 'EOF'
import json
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return json.dumps({"status": "Great Success!"})
if __name__ == "__main__":
app.run()
EOF
cat > requirements.txt << 'EOF'
Flask==3.0.3
gunicorn==23.0.0
Werkzeug==3.0.4
EOF
zip -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:

Terminal window
az webapp deploy \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--src-path python_webapp.zip \
--type zip \
--async true
Output
{
"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:

Terminal window
az webapp show \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--query "state" \
--output tsv
Output
Running

Read the web app’s host name and send it a request:

Terminal window
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/"
Output
{"status": "Great Success!"}

Stop the web app and check its state:

Terminal window
az webapp stop \
--name ls-web-doc89 \
--resource-group rg-web-demo
az webapp show \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--query "state" \
--output tsv
Output
Stopped

A stopped web app answers 403:

Terminal window
curl --silent --output /dev/null --write-out "%{http_code}\n" "http://$WEB_APP_HOST/"
Output
403

Start the web app again, and it serves its code:

Terminal window
az webapp start \
--name ls-web-doc89 \
--resource-group rg-web-demo
curl "http://$WEB_APP_HOST/"
Output
{"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:

Terminal window
az webapp restart \
--name ls-web-doc89 \
--resource-group rg-web-demo

Create a staging slot that copies the production configuration:

Terminal window
az webapp deployment slot create \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--slot staging \
--configuration-source ls-web-doc89
Output
{
"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:

Terminal window
mkdir python_webapp_v2 && cd python_webapp_v2
cat > app.py << 'EOF'
import json
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return json.dumps({"status": "Great Success!", "version": 2})
if __name__ == "__main__":
app.run()
EOF
cp ../python_webapp/requirements.txt .
zip -r ../python_webapp_v2.zip .
cd ..

Deploy it to the staging slot only:

Terminal window
az webapp deploy \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--slot staging \
--src-path python_webapp_v2.zip \
--type zip \
--async true

The staging slot serves the new version on its own host name, while production still serves the first one:

Terminal window
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/"
Output
{"status": "Great Success!", "version": 2}
{"status": "Great Success!"}

Swap the staging slot into production:

Terminal window
az webapp deployment slot swap \
--name ls-web-doc89 \
--resource-group rg-web-demo \
--slot staging \
--target-slot production

Production now serves the new version, and the staging slot the previous one:

Terminal window
curl "http://$WEB_APP_HOST/"
curl "http://$STAGING_HOST/"
Output
{"status": "Great Success!", "version": 2}
{"status": "Great Success!"}

Delete the web app, together with its slot, and verify it no longer appears:

Terminal window
az webapp delete \
--name ls-web-doc89 \
--resource-group rg-web-demo
az webapp list --resource-group rg-web-demo
Output
[]

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 second terraform 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 checkNameAvailability reports it.
  • Code deployment: az webapp deploy, Kudu’s zip deploy, and OneDeploy from a package URL (az webapp deploy --src-url, Bicep’s onedeploy extension) 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 own PORT, 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 as WEBSITE_SITE_NAME and WEBSITE_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 for az webapp log tail, and the log archive for az 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.
  • Keys, access restrictions, authentication settings, and publishing credential policies are not enforced: They are stored and returned, and every request is allowed.
  • httpsOnly is 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, and ignorestack options are not read, and only zip, WAR, and JAR packages are accepted. A deployment from a URL to a slot fails with 404, 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 deploy instead.
  • WEBSITES_PORT reaches 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.

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.

The following samples demonstrate how to use App Services with LocalStack for Azure:

143 of 616 operations implemented

Operation ▲Implemented ▼
Page 1 of 0
Was this page helpful?