App Service & Functions
PaaS web hosting and serverless code.
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.
# 1. The plan — the compute you actually pay foraz 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 planaz 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 bootaz 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.
# Create a staging slot, cloning production's configurationaz 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 swapsaz 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 onlyaz 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 checkaz 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
--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.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 profile on the PLAN (serverfarm), floor 2, ceiling 10az 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 minutesaz 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 flappingaz monitor autoscale rule create -g rg-web \--autoscale-name autoscale-web \--condition "CpuPercentage < 30 avg 10m" \--scale in 1 --cooldown 10az 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.
# Flex Consumption isn't in every region yet — check firstaz functionapp list-flexconsumption-locations -o table# Functions require a storage account for runtime stateaz storage account create -g rg-web -n stcontosofn01 \-l westeurope --sku Standard_LRS# Flex Consumption: scale-to-zero, pay only while code runsaz 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.
RG=rg-lab-appaz group create -n $RG -l eastusaz appservice plan create -g $RG -n plan-lab --sku B1 --is-linuxaz 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
$ 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.
--slot-settings flag, or the 'Deployment slot setting' checkbox in the portal, ties a value to its slot so it never travels.