Back to blog
Cloud & DevOpsbeginner

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.

LearnixoApril 14, 20267 min read
AzureCloudDevOps.NETApp ServiceAzure FunctionsCI/CDGitHub Actions
Share:š•

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 DevOps

Azure 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

YAML
# .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: ./publish

App Service configuration (.NET)

C#
// 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.

C#
// 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 Grid

Azure SQL — Managed SQL Server

Azure SQL is fully managed SQL Server. Handles patching, backups, HA — you just use it.

C#
// 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

Bash
# 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.

Bash
dotnet add package Azure.Messaging.ServiceBus
C#
// 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.

Bash
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity
C#
// 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
C#
// 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:

Bash
dotnet add package Microsoft.Identity.Web
C#
// 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"
  }
}
C#
[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

Bash
dotnet add package Azure.Storage.Blobs
C#
// 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 rightsizing

Local Development with Azure Emulators

Bash
# 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 production

Key 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?

Share:š•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.