Azure for Developers: The Complete Fundamentals Guide
Learn Azure from a developer's perspective ā App Service, Azure Functions, Cosmos DB, Azure SQL, Service Bus, Azure AD (Entra ID), GitHub Actions CI/CD, Key Vault, and cost management. With real .NET deployment examples.
Azure from a Developer's Perspective
Azure is Microsoft's cloud platform ā 200+ services covering compute, storage, databases, networking, AI, and more. As a developer, you don't need to learn all of them. You need to know the 10 services that appear in 90% of production .NET applications.
Core Services for .NET Developers
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Compute: App Service, Azure Functions, Container Apps, AKS
Database: Azure SQL, Cosmos DB, Azure Cache for Redis
Messaging: Service Bus, Event Grid, Event Hubs
Security: Azure AD (Entra ID), Key Vault
Storage: Blob Storage, Queue Storage
DevOps: GitHub Actions, Azure DevOpsAzure App Service ā Deploy Your .NET API
App Service is a managed hosting platform for web apps, APIs, and backends. No server management required.
Deploy via GitHub Actions
# .github/workflows/deploy.yml
name: Deploy to Azure App Service
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "9.x"
- name: Build and publish
run: |
dotnet restore
dotnet build --configuration Release
dotnet publish --configuration Release --output ./publish
- name: Deploy to Azure App Service
uses: azure/webapps-deploy@v3
with:
app-name: ${{ vars.AZURE_APP_NAME }}
publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}
package: ./publishApp Service configuration (.NET)
// Program.cs ā reads from Azure App Settings (environment variables)
builder.Configuration
.AddEnvironmentVariables()
.AddAzureKeyVault(
new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
new DefaultAzureCredential());
// Azure App Service sets PORT automatically ā bind to it
var port = Environment.GetEnvironmentVariable("PORT") ?? "8080";
builder.WebHost.UseUrls($"http://0.0.0.0:{port}");App Service tiers
| Tier | Use Case | Price (est.) | |------|----------|-------------| | Free (F1) | Dev/test | £0 | | Basic (B1) | Small apps, no scale-out | ~£12/month | | Standard (S1) | Production, auto-scale | ~£54/month | | Premium (P1v3) | High performance | ~£110/month |
Always use Standard or Premium in production ā Basic doesn't support auto-scaling.
Azure Functions ā Serverless Compute
Azure Functions runs code in response to triggers ā HTTP requests, timers, Service Bus messages, blob uploads.
// Install tools
// dotnet tool install -g Azure.Functions.Worker.Sdk
// HTTP trigger
[Function("GetOrders")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "orders")] HttpRequestData req)
{
var orders = await _db.Orders
.OrderByDescending(o => o.CreatedAt)
.Take(20)
.ToListAsync();
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(orders);
return response;
}
// Timer trigger ā run every day at 2am
[Function("DailyReport")]
public async Task RunTimer(
[TimerTrigger("0 0 2 * * *")] TimerInfo timerInfo,
FunctionContext context)
{
var logger = context.GetLogger<DailyReportFunction>();
logger.LogInformation("Generating daily report at {Time}", DateTime.UtcNow);
await _reportService.GenerateAndEmailAsync();
}
// Service Bus trigger ā process messages from a queue
[Function("ProcessOrder")]
public async Task ProcessOrder(
[ServiceBusTrigger("orders", Connection = "ServiceBusConnection")] string messageBody,
FunctionContext context)
{
var order = JsonSerializer.Deserialize<OrderCreatedEvent>(messageBody)!;
await _orderProcessor.ProcessAsync(order);
}When to use Functions vs App Service
App Service:
ā
Long-running HTTP API
ā
Consistent traffic requiring always-on instances
ā
WebSocket connections
Azure Functions:
ā
Event-driven tasks (process a queue message, react to a blob upload)
ā
Scheduled jobs (nightly reports, data cleanup)
ā
Sporadic workloads (cost: pay per execution, not per hour)
ā
Background processing triggered by Service Bus or Event GridAzure SQL ā Managed SQL Server
Azure SQL is fully managed SQL Server. Handles patching, backups, HA ā you just use it.
// Connection string (from Azure Key Vault or App Settings)
"ConnectionStrings": {
"DefaultConnection": "Server=myserver.database.windows.net;Database=mydb;Authentication=Active Directory Default"
}
// Use Managed Identity ā no password in config
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));Firewall and authentication
# Allow Azure services (App Service ā Azure SQL ā no IP needed)
az sql server firewall-rule create \
--resource-group myRG \
--server myserver \
--name AllowAzureServices \
--start-ip-address 0.0.0.0 \
--end-ip-address 0.0.0.0
# Grant App Service Managed Identity access to the database
# In Azure SQL: CREATE USER [my-app-service] FROM EXTERNAL PROVIDER;
# ALTER ROLE db_datareader ADD MEMBER [my-app-service];
# ALTER ROLE db_datawriter ADD MEMBER [my-app-service];Azure Service Bus ā Reliable Messaging
Service Bus is a managed message broker ā queues and topics for decoupled, reliable async communication between services.
dotnet add package Azure.Messaging.ServiceBus// Publish a message
var client = new ServiceBusClient(connectionString);
var sender = client.CreateSender("orders");
var message = new ServiceBusMessage(JsonSerializer.Serialize(new OrderCreatedEvent
{
OrderId = order.Id,
CustomerId = order.CustomerId,
Total = order.TotalAmount,
}))
{
ContentType = "application/json",
MessageId = order.Id.ToString(), // deduplication key
};
await sender.SendMessageAsync(message);
// Consume messages (in a background service)
var processor = client.CreateProcessor("orders", new ServiceBusProcessorOptions
{
MaxConcurrentCalls = 5,
AutoCompleteMessages = false, // explicitly complete after processing
});
processor.ProcessMessageAsync += async args =>
{
var body = args.Message.Body.ToString();
var event = JsonSerializer.Deserialize<OrderCreatedEvent>(body)!;
await _orderProcessor.ProcessAsync(event);
await args.CompleteMessageAsync(args.Message); // remove from queue
};
processor.ProcessErrorAsync += async args =>
{
_logger.LogError(args.Exception, "Service Bus error on {EntityPath}", args.EntityPath);
};
await processor.StartProcessingAsync();Azure Key Vault ā Secrets Management
Never store secrets in code or config files. Key Vault centralises secret management.
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity// Program.cs ā load secrets from Key Vault
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
new DefaultAzureCredential()); // uses Managed Identity in production, dev login locally
// Secrets are then accessible via normal configuration
var connectionString = builder.Configuration["ConnectionStrings--DefaultConnection"];
// Key Vault uses "--" instead of ":" for nested keys// Access secrets directly in code
var secretClient = new SecretClient(
new Uri($"https://myvault.vault.azure.net/"),
new DefaultAzureCredential());
var secret = await secretClient.GetSecretAsync("OpenAI--ApiKey");
var apiKey = secret.Value.Value;Managed Identity means your App Service automatically authenticates to Key Vault without any credentials ā zero secrets in your code.
Azure AD (Entra ID) ā Authentication
Protect your API with Azure AD ā any Microsoft/work account can authenticate:
dotnet add package Microsoft.Identity.Web// Program.cs
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
// appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-api-client-id",
"Audience": "api://your-api-client-id"
}
}[Authorize]
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetOrders()
{
// Get the user's Azure AD object ID
var userId = User.FindFirstValue("oid") ?? User.FindFirstValue("sub");
// ...
}
// Require a specific app role
[HttpDelete("{id}"), Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteOrder(int id) { ... }
}Azure Blob Storage ā File Storage
dotnet add package Azure.Storage.Blobs// Upload a file
var blobClient = new BlobServiceClient(
new Uri("https://mystorageaccount.blob.core.windows.net"),
new DefaultAzureCredential());
var containerClient = blobClient.GetBlobContainerClient("uploads");
await containerClient.CreateIfNotExistsAsync(PublicAccessType.None);
var blob = containerClient.GetBlobClient($"avatars/{userId}.jpg");
await blob.UploadAsync(fileStream, new BlobHttpHeaders { ContentType = "image/jpeg" });
// Generate a SAS URL for temporary access (1 hour)
var sasUri = blob.GenerateSasUri(BlobSasPermissions.Read, DateTimeOffset.UtcNow.AddHours(1));
return Ok(new { url = sasUri.ToString() });Cost Management
Azure costs are easy to overspend. Key practices:
1. Set Budget Alerts
ā Azure Portal ā Cost Management ā Budgets
ā Alert at 80% and 100% of monthly budget
2. Use the right tier
ā Development: Free/Basic tiers
ā Production: Standard with auto-scale
ā Never leave Premium running if you don't need it
3. Stop/deallocate dev resources overnight
ā Azure Automation or GitHub Actions schedule
ā Can save 60-70% on dev environment costs
4. Use Serverless for sporadic workloads
ā Azure Functions Consumption plan: pay per execution
ā Cosmos DB serverless: pay per request unit
5. Reserved Instances (1 or 3 year) = 40-65% discount
ā Use for production workloads with predictable usage
6. Monitor with Azure Advisor
ā Recommendations for unused resources and rightsizingLocal Development with Azure Emulators
# Azurite ā local emulator for Blob/Queue/Table Storage
npm install -g azurite
azurite --silent --location ./azurite-data
# Azure SQL ā use SQL Server in Docker
docker run -e ACCEPT_EULA=Y -e SA_PASSWORD=YourStrong@Passw0rd \
-p 1433:1433 mcr.microsoft.com/mssql/server:2022-latest
# Service Bus ā use the emulator
docker run -p 5672:5672 mcr.microsoft.com/azure-messaging/servicebus-emulator
# DefaultAzureCredential for local dev ā uses your az login credentials
# No code change needed between local and productionKey Takeaways
- App Service for always-on web APIs; Azure Functions for event-driven / scheduled tasks
- Managed Identity is the right way to authenticate App Service ā Azure SQL, Key Vault, Service Bus ā no passwords anywhere
- Key Vault for all secrets ā never in code, never in config files
- Service Bus for reliable async messaging ā queues for 1:1, topics for fan-out
- Azure AD for enterprise authentication ā protects your API with OAuth2/OIDC with 5 lines of config
- Cost management: set budget alerts, use the right tier, stop dev resources overnight
- DefaultAzureCredential works identically in development (uses your
az login) and production (uses Managed Identity) ā zero code change
Enjoyed this article?
Explore the Cloud & DevOps learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.