App Service & Functions

PaaS web hosting and serverless code.

Intermediate30 min · lesson 5 of 15

There are three ways to get around a city, and Azure sells all three. Buy a car and you get total control, but you pay for insurance, repairs and a parking space whether you drive or not. That is a virtual machine (VM), a whole computer you rent and look after. Lease a serviced car and somebody else handles the oil changes and the annual inspection while you do the driving. That is Azure App Service, a *platform as a service* (PaaS) offering: you hand Azure your code, and Azure supplies patched, load-balanced web servers underneath it. Or open a rideshare app, where no car exists for you until the moment you need one and you pay per trip. That is Azure Functions, Azure's *serverless* offering, where your code runs only when something triggers it. The last lesson was about owning the car. This one covers the two models where Azure absorbs the operations work, plus the judgment call that AZ-104 (the Azure Administrator Associate exam) tests over and over: which ride fits which workload.

The plan is the building, your apps are the tenants

Every App Service app lives inside an App Service Plan, a set of managed virtual machines that you size and pay for. The plan is the thing with the meter on it. Ten apps on one plan cost exactly what one app costs, because Azure charges for the plan's tier and instance count, never for how many apps you park on it. The tier (Microsoft calls it the SKU, short for stock-keeping unit, the same word a shop uses for a priced item on a shelf) decides which features you get. Free and Basic are for dev and test. Standard (S1) turns on autoscale and five deployment slots. Premium v3 adds faster hardware, twenty slots, a higher ceiling when you scale out (thirty instances) and availability-zone support. Isolated hands you a dedicated, single-tenant environment with no other customers on the hardware. One detail people misremember: virtual-network integration starts at the Basic tier, not at Premium. Two verbs also get mixed up under exam pressure. *Scale up* changes the tier, so instances get bigger and more features appear, and your app itself does not change. *Scale out* raises the instance count, so more copies of your app sit behind Azure's load balancer. Scaling out only works if your app tolerates running as several copies at once.

create-plan-app-deploy.sh
# 1. The plan — the compute you actually pay for
az appservice plan create -g rg-web -n plan-prod \
--sku P1V3 --is-linux
# {
# "location": "westeurope",
# "provisioningState": "Succeeded",
# "sku": { "capacity": 1, "name": "P1v3", "tier": "PremiumV3" },
# ...
# }
# 2. The app — a tenant on that plan
az webapp create -g rg-web -p plan-prod -n contoso-api \
--runtime "NODE:22-lts"
# "defaultHostName": "contoso-api.azurewebsites.net",
# "state": "Running",
# 3. Ship code as a zip (build first: npm ci && npm run build)
az webapp deploy -g rg-web -n contoso-api \
--src-path release.zip --type zip
# { "complete": true, "site_name": "contoso-api", "status": 4, ... }
# (status 4 = deployment succeeded)
# Watch it boot
az webapp log tail -g rg-web -n contoso-api

One consequence catches teams out constantly. Apps on the same plan share the same instances, so one badly behaved app can eat the processor and memory its neighbours needed. Group apps with similar load profiles and keep the loud ones apart. Remember too that an *empty* plan still bills every hour it exists. Deleting the last app does not delete the plan, and that orphaned plan will be sitting on next month's invoice.

Deployment slots: release by swapping, not by redeploying

A deployment slot is a second, live copy of your app running on the same plan, with its own hostname, its own settings and no extra compute charge. Two identical shop units sit side by side sharing one street sign. You fit out the second unit while customers keep using the first, then you move the sign. The release pattern has that shape: push new code to a *staging* slot, warm it up, smoke-test it against the real production database and the real downstream services, then swap. A swap copies no files. Azure warms the staging workers, then rewrites the routing rules so a different set of workers answers the production hostname. Users see no restart, no cold start, no dropped requests. Rolling back is the same swap run a second time, and it takes seconds. Slots start at the Standard tier. When an exam question says "zero-downtime deployment with instant rollback", it is almost always pointing at slots.

slot-swap-release.sh
# Create a staging slot, cloning production's configuration
az webapp deployment slot create -g rg-web -n contoso-api \
--slot staging --configuration-source contoso-api
# "defaultHostName": "contoso-api-staging.azurewebsites.net",
# Sticky setting: stays with the slot through swaps
az webapp config appsettings set -g rg-web -n contoso-api \
--slot staging --slot-settings DB_HOST=sql-staging.contoso.net
# Ship the new build to staging only
az webapp deploy -g rg-web -n contoso-api --slot staging \
--src-path release-v2.zip --type zip
# Rehearse: applies production's settings to staging for a final check
az webapp deployment slot swap -g rg-web -n contoso-api \
--slot staging --action preview
# Complete it. No output on success — production is now v2.
az webapp deployment slot swap -g rg-web -n contoso-api \
--slot staging --action swap
Your settings ride along with the swap
By default, app settings and connection strings travel *with the code* during a swap. Staging's test database connection string lands in production, and production's real one lands in staging. Anything that must stay where it is has to be marked as a slot setting: --slot-settings on the command line, or the "Deployment slot setting" checkbox in the portal. Rehearse with --action preview first, which applies production's settings to staging so you can check the app comes up clean before you complete the swap. Plenty of engineers learn this the hard way, mid-outage, at 2 a.m. Audit your settings before your first swap instead.
Choosing the compute model: the least operational work that still fits
Which hosting model?
Start with the option that leaves you the least to operate, and move up the ladder only when a requirement forces you to. Every layer Azure manages is a layer you never patch and never get paged about.
Need operating-system or root access, kernel tuning, or heavy long-running work
Virtual Machine
Full control of the box, and full ownership of patching, certificates, load balancing and availability.
A standard website or web service; you want managed, load-balanced servers
App Service (PaaS)
Azure runs the operating system and the web tier. You get slots, autoscale and managed certificates, and you give up root access and kernel tuning.
Event-driven and bursty; you want to pay only while code runs
Azure Functions (serverless)
Code wakes on a trigger and scales back to zero. You accept execution time limits and cold starts.
Each step down the ladder hands Azure more of the operations and leaves you less flexibility in return. Workloads built from many cooperating containers step off this ladder onto Azure Container Instances or Azure Kubernetes Service.

Autoscale belongs to the plan, not to the app

Compute belongs to the plan, so autoscale targets the plan, never an individual app. On the command line the plan's resource type is Microsoft.Web/serverfarms, an old internal name that stuck. Every app on that plan scales together, which is one more reason to be deliberate about who shares a plan with whom. Autoscale itself is an Azure Monitor feature and needs Standard tier or above. It watches one metric over a time window and moves the instance count between a floor and a ceiling you set. Two habits keep it healthy. First, always pair a scale-out rule with a scale-in rule. An out-only configuration ratchets upward and never comes back down, and your bill follows it up. Second, leave a wide gap between the two thresholds. Rules set at 70% and 65% will *flap*, adding and removing instances in a loop while users feel every bit of the churn. In production, set the minimum to two instances so a platform reboot of one never takes you to zero.

autoscale-plan.sh
# Autoscale profile on the PLAN (serverfarm), floor 2, ceiling 10
az monitor autoscale create -g rg-web \
--resource plan-prod --resource-type Microsoft.Web/serverfarms \
-n autoscale-web --min-count 2 --max-count 10 --count 2
# Scale OUT: +2 instances when CPU averages >70% over 10 minutes
az monitor autoscale rule create -g rg-web \
--autoscale-name autoscale-web \
--condition "CpuPercentage > 70 avg 10m" \
--scale out 2 --cooldown 5
# Scale IN: -1 below 30% — the 70/30 gap prevents flapping
az monitor autoscale rule create -g rg-web \
--autoscale-name autoscale-web \
--condition "CpuPercentage < 30 avg 10m" \
--scale in 1 --cooldown 10
az monitor autoscale show -g rg-web -n autoscale-web \
--query "profiles[0].capacity"
# { "default": "2", "maximum": "10", "minimum": "2" }

Functions: rent the milliseconds

Azure Functions strips the model down one more notch. There is no always-running app. There is code that wakes when a trigger fires: an HTTP (hypertext transfer protocol, the language browsers speak) request, a timer, a message landing on a queue, a file landing in blob storage. Bindings declare the data your code reads and writes, so a function that pulls from a queue and writes a row to a table carries none of that connection plumbing itself. The real decision is the hosting plan, because it sets both the economics and the limits. Flex Consumption is Microsoft's recommended plan for new serverless apps, now that the classic Consumption plan carries the *legacy* label. It bills for executions plus the memory they use, only while your code is actually running, and it scales all the way to zero. The price of zero is a cold start: a delay of one to several seconds on the first request after an idle spell, while Azure finds and prepares a worker. The classic Consumption plan caps a single execution at ten minutes, with a five-minute default. Flex raises the default to thirty minutes and enforces no maximum. Premium, sized EP1 through EP3, keeps workers pre-warmed so there is no cold start, adds virtual-network access and long executions, and charges a fixed monthly floor for the privilege. You can also host functions on an App Service plan you already pay for, at no extra cost, if it has headroom. One more requirement catches people out: every function app needs a storage account. The runtime keeps trigger checkpoints, access keys and often the deployment package itself in there. Delete it and the app is bricked.

create-function-app.sh
# Flex Consumption isn't in every region yet — check first
az functionapp list-flexconsumption-locations -o table
# Functions require a storage account for runtime state
az storage account create -g rg-web -n stcontosofn01 \
-l westeurope --sku Standard_LRS
# Flex Consumption: scale-to-zero, pay only while code runs
az functionapp create -g rg-web -n contoso-events \
--storage-account stcontosofn01 \
--flexconsumption-location westeurope \
--runtime python --runtime-version 3.11
# "defaultHostName": "contoso-events.azurewebsites.net",
# "state": "Running",
# Publish code from a local project (Azure Functions Core Tools)
func azure functionapp publish contoso-events
# Getting site publishing info...
# Uploading package...
# Deployment completed successfully.
# Functions in contoso-events:
# process-order - [queueTrigger]

Choosing the model: security, scale, cost

The three models form a ladder, and every rung trades control for operational relief. On a VM you get full access to the operating system and full ownership of everything above it: patching, certificates, load balancing, availability. On App Service, Azure owns the operating system and the web tier. You give up root access and kernel tuning, and long-running background work fits awkwardly. On Functions, Azure owns everything except your code, and you accept execution time limits, cold starts and an event-driven shape for the work. An administrator's default should be the *least-operational option that meets the requirement*, because every layer Azure manages is a layer you neither patch nor get paged about. Security pulls the same way. App Service issues and renews managed TLS (transport layer security, the padlock in the browser bar) certificates for you, one setting puts Microsoft Entra ID sign-in in front of the whole app, and Key Vault references let an app setting resolve its value from a vault at run time. Pair that with a managed identity and no secret ever sits in your configuration as plain text. Cost pulls the same way too: consolidate small apps onto shared plans, use slots instead of a duplicate plan for pre-production, and let scale-in rules earn back the twenty minutes you spent writing them.

One gap here is deliberate. Both App Service and Functions will happily run a *container* you hand them (App Service does it natively, Functions only on its Premium or Dedicated plans), but always as a single app, on Azure's terms. Once the workload becomes many cooperating containers, or has to run identically on any cloud, you step off the PaaS ladder into container infrastructure: single containers on Azure Container Instances (ACI), orchestrated fleets on Azure Kubernetes Service (AKS), and images shipped from Azure Container Registry (ACR). That is exactly where the next lesson picks up.

In practice the sizing decision comes down to three names. B1 (Basic) for a dev or test app you do not mind restarting. S1 (Standard) as the first tier with autoscale and staging slots, which is where most production web apps start. P1v3 (Premium v3) when you need more processor and memory per instance, or you want the app spread across availability zones. The web app is the tenant riding on that plan: runtime stack, app settings, deployment credentials, custom domain names. Functions can share the same plan or run on Consumption or Flex Consumption, where idle code costs nothing. Pick App Service when the work is a long-running HTTP process, a website or an API (application programming interface, an address other programs call directly). Pick Functions when the work is triggered, short and bursty.

Most day-two administration is configuration and identity, and the sooner you do it the less it hurts. Force httpsOnly so plain HTTP requests get redirected. Raise the minimum TLS version. Turn on always-on for any plan that is not Consumption, so the platform stops unloading your app between requests. Send diagnostic logs to a Log Analytics workspace where you can query them weeks later. Give the app a managed identity and use it to reach Key Vault and Storage, so no keys sit in the configuration at all. Slots really belong to the deployment story you meet later, but as an administrator you should know two things about them: they exist from Standard upward, and a swap is a routing flip rather than a file copy.

Try this

Build the smallest possible version of all this in a lab subscription: one App Service plan, one web app riding on it, then read back the hostname and the configuration. If you want to feel the difference between the models, add a function app on a Consumption plan afterwards and compare what each one costs you while it sits doing nothing.

terminal
RG=rg-lab-app
az group create -n $RG -l eastus
az appservice plan create -g $RG -n plan-lab --sku B1 --is-linux
az webapp create -g $RG -p plan-lab -n contosolabweb$RANDOM --runtime "NODE:20-lts"
az webapp show -g $RG -n contosolabweb* --query "{state:state,host:defaultHostName,https:httpsOnly}" -o json 2>/dev/null || \
az webapp list -g $RG --query "[0].{name:name,host:defaultHostName,https:httpsOnly}" -o json
output
$ az webapp list -g rg-lab-app --query "[0].{name:name,host:defaultHostName,https:httpsOnly}" -o json
{
"name": "contosolabweb1842",
"host": "contosolabweb1842.azurewebsites.net",
"https": false
}
# Sample output — turn on HTTPS-only next:
# "https": true

Takeaway

App Service rents you a managed web server on a plan you size yourself. Functions rents you the milliseconds your code is awake. Neither one puts you on the hook for patching an operating system, and that is the whole reason to pick them.

Next: turn on HTTPS-only, give the app a managed identity, and pull one secret from Key Vault instead of storing it in app settings as plain text. Those three changes take about ten minutes and clear the findings that come up most often in an App Service review.

Quick check
01You push v2 to a staging slot whose DB_HOST app setting points at a test database, then run the swap. Production immediately starts talking to that test database. What happened, and how do you stop it happening again?
Correct — Settings and connection strings move with the code on a swap unless you pin them. The --slot-settings flag, or the 'Deployment slot setting' checkbox in the portal, ties a value to its slot so it never travels.
Incorrect — A swap copies no files at all. It flips which workers answer the production hostname, which is why it finishes in seconds. The leak is the setting travelling, not the files.
Incorrect — Autoscale only changes how many instances the plan runs. It has no say in which app settings apply to a slot after a swap.
Incorrect — A Key Vault reference is still an app-setting value, and it swaps like any other one unless it is also marked as a slot setting. Sticky settings are what stop the travel.
02In Azure App Service, what is the difference between scaling up and scaling out?
Correct — Scaling up changes the plan's tier, so instances get bigger and more features appear. Scaling out adds more copies of the app behind the load balancer.
Incorrect — and backwards. Adding instances is scaling out; moving to a higher tier is scaling up.
Incorrect — These are two different operations, one about instance size and features and the other about instance count. They are not automatic and manual versions of the same thing.
Incorrect — Neither verb touches the runtime version or the region. Both are about compute size or compute count.
03An internal API fires a few times an hour in bursts nobody can predict, and sits completely idle in between. The team wants to pay only for the seconds their code actually runs, and they are happy to accept a one-to-few-second delay on the first call after an idle spell. Which hosting choice costs them least?
Incorrect — Premium v3 bills a fixed monthly amount for instances that run all the time, so you keep paying right through every idle stretch.
Incorrect — A VM bills for every allocated second, busy or not, which makes it the most expensive option on this list for a bursty, low-volume workload.
Correct — Scale-to-zero, pay-per-execution billing is built for bursty, mostly idle work, and the team has already said the cold start is acceptable.
Incorrect — Functions Premium keeps workers pre-warmed for a fixed monthly floor. It removes the cold start, but it is not the cheapest home for an app that is idle most of the time.

Related