Background
Sections
IntroductionFoundations1. Resource Hierarchy2. Resource Manager3. Identity and RBAC4. Regions and Availability5. Naming and TaggingVirtual Machines1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetVirtual Network1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetBlob Storage1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure SQL Database1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Kubernetes Service1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Container Registry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetMicrosoft Entra ID1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure RBAC1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Functions1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAPI Management1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure App Configuration1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Machine Learning1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Monitor1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure AI Foundry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and Cheatsheet

7. Production

11 min read

The difference between "it worked in the portal" and "I run this". Five pillars, in the order they usually bite.

The five concerns that separate a working demo from a system you can run

Security

Inbound: stop using function keys as your security model.

The default authLevel: function gives you a shared secret in a query string. It has no identity, no expiry, no per-caller revocation, and it ends up in browser history, proxy logs, and Slack. It is fine for a webhook from a system that can't do anything better; it is not an authentication strategy.

The ladder, in order of preference:

  1. App Service Authentication (Easy Auth) with Microsoft Entra ID — the platform validates a bearer token before your code runs. Per-caller identity, revocable, auditable, no secret in the URL. This is the right answer for most internal APIs.
  2. API Management in front, presenting OAuth outward and holding the function key inward. Right when several teams consume the API and you need throttling and versioning too.
  3. Function keys, restricted to inbound-webhook cases, rotated on a schedule, and stored in Key Vault rather than pasted into the caller's config.
  4. authLevel: anonymous — legitimate only when something in front (Easy Auth, APIM, Front Door with WAF) is doing the authentication. It is a footgun everywhere else.

Outbound: managed identity, always. The function app's system-assigned identity plus a least-privilege data-plane role assignment replaces every connection string you would otherwise hold. The distinction that trips people up in review:

You want Control-plane role Data-plane role
Manage the storage account Contributor
Read blobs ❌ not Contributor Storage Blob Data Reader
Read a Key Vault secret ❌ not Key Vault Contributor Key Vault Secrets User
Receive Service Bus messages ❌ not Contributor Azure Service Bus Data Receiver

Where the built-in role is too broad. Website Contributor on a function app lets the holder list keys — and listing keys is, for a key-protected app, equivalent to full data-plane access. If you need someone to restart apps and read configuration without being able to invoke everything, write a custom role that excludes Microsoft.Web/sites/host/listkeys/action and Microsoft.Web/sites/functions/listkeys/action.

Network isolation.

  • publicNetworkAccess = Disabled plus a private endpoint (sub-resource sites) for inbound.
  • VNet integration for outbound — Flex Consumption, Premium, or Dedicated only. Consumption cannot.
  • Access restrictions (IP or service-tag rules) as the lighter-weight alternative.
  • Do not forget the SCM endpoint. It is a separate sub-resource with separate rules. Leaving it public while locking the main endpoint is a common and serious oversight; locking it without planning breaks your pipeline.

Other hardening worth doing on day one: httpsOnly = true, minimum TLS 1.2, FTPS disabled, shared_access_key_enabled = false on the backing storage account (with the three data roles granted — see Deployment), and basic publishing credentials disabled so nobody can deploy with a username and password.

Encryption. At rest, everything is encrypted with platform-managed keys by default. Customer-managed keys are available for the backing storage account, which is where your Durable state and keys live — consider it when a compliance requirement names CMK specifically, not by default.

Cost

What you actually pay for, by plan:

Plan Compute meter Idle cost
Consumption Executions + GB-seconds (memory × duration), after a monthly free grant ⚠️ verify ~Zero
Flex Consumption Executions + GB-seconds, plus any always-ready instance baseline The baseline you chose
Premium (EP1EP3) Per instance, per hour Never zero — minimum one instance, always
Dedicated The App Service Plan, hourly The whole plan, always

And the meters people forget, which frequently exceed the compute line:

  • Application Insights ingestion. Billed per GB. A chatty app with verbose logging and no sampling can spend more on telemetry than on execution. Set sampling in host.json, set a daily cap on the workspace, and drop Information-level dependency noise you never read.
  • Storage transactions. Polling blob triggers, Durable orchestrations, and Event Hub checkpointing all generate high transaction counts. Durable in particular is transaction-dominated.
  • Data egress, and any private endpoints (billed hourly, plus processing).

The biggest cost trap: an idle Premium plan. It cannot scale to zero, and an EP1 created for a one-week VNet experiment bills every hour for years. Search any mature subscription for Elastic Premium plans with no traffic; you will find one. Azure Policy denying EP* SKUs outside prod is a five-minute control that pays for itself.

Three concrete optimisations:

  1. Right-size the plan, and reconsider it quarterly. The Consumption→Premium jump is often made for one reason (usually VNet access or cold start). Flex Consumption may now solve that reason while keeping scale-to-zero economics — re-evaluate rather than inheriting the decision.
  2. Reduce memory footprint and duration. On GB-seconds pricing these multiply. Lazy-load dependencies, reuse clients, and don't hold large buffers.
  3. Sample telemetry and cap the workspace. Ten minutes of host.json work, frequently the largest single saving.

For steady, predictable, high-volume load, run the arithmetic honestly: at constant throughput, Dedicated or a reserved App Service Plan is often cheaper than per-execution pricing. Serverless is elasticity insurance, and insurance has a premium.

Scaling and limits

The Azure-specific discipline: every limit needs a scope, because a number without one is useless.

Limit Scope it's counted at
Maximum scale-out instances Per function app (varies by plan and OS)
Total instances available Per subscription, per region — shared with other App Service workloads
Plan instance count Per App Service Plan
Execution timeout Per function invocation, ceiling set by plan
Storage transaction rate Per storage account — shared by every app pointed at it
Event-driven parallelism Per partition (Event Hubs, Cosmos DB) or per session (Service Bus)

⚠️ The specific numbers behind each row vary by plan, OS, region, and subscription type — verify against current Azure docs before designing to one.

Hard vs. soft. Instance counts per subscription-per-region are generally soft and raisable via a quota request (az quota, or a support ticket for App Service capacity). The per-plan and per-invocation limits are structural — you change them by changing the plan, not by asking.

The ceiling you'll actually hit is downstream. In practice, function apps rarely exhaust their own instance limit. They exhaust the database's connection pool, the Cosmos container's provisioned RU/s, the third-party API's rate limit, or their own SNAT ports. When you see 429s and timeouts under load, look downstream before you look at the plan.

The controls that matter, in the order to reach for them:

  1. Per-instance concurrency in host.jsonmaxConcurrentRequests for HTTP, maxConcurrentCalls for Service Bus, batchSize for queues. This is your throttle.
  2. A maximum scale-out limit on the app — a hard ceiling on how far the app can grow, which is how you protect a fragile dependency from a traffic spike.
  3. A queue between ingress and work, converting a burst into a drain.
  4. Splitting the app, so a hot function's scaling doesn't drag an unrelated one along.

Observability

Diagnostic settings are not on by default. Neither is anything else. A function app with no Application Insights and no diagnostic setting is a black box, and you will find that out during an incident.

Turn on, at minimum:

  • Application Insights — connected via APPLICATIONINSIGHTS_CONNECTION_STRING, ideally a workspace-based component so the data lands in your central Log Analytics workspace.
  • A diagnostic setting on the app routing FunctionAppLogs and AllMetrics to Log Analytics.
  • Sampling configured deliberately in host.json, so you keep failures and sample successes rather than losing both to a cap.

Metrics worth alerting on:

Metric / signal Why Typical alert shape
Function error rate The obvious one Failures > N over 5 minutes
Invocation duration p95 Catches the slow-dependency drift before it becomes a timeout p95 above your SLO for 10 minutes
Queue depth / dead-letter count (on the source, not the app) The real backlog signal — an app processing nothing looks healthy Dead-letter count > 0; oldest message age > N
HTTP 5xx Availability Any sustained rate
Instance count pinned at maximum You've hit the ceiling and are now queueing Sustained at max
Health check endpoint Distinguishes "no traffic" from "broken" Failed health check

The dead-letter alert deserves emphasis: the most dangerous Functions failure is the silent one, where triggers stop firing and every dashboard on the app itself looks fine because nothing is erroring. Alert on the source's backlog, not just the app's errors.

The KQL query you'll type most — recent failures with their exception, grouped by function:

requests
| where timestamp > ago(1h)
| where success == false
| join kind=leftouter (
    exceptions
    | where timestamp > ago(1h)
    | project operation_Id, exceptionType = type, outerMessage
  ) on operation_Id
| summarize failures = count(), sample = any(outerMessage) by operation_Name, exceptionType
| order by failures desc

And the cold-start question, which is really "how often is a request served by a brand-new instance":

requests
| where timestamp > ago(24h)
| summarize p50 = percentile(duration, 50), p95 = percentile(duration, 95),
            p99 = percentile(duration, 99), count() by operation_Name
| order by p99 desc

A p99 that is tens of times the p50 on a low-traffic HTTP function is the signature of cold start, not of a slow dependency.

The activity log answers a different question — who changed this resource and when. Route it to the same workspace; it is what you will actually query during a "it worked yesterday" investigation.

Reliability

Zone redundancy. A function app is regional. Zone redundancy is a property of the plan and is available on Premium and Flex Consumption in regions that support it ⚠️ verify current availability against current Azure docs. Consumption gives you no zonal guarantee. If a zone outage must not take your workload down, this is a plan decision made at design time, not a switch flipped later.

Multi-region means two function apps in two regions with an active/active or active/passive front. The hard parts are never the compute:

  • State. The backing storage account is regional. Durable orchestrations do not fail over. Design regional independence into the workflow or accept that in-flight orchestrations are lost.
  • Triggers. Two apps both listening to one queue will each take a share — sometimes what you want, sometimes duplicated work. Two apps listening to regional queues is usually cleaner.
  • Routing. Front Door or Traffic Manager in front for HTTP; for event-driven work there is often no routing layer at all and failover is a deployment action.

Backup and restore. There's little to back up in the app itself — it's rebuilt from source and a package, which is the point. What genuinely needs a recovery story:

  • The backing storage account (Durable history, function keys) — enable soft delete for blobs.
  • Key Vault contents — soft delete and purge protection on.
  • Function keys, if any integration depends on a specific key value. Keep them in Key Vault so they survive a storage account recreate.

Idempotency is a reliability control, not a code style preference. At-least-once delivery means your function will run twice for the same event. Carry an idempotency key, make the write conditional, and you have removed an entire class of production incident that no amount of platform configuration can address.

The failure drill worth actually running: disable the trigger source (stop the Service Bus queue, or block the storage account), confirm your alerts fire on backlog rather than on error rate, then restore and confirm the backlog drains without duplicating work. That one exercise tests observability, scaling, and idempotency together, and most teams discover at least one gap.


Next: Interview Questions →

← Back to the Azure Functions overview · ← Previous: Integrations