This sample demonstrates a Python Flask single-page web application called Vacation Planner hosted on an Azure Web App. The app runs on an Azure App Service Plan and stores activity data in an activities table within the sampledb database on an Azure SQL Database instance. The connection string of the SQL database is stored as a secret in Azure Key Vault. The application also retrieves its certificate from Key Vault to serve traffic over HTTPS.
The following diagram illustrates the architecture of the solution:
- Azure Web App: Hosts the Python Flask application
- Azure App Service Plan: Provides compute resources for the web app
- Azure SQL Database: Stores activity data in a relational table
- Azure Key Vault: Stores the database connection string and the certificate used to secure HTTPS traffic
- Azure Subscription
- Azure CLI
- Python
- Flask
- pyodbc
- Bicep extension, if you plan to install the sample via Bicep.
- Terraform, if you plan to install the sample via Terraform.
The Vacation Planner Web App supports two common approaches for accessing Azure SQL Database securely:
- Using a SQL Database connection string: Provide a standard SQL connection string using the following environment variables:
- SQL_SERVER: The SQL server container IP address (e.g. 172.17.0.4).
- SQL_DATABASE: The name of the SQL database (e.g.PlannerDB).
- SQL_USERNAME: The username to connect to the SQL database (e.g. testuser).
- SQL_PASSWORD: The password to connect to the SQL database (e.g. TestP@ssw0rd123).
- Using Microsoft Entra ID service principal credentials: Specify the service principal credentials in your environment using the following environment variables:
- AZURE_CLIENT_ID: The service principal's client ID.
- AZURE_CLIENT_SECRET: One of the service principal's client secrets.
- AZURE_TENANT_ID: The Microsoft Entra Tenant ID.
This flexibility allows the app to run securely in Azure or in emulated environments like LocalStack for Azure. The client code supports both authentication modes using ClientSecretCredential or DefaultAzureCredential from the Azure SDK.
The application integrates with Azure Key Vault for managing secrets and certificates:
Secrets: The SQL connection string is stored as a secret in Key Vault. At runtime, the app retrieves it using the Azure Key Vault Secrets SDK. This is configured via the KEY_VAULT_NAME and SECRET_NAME environment variables.
Certificates: A self-signed certificate is created in Key Vault during deployment. The app exposes a GET /api/certificate endpoint that retrieves the certificate using the Azure Key Vault Certificates SDK and returns its name, confirming the integration works. This is configured via the KEYVAULT_URI and CERT_NAME environment variables.
Set up the Azure emulator using the LocalStack for Azure Docker image. Before starting, ensure you have a valid LOCALSTACK_AUTH_TOKEN to access the Azure emulator. Refer to the Auth Token guide to obtain your Auth Token and set it in the LOCALSTACK_AUTH_TOKEN environment variable. The Azure Docker image is available on the LocalStack Docker Hub. To pull the image, execute:
docker pull localstack/localstack-azure-alphaStart the LocalStack Azure emulator by running:
# Set the authentication token
export LOCALSTACK_AUTH_TOKEN=<your_auth_token>
# Start the LocalStack Azure emulator
IMAGE_NAME=localstack/localstack-azure-alpha localstack start -d
localstack wait -t 60
# Route all Azure CLI calls to the LocalStack Azure emulator
azlocal start-interceptionDeploy the application to LocalStack for Azure using one of these methods:
All deployment methods have been fully tested against Azure and the LocalStack for Azure local emulator.
Note
When you deploy the application to LocalStack for Azure for the first time, the initialization process involves downloading and building Docker images. This is a one-time operation—subsequent deployments will be significantly faster. Depending on your internet connection and system resources, this initial setup may take several minutes.
- Retrieve the port published and mapped to port 80 by the Docker container hosting the emulated Web App.
- Open a web browser and navigate to
http://localhost:<published-port>. - If the deployment was successful, you will see the following user interface for adding and removing activities:
You can use the call-web-app.sh Bash script below to call the web app. The script demonstrates three methods for calling web apps:
- Through the LocalStack for Azure emulator: Call the web app via the emulator using its default host name. The emulator acts as a proxy to the web app.
- Via localhost and host port mapped to the container's port: Use
127.0.0.1with the host port mapped to the container's port80. - Via container IP address: Use the app container's IP address on port
80. This technique is only available when accessing the web app from the Docker host machine. - Via Runtime Gateway: Use the
{web_app_name}website.localhost.localstack.cloud:4566URL to call the web app via the LocalStack runtime gateway.
#!/bin/bash
get_docker_container_name_by_prefix() {
local app_prefix="$1"
local container_name
# Check if Docker is running
if ! docker info >/dev/null 2>&1; then
echo "Error: Docker is not running" >&2
return 1
fi
echo "Looking for containers with names starting with [$app_prefix]..." >&2
# Find the container using grep
container_name=$(docker ps --format "{{.Names}}" | grep "^${app_prefix}" | head -1)
if [ -z "$container_name" ]; then
echo "Error: No running container found with name starting with [$app_prefix]" >&2
return 1
fi
echo "Found matching container [$container_name]" >&2
echo "$container_name"
}
get_docker_container_ip_address_by_name() {
local container_name="$1"
local ip_address
if [ -z "$container_name" ]; then
echo "Error: Container name is required" >&2
return 1
fi
# Get IP address
ip_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_name")
if [ -z "$ip_address" ]; then
echo "Error: Container [$container_name] has no IP address assigned" >&2
return 1
fi
echo "$ip_address"
}
get_docker_container_port_mapping() {
local container_name="$1"
local container_port="$2"
local host_port
if [ -z "$container_name" ] || [ -z "$container_port" ]; then
echo "Error: Container name and container port are required" >&2
return 1
fi
# Get host port mapping
host_port=$(docker inspect -f "{{(index (index .NetworkSettings.Ports \"${container_port}/tcp\") 0).HostPort}}" "$container_name")
if [ -z "$host_port" ]; then
echo "Error: No host port mapping found for container [$container_name] port [$container_port]" >&2
return 1
fi
echo "$host_port"
}
call_web_app() {
# Get the web app name
echo "Getting web app name..."
web_app_name=$(azlocal webapp list --query '[0].name' --output tsv)
if [ -n "$web_app_name" ]; then
echo "Web app [$web_app_name] successfully retrieved."
else
echo "Error: No web app found"
exit 1
fi
# Get the resource group name
echo "Getting resource group name for web app [$web_app_name]..."
resource_group_name=$(azlocal webapp list --query '[0].resourceGroup' --output tsv)
if [ -n "$resource_group_name" ]; then
echo "Resource group [$resource_group_name] successfully retrieved."
else
echo "Error: No resource group found for web app [$web_app_name]"
exit 1
fi
# Get the the default host name of the web app
echo "Getting the default host name of the web app [$web_app_name]..."
app_host_name=$(azlocal webapp show \
--name "$web_app_name" \
--resource-group "$resource_group_name" \
--query 'defaultHostName' \
--output tsv)
if [ -n "$app_host_name" ]; then
echo "Web app default host name [$app_host_name] successfully retrieved."
else
echo "Error: No web app default host name found"
exit 1
fi
# Get the Docker container name
echo "Finding container name with prefix [ls-$web_app_name]..."
container_name=$(get_docker_container_name_by_prefix "ls-$web_app_name")
if [ $? -eq 0 ] && [ -n "$container_name" ]; then
echo "Container [$container_name] found successfully"
else
echo "Failed to get container name"
exit 1
fi
# Get the container IP address
echo "Getting IP address for container [$container_name]..."
container_ip=$(get_docker_container_ip_address_by_name "$container_name")
if [ $? -eq 0 ] && [ -n "$container_ip" ]; then
echo "IP address [$container_ip] retrieved successfully for container [$container_name]"
else
echo "Failed to get container IP address"
exit 1
fi
# Get the mapped host port for web app HTTP trigger (internal port 80)
echo "Getting the host port mapped to internal port 80 in container [$container_name]..."
host_port=$(get_docker_container_port_mapping "$container_name" "80")
if [ $? -eq 0 ] && [ -n "$host_port" ]; then
echo "Mapped host port [$host_port] retrieved successfully for container [$container_name]"
else
echo "Failed to get mapped host port for container [$container_name]"
exit 1
fi
# Retrieve LocalStack proxy port
proxy_port=$(curl http://localhost:4566/_localstack/proxy -s | jq '.proxy_port')
if [ -n "$proxy_port" ]; then
# Call the web app via emulator proxy
echo "Calling web app [$web_app_name] via emulator..."
curl --proxy "http://localhost:$proxy_port/" -s "http://$app_host_name/" 1> /dev/null
if [ $? == 0 ]; then
echo "Web app call via emulator proxy port [$proxy_port] succeeded."
else
echo "Web app call via emulator proxy port [$proxy_port] failed."
fi
else
echo "Failed to retrieve LocalStack proxy port"
fi
if [ -n "$container_ip" ]; then
# Call the web app via the container IP address
echo "Calling web app [$web_app_name] via container IP address [$container_ip]..."
curl -s "http://$container_ip/" 1> /dev/null
if [ $? == 0 ]; then
echo "Web app call via container IP address [$container_ip] succeeded."
else
echo "Web app call via container IP address [$container_ip] failed."
fi
else
echo "Failed to retrieve container IP address"
fi
if [ -n "$host_port" ]; then
# Call the web app via the host port
echo "Calling web app [$web_app_name] via host port [$host_port]..."
curl -s "http://127.0.0.1:$host_port/" 1> /dev/null
if [ $? == 0 ]; then
echo "Web app call via host port [$host_port] succeeded."
else
echo "Web app call via host port [$host_port] failed."
fi
else
echo "Failed to retrieve host port"
fi
gateway_port=4566
if [ -n "$gateway_port" ]; then
# Call the web app via the runtime gateway
echo "Calling web app [$web_app_name] via runtime gateway on port [$gateway_port]..."
curl -s "http://${web_app_name}website.localhost.localstack.cloud:$gateway_port/" 1> /dev/null
if [ $? == 0 ]; then
echo "Web app call via runtime gateway on port [$gateway_port] succeeded."
else
echo "Web app call via runtime gateway on port [$gateway_port] failed."
fi
else
echo "Failed to retrieve runtime gateway port"
fi
}
call_web_appYou can use SQL Server Management Studio to explore and manage your SQL databases. When connecting, use SQL Server Authentication and specify 127.0.0.1,port as the server name in the Connect dialog box, where port is the host port mapped to the container's internal SQL Server port 1433, as shown in the following picture:
SQL Server Management Studio allows you to manage database objects such as tables and stored procedures, as well as query data, as shown in the following picture:
Alternatively, you can use the sqlcmd to interact with and administer your SQL databases, as shown in the following table:
~$ sqlcmd -S 172.17.0.4 -d PlannerDB -U testuser -P TestP@ssw0rd123
1> SELECT id, username, SUBSTRING(activity, 1, 20) FROM Activities;
2> GO
id username
------------------------------------ -------------------------------- --------------------
e444433e-f36b-1410-88e7-0034efb7413b paolo Go to Paris
e644433e-f36b-1410-88e7-0034efb7413b paolo Go to London
e844433e-f36b-1410-88e7-0034efb7413b paolo Go to Mexico
(3 rows affected)


