Azure Functions for .NET Developers: Series
- Part 1: Why Azure Functions? Serverless for .NET Developers
- Part 2: Your First Azure Function: HTTP Triggers Step-by-Step
- Part 3: Beyond HTTP: Timer, Queue, and Blob Triggers
- Part 4: Local Development Setup: Tools, Debugging, and Hot Reload ← you are here
You add a breakpoint. You press F5. The function executes. The breakpoint never fires.
This is the most common introduction to Azure Functions local development. The reason is non-obvious: when you debug a .NET isolated worker function, two separate processes run. Your debugger attached to the host process (func.exe) instead of the worker process (dotnet.exe), which is where your code actually executes.
local.settings.json causes a different kind of confusion. Unlike appsettings.json, it injects environment variables: put a connection string in the wrong section and your bindings silently break. Azurite is a related trap: timer, queue, and blob triggers all use Azure Storage internally, so the emulator has to be running before those trigger types will initialize, even if your function code touches no storage directly.
This covers the full local setup for Azure Functions with .NET 10 and the isolated worker model. What's not here: deployment, Application Insights (that's Part 9), or unit testing (Part 7).
What you need: three installs
Three things have to be installed and working before you can run any Azure Function locally.
A. .NET 10 SDK
dotnet --version # expect 10.x.x
dotnet --list-sdks # confirm 10.x is listed
If you followed Parts 1-3, this is already done. If not, download from dot.net.
B. Azure Functions Core Tools v4
Core Tools provides the func CLI that starts the local Functions host. Install it with your package manager:
-
macOS:
brew tap azure/functions && brew install azure-functions-core-tools@4 -
Windows:
winget install Microsoft.Azure.FunctionsCoreToolsor the MSI download (64-bit required for VS Code debugging) - Linux: APT install from Microsoft package feeds (see the official instructions)
-
npm (cross-platform):
npm i -g azure-functions-core-tools@4 --unsafe-perm true
A note on the npm option for Windows: if you are using Microsoft.Azure.Functions.Worker.Sdk 2.x, which enables dotnet run support, the npm Core Tools installation does not work correctly for that workflow. Use the winget or MSI install instead.
func --version # expect 4.x.x
C. Azurite
Azurite emulates Azure Blob, Queue, and Table storage locally. Install it once; you will need it running for every non-HTTP function.
-
VS Code extension (recommended for VS Code users): search "Azurite" in the Extensions panel, or install
Azurite.azuritedirectly -
npm:
npm install -g azurite -
Docker:
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite - Visual Studio 2026: Azurite is bundled and starts automatically when you press F5
The old Azure Storage Emulator is deprecated. If you see references to it in older articles or documentation, ignore them.
Verification
Run this sequence to confirm everything is wired up correctly:
dotnet --version
func --version
# Start Azurite: either the VS Code extension (Command Palette: "Azurite: Start") or `azurite` in a terminal
func init TestProj --worker-runtime dotnet-isolated --target-framework net10.0
cd TestProj
func start
# Should print: "Host lock lease acquired" with no errors
If you see SocketException: Unable to connect to the remote server instead of the lock lease message, Azurite is not running. Start it first, then retry func start.
The two-process model
This is the section most local dev tutorials skip, which is why "my breakpoint won't fire" is such a common question.
In the isolated worker model, two separate processes run every time you start a debug session:
┌──────────────────────────────────────────┐
│ func.exe (the Functions host) │
│ - Manages triggers │
│ - Handles bindings │
│ - Routes HTTP requests │
│ - Runs on port 7071 │
└──────────────────────┬───────────────────┘
│ gRPC
│
┌──────────────────────┴───────────────────┐
│ dotnet.exe (your worker) │
│ - Runs your actual function code │
│ - Gets invoked by the host via gRPC │
│ - This is where breakpoints fire │
└──────────────────────────────────────────┘
func.exe is the Functions runtime: it manages triggers, handles bindings, and routes incoming requests. dotnet.exe is your application: it receives invocation requests from the host over gRPC and runs your actual function code. Breakpoints live in dotnet.exe, not func.exe.
This matters for debugging in two ways. First, your debugger must attach to the worker process (dotnet.exe), not to func.exe. This is why the launch.json the Azure Functions extension generates uses "request": "attach" rather than "request": "launch", and why the process ID is set to ${command:azureFunctions.pickProcess} instead of a static value. Second, the two processes produce separate log streams with separate configuration: host.json controls what func.exe logs, while your ILogger<T> calls are configured on the worker side. Changing one does not affect the other.
For comparison: the deprecated in-process model runs everything in a single process. One process, one debugger attach target. That simplicity is gone with the isolated model, but the isolation is what allows it to run on .NET 10 instead of being locked to .NET 8.
local.settings.json: not config, environment variables
A common mental model for local.settings.json is that it works like appsettings.json. It does not.
Everything in the Values section is read by Core Tools at startup and injected as process environment variables into both func.exe and the worker process. That is the entire job of this file when running locally. In Azure, there is no local.settings.json at all: the App Settings page in the portal (or the equivalent in Bicep/ARM/Terraform) sets those same environment variables directly on the hosted function app.
File structure:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"MyQueueConnection": "UseDevelopmentStorage=true",
"MyServiceBusConnection": "<real-sb-connection-string>"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*",
"CORSCredentials": false
},
"ConnectionStrings": {
"AppDb": "Server=localhost;Database=MyApp;Trusted_Connection=True;"
}
}
Two keys in Values are required for every project:
-
FUNCTIONS_WORKER_RUNTIME: must be"dotnet-isolated"(not"dotnet", which is the in-process value; using it causes confusing startup failures) -
AzureWebJobsStorage: required for every trigger type except HTTP. The host uses this storage account for timer leases, key management, and Durable Functions. Set to"UseDevelopmentStorage=true"when Azurite is running.
The ConnectionStrings section gotcha
ConnectionStrings is for Entity Framework and similar frameworks that call IConfiguration.GetConnectionString("Name"). It is not for Functions trigger and binding configuration. If you put your Service Bus or Queue connection string here, the binding's Connection property won't find it, and the error message won't point you at the right place. All binding connection strings must go in Values.
Why the file is gitignored
Even if your local values only point at Azurite today, a real connection string will appear in this file at some point, whether you add it or a teammate does. Once committed, it lives in git history. The .gitignore and .funcignore that func init generates both exclude local.settings.json from the start.
Sharing the structure with the team
The standard approach: commit a local.settings.json.example file with the same structure but placeholder values. New team members copy it to local.settings.json and fill in the real values.
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"ServiceBusConnection": "<your-service-bus-connection-string>"
}
}
Document in the README which values work with Azurite and which require a real Azure service. Your teammates will thank you the first time they clone the repo.
Reading values in code
Direct access anywhere in your code:
var value = Environment.GetEnvironmentVariable("MyKey");
The recommended approach: inject IConfiguration and read by key name. Since the values are environment variables, the key name is the exact string in Values:
public class OrderProcessor
{
private readonly IConfiguration _config;
public OrderProcessor(IConfiguration config)
{
_config = config;
}
public void Process()
{
var connStr = _config["MyQueueConnection"];
}
}
Part 6 covers structuring values for testability with IOptions<T>, including the double-underscore separator needed for nested configuration in environment variables.
Azurite: your local storage account
Azurite emulates the three Azure Storage services locally: Blob, Queue, and Table. Azure Functions uses these services internally, which is why Azurite has to be running even if your function code does not touch storage directly.
Why the host needs storage
The Functions host uses Azure Storage for:
- Timer trigger lease management: prevents duplicate executions when multiple instances run
- Function key storage: host keys and function keys are persisted in blob storage
- Durable Functions task hub: state and message coordination
- Event Hubs checkpoints: tracks which events have been processed
AzureWebJobsStorage in local.settings.json is the connection string for this internal storage use. Only pure HTTP-only projects can skip it. Everything else needs Azurite running first.
Which service maps to which trigger
| Azurite service | Port | Used by |
|---|---|---|
| Blob service | 10000 | Blob trigger, Blob input/output bindings, host key storage |
| Queue service | 10001 | Queue trigger, Queue output binding, Durable Functions activity queue |
| Table service | 10002 | Table input/output bindings, Durable Functions instance state |
Azurite Table Storage is still in preview as of February 2026. It works for most local development scenarios but is not production-equivalent.
You can start individual services if you want to be selective: azurite-blob, azurite-queue, or azurite-table as separate commands. From the VS Code Command Palette: Azurite: Start Blob Service, Azurite: Start Queue Service, etc.
Connection strings
The shorthand that works when Azurite runs on the default ports:
UseDevelopmentStorage=true
The full explicit form, needed when Azurite is in Docker or using non-default ports:
DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;
The devstoreaccount1 account name and the key above are the public well-known Azurite credentials, the same for every developer. They carry no security value.
Common gotchas
Port conflicts: if ports 10000, 10001, or 10002 are already in use, Azurite fails silently or with a cryptic error. Check with
lsof -i :10000(macOS/Linux) ornetstat -ano | findstr :10000(Windows). Run with--blobPort 20000 --queuePort 20001 --tablePort 20002to use alternate ports, and update your connection strings accordingly.Data location: running
azuritewith no--locationflag writes data files to the current working directory. The VS Code extension stores data in the workspace folder. Run Azurite: Clean from the Command Palette to wipe all data and start fresh, which is useful when you want to test a clean startup sequence.Docker networking:
UseDevelopmentStorage=trueresolves to127.0.0.1, meaning the local machine. If your function host runs in a Docker container,127.0.0.1points inside that container, not at Azurite running on your host. Usehost.docker.internalin the explicit connection string form instead.Never publish
UseDevelopmentStorage=trueto Azure: the Functions host will fail to start. Azure App Settings needs a real storage account connection string, not the Azurite shorthand.
Debugging in VS Code
Required extensions
Two extensions are needed:
-
Azure Functions (
ms-azuretools.vscode-azurefunctions): provides the${command:azureFunctions.pickProcess}variable that makes F5 work, and the Execute Function Now... command for triggering non-HTTP functions from the editor -
C# Dev Kit (
ms-dotnettools.csdevkit): the recommended C# extension for new setups. Installs the base C# extension (ms-dotnettools.csharp) automatically, which provides thecoreclrdebugger needed for attaching to the worker process.
Recommended additions:
-
Azurite (
Azurite.azurite): start and stop Azurite directly from the Command Palette without switching to a terminal -
REST Client (
humao.rest-client): run.httpfiles against your functions from inside the editor
The F5 workflow
When you press F5 on a Functions project, the following sequence runs:
- VS Code executes the
build (functions)task fromtasks.json(adotnet buildwith--no-incremental) - The Azure Functions extension starts
func start -
func.exelaunches the worker process (dotnet.exe) - The extension resolves the worker PID via
${command:azureFunctions.pickProcess} - The
coreclrdebugger attaches to the worker process - Breakpoints activate
Generated launch.json
Running func init for a dotnet-isolated project generates this configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to .NET Functions",
"type": "coreclr",
"request": "attach",
"processId": "${command:azureFunctions.pickProcess}"
}
]
}
"request": "attach" is not a mistake. The worker process is started by func.exe, not by VS Code, so VS Code cannot launch it. It can only attach to it after the fact. The ${command:azureFunctions.pickProcess} variable is what prompts the process picker if the extension cannot auto-detect the right PID.
tasks.json
The extension generates five tasks. The critical one is the func: host start task at the bottom:
{
"version": "2.0.0",
"tasks": [
{
"label": "clean (functions)",
"command": "dotnet",
"args": [
"clean", "${workspaceFolder}/MyProject.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"type": "process",
"problemMatcher": "$msCompile"
},
{
"label": "build (functions)",
"command": "dotnet",
"args": [
"build", "${workspaceFolder}/MyProject.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"type": "process",
"dependsOn": "clean (functions)",
"group": { "kind": "build", "isDefault": true },
"problemMatcher": "$msCompile"
},
{
"type": "func",
"label": "func: host start",
"command": "host start",
"options": {
"cwd": "${workspaceFolder}/bin/Debug/net10.0"
},
"isBackground": true,
"dependsOn": "build (functions)",
"problemMatcher": "$func-dotnet-isolated-watch"
}
]
}
The func task type points cwd at the compiled output directory because the isolated worker is an executable that runs from there. The problemMatcher is $func-dotnet-isolated-watch (not $func-dotnet-watch, which is for in-process). The dependsOn chain means F5 triggers a build before starting the host.
If you create a project via func init on the CLI instead of through the VS Code extension, you may not get .vscode files automatically. Run Azure Functions: Initialize Project for Use with VS Code from the Command Palette to generate them.
Setting breakpoints and inspecting state
Click the gutter (left of line numbers) to set a breakpoint. When the function is triggered, execution pauses. From there:
- Variables panel: all locals and parameters in scope
- Hover over any identifier: shows its current value inline
- Debug Console: evaluates any C# expression against live state (LINQ works, method calls work)
- Call Stack panel: the full call chain from the gRPC dispatcher into your function code
Testing non-HTTP functions from VS Code
Command Palette → Azure Functions: Execute Function Now... → select Local project → pick the function.
This posts to the admin endpoint: POST http://localhost:7071/admin/functions/{FunctionName} with {"input": ""}. For queue triggers, set input to the message body you want to inject.
Common pitfall
Opening a subfolder of your project instead of the folder containing host.json causes F5 to fail without a clear error. The .vscode folder is not found. Always open the project root.
Debugging in Visual Studio 2026
Prerequisites
Visual Studio 2026 (GA since November 2025) is required for .NET 10 development. Visual Studio 2022 cannot target net10.0. Install Visual Studio 2026 from the standard Visual Studio installer.
Install the Azure development workload during setup or add it afterward via the Visual Studio Installer. This adds the Functions project templates and Core Tools integration. Update Core Tools separately when Visual Studio prompts, or through Tools | Options | Projects and Solutions | Azure Functions | Check for Updates.
The F5 workflow
- Visual Studio builds the project
- Visual Studio launches
func.exe(output visible in the terminal pane) -
func.exestarts the worker (dotnet.exe) and reports the worker PID back over gRPC - Visual Studio automatically attaches its debugger to the worker
dotnet.exeprocess
No launch.json is needed. Visual Studio handles the two-process attach automatically.
Useful VS-specific features
-
App Settings dialog: right-click the project → Publish → Hosting → Manage Azure App Service settings. It shows a side-by-side view of local
local.settings.jsonvalues vs. Azure App Settings, and lets you push individual values to Azure with one click, useful for keeping environments in sync without manually copying strings. -
Remote debugging: attach to a deployed function app from Visual Studio. Requires Premium or App Service plan (not Consumption or Flex), Windows OS, and a Debug build. Available for 48 hours before it auto-disables. Use Attach to Process, set the connection type to Microsoft Azure App Services, then select
dotnet.exein the process list.
Known issue
In some Core Tools versions, starting with debugging can take 60 seconds to attach while starting without debugging is instant. Updating Core Tools via Tools | Options | Azure Functions resolves this in most cases.
Debugging in Rider
Plugin requirement
The Azure Toolkit for Rider is not bundled with Rider and is not part of the JetBrains marketplace defaults. Install it from Settings | Plugins | Marketplace, search for "Azure Toolkit for Rider". The current version is v4.6.5 (November 2025), a full rewrite of v3.x. Existing v3.x run configurations are not forward-compatible.
The run configuration trap
When you open a Functions project, Rider generates two run configurations:
-
.NET Launch Settings Profile: based on
launchSettings.json. This does not work for Azure Functions. Ignore it. -
Azure Function Host: the correct one. Rider calls
func.exewith the right flags internally.
If you see "Broken configuration due to unavailable plugin" after installing or updating to v4.0, delete the old configuration and create a new "Azure Function Host" configuration from scratch via Run | Edit Configurations | + | Azure Function Host.
Debugging workflow
Select the Azure Function Host configuration and press Shift+F9. Rider:
- Builds the project
- Starts
func.exewith--dotnet-isolated-debug - Reads the worker PID from the host's stdout
- Attaches the Rider debugger to the worker process
Each [Function] method gets gutter icons: the bug icon starts that function in debug mode, and the play icon opens a .http scratch file with an invocation request ready to send from Rider's built-in HTTP client.
Known bugs
"Azure Functions host did not return isolated worker process id": Rider reads the PID from the host log output. If the logging level in
host.jsonis set aboveInformation, the PID line is suppressed and Rider cannot detect the process. Fix: set"logging": { "logLevel": { "default": "Information" } }inhost.json. JetBrains has marked this aswontfixupstream: the PID must come from the host log line.Slow or failed debugger attach: start without debugging via the Run button, then attach manually through Run | Attach to Process and select the worker
dotnetprocess.Port configuration: the Azure Function Host run configuration does not read the port from
launchSettings.json. Set the port directly in Run | Edit Configurations | Function host arguments. The default 7071 is fine for most setups.
Hot reload and dotnet watch
Hot reload support for Azure Functions isolated worker depends on which tool starts the session:
| Start method | What happens on save |
|---|---|
| Visual Studio F5 | True hot reload (Edit and Continue). Most method body changes apply without restart. |
dotnet watch |
Full process restart. All in-memory state is lost. |
func start (and Rider's Azure Function Host config) |
No change detection. Stop and restart manually. |
| VS Code F5 | Full process restart when you stop and re-run. |
Visual Studio hot reload
Changes that apply without a restart:
- Code changes inside existing method bodies
- Adding new methods, properties, or fields to existing types
Changes that require a full restart (rude edits):
- Adding a new trigger function (new
[Function]attribute) - Changing binding attributes (
[QueueTrigger],[BlobTrigger], etc.) - Changing
Program.csstartup code - Changes to
host.jsonorlocal.settings.json - Adding new types or changing method signatures
The binding attribute restriction is the most relevant one for daily Functions development: any time you add a new trigger or modify a binding, you need a full restart.
One gotcha: mixed-mode debugging (the "Enable native code debugging" checkbox in project properties) breaks hot reload. If hot reload stopped working after enabling that option, that's why.
dotnet watch
With Microsoft.Azure.Functions.Worker.Sdk 2.0.0+, dotnet run is supported when Core Tools is installed. dotnet watch wraps dotnet run and restarts the process on file changes:
dotnet watch
This is a full restart, not hot reload. Useful if you want automatic restarts without manually stopping and restarting func start, but in-memory state does not survive between restarts. On Windows, requires the MSI or winget Core Tools installation (not npm).
The --dotnet-isolated-debug flag
func start --dotnet-isolated-debug
This flag starts the worker process, pauses it immediately, and prints:
Azure Functions .NET Worker (PID: 28664) initialized in debug mode. Waiting for debugger to attach...
The process stays paused until you manually attach a debugger. Use it when:
- You need to debug
Program.csstartup code before any function gets called (the worker pauses before startup completes, so you can attach and step through the entire initialization sequence) - VS Code or Rider is not auto-attaching to the correct process
For typical VS Code or Rider development, you do not need this flag. The IDE extensions handle process detection automatically.
Testing non-HTTP triggers locally
Every locally running Functions host exposes an admin API. Any trigger type can be invoked through it, without waiting for the actual trigger to fire.
The admin endpoint
# Timer trigger (input can be empty)
curl -X POST http://localhost:7071/admin/functions/CleanupFunction \
-H "Content-Type: application/json" \
-d '{"input": ""}'
# Queue trigger (input = the queue message body)
curl -X POST http://localhost:7071/admin/functions/OrderProcessor \
-H "Content-Type: application/json" \
-d '{"input": "{\"orderId\": \"ord-123\", \"amount\": 99.99}"}'
# Blob trigger
curl -X POST http://localhost:7071/admin/functions/ImageProcessor \
-H "Content-Type: application/json" \
-d '{"input": "test-blob-content"}'
The function name in the URL is the string in [Function("...")], not the C# method name.
Simulating real storage events
The admin endpoint is fast for testing, but it bypasses the actual trigger mechanism. For testing the trigger polling behavior, use Azurite directly:
- Add a message to an Azurite queue: the queue trigger picks it up on the next poll cycle (default: 2 seconds locally)
- Upload a blob to an Azurite container: the blob trigger fires
Azure Storage Explorer (the desktop app) and the Azure Storage VS Code extension both work against Azurite. Point them at http://127.0.0.1:10000 and use the well-known devstoreaccount1 credentials.
.http scratch files
Create requests.http in the project root for quick invocations during development:
### Trigger cleanup manually
POST http://localhost:7071/admin/functions/CleanupFunction
Content-Type: application/json
{"input": ""}
### Test HTTP endpoint
POST http://localhost:7071/api/orders
Content-Type: application/json
{
"orderId": "ord-123",
"customerId": "cust-456",
"amount": 99.99
}
REST Client (VS Code), Rider's built-in HTTP client, and Visual Studio's native .http editor all execute these files with a click.
Productivity tips
Two log streams, two configs
In the isolated worker model, logging is split across two configuration points:
-
host.json: controls the Functions host logs (trigger polling, execution lifecycle, binding errors) -
Worker-side (
appsettings.jsonorProgram.cs): controls yourILogger<T>calls
Changes to one do not affect the other. To see your Information-level logs during local development without drowning in host noise:
// host.json
{
"version": "2.0",
"logging": {
"logLevel": {
"default": "Information",
"Host.Aggregator": "Trace",
"Host.Results": "Information"
}
}
}
// appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"MyFunctionApp": "Debug"
}
}
}
Application Insights drops worker logs by default
If you add Application Insights to a local setup and your ILogger calls stop appearing, this is why: Application Insights registers a default filter rule in the isolated worker model that drops Information and Debug logs. To remove it, update Program.cs:
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = FunctionsApplication.CreateBuilder(args);
builder.Services
.AddApplicationInsightsTelemetryWorkerService()
.ConfigureFunctionsApplicationInsights();
// Application Insights registers a default filter rule that drops everything
// below Warning. Remove it to allow Information-level logs through.
builder.Logging.Services.Configure<LoggerFilterOptions>(options =>
{
LoggerFilterRule? defaultRule = options.Rules.FirstOrDefault(rule =>
rule.ProviderName
== "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider");
if (defaultRule is not null)
{
options.Rules.Remove(defaultRule);
}
});
builder.Build().Run();
If you prefer to avoid touching Program.cs, you can configure Application Insights log levels through appsettings.json instead. Add this file to your project root (set "Copy to Output Directory" to "Copy if newer"):
{
"Logging": {
"LogLevel": { "Default": "Information" },
"ApplicationInsights": {
"LogLevel": { "Default": "Information" }
}
}
}
When using FunctionsApplication.CreateBuilder, this file is loaded automatically and the filter removal code above is not needed.
Use APPLICATIONINSIGHTS_CONNECTION_STRING, not the instrumentation key
Microsoft ended instrumentation key ingestion support on March 31, 2025. For any project started after that date, use the connection string setting in local.settings.json:
"APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=...;IngestionEndpoint=https://..."
Do not set both. If both are present, newer SDKs ignore the key.
Override log levels without restarting
Use double-underscore environment variable overrides in local.settings.json:
"AzureFunctionsJobHost__logging__logLevel__default": "Debug"
This overrides host.json log levels without touching the file or restarting func start. Useful for temporarily enabling verbose output to track down a specific issue.
host.json queue settings for faster debugging
{
"extensions": {
"queues": {
"batchSize": 1,
"visibilityTimeout": "00:00:05",
"maxDequeueCount": 3
}
}
}
-
batchSize: 1: processes one queue message at a time, making it much easier to follow execution in the logs -
visibilityTimeout: "00:00:05": failed messages reappear in 5 seconds instead of the default 10 minutes, so you can iterate on error handling without waiting -
maxDequeueCount: 3: messages move to the poison queue after 3 failures (default is 5)
Terminal aliases
# Add to .zshrc / .bashrc
alias fstart='func start'
alias fstartd='func start --dotnet-isolated-debug'
alias fstartv='func start --verbose'
# Start Azurite in background, then start the function host
alias devstart='azurite --silent --location /tmp/azurite & func start'
What's next
You can now run any trigger type locally, attach a debugger to your actual function code, test non-HTTP functions without waiting for real messages, and reproduce storage-related behaviors against Azurite without touching a live Azure subscription.
Part 5 goes deeper into the architecture you've been running locally: the isolated worker model. Why Microsoft created a separate worker process, what you gain over the old in-process model, and what the November 2026 end-of-support deadline means for existing projects.
Azure Functions for .NET Developers: Series
- Part 1: Why Azure Functions? Serverless for .NET Developers
- Part 2: Your First Azure Function: HTTP Triggers Step-by-Step
- Part 3: Beyond HTTP: Timer, Queue, and Blob Triggers
- Part 4: Local Development Setup: Tools, Debugging, and Hot Reload ← you are here
- Part 5: Understanding the Isolated Worker Model (coming soon)




Top comments (0)