//JorgenHoc
← Todos os artigos
.NET Hosting•Por Jorge Calderón••Atualizado •13 min read

Azure App Service para .NET — Configuração, Preços e Ajustes

Guia passo a passo para implantar aplicações .NET 10 no Azure App Service: configuração com CLI, níveis de preços, slots de implantação, variáveis de ambiente, escalabilidade e CI/CD com GitHub Actions.

#azure#dotnet#cloud#devops

O Azure App Service é o caminho mais rápido para levar código .NET a uma URL de produção em funcionamento se você já está no ecossistema Microsoft. Ele cuida do patching do sistema operacional, atualizações de runtime, certificados TLS e balanceamento de carga — você só precisa implantar sua aplicação.

Pré-requisitos

# Instalar o Azure CLI
winget install Microsoft.AzureCLI
 
# Fazer login
az login
💡

Versões recentes da Azure CLI mudaram o fluxo de login (encontrei isso na 2.88.0): az login agora lista todas as assinaturas que sua conta pode acessar e pede para você escolher uma ali mesmo, em vez de assumir uma silenciosamente. Então o az account set abaixo não faz mais parte do ritual do primeiro login — você só precisa dele para trocar de assinatura mais tarde.

# Definir sua assinatura (se você tiver várias e quiser trocar depois)
az account set --subscription "My Subscription"

Criando um App Service pela CLI

# 1. Criar um grupo de recursos (contêiner lógico para recursos relacionados)
az group create \
  --name myapp-rg \
  --location eastus
 
# 2. Criar um plano do App Service (define o nível de computação)
az appservice plan create \
  --name myapp-plan \
  --resource-group myapp-rg \
  --sku B1 \
  --is-linux
 
# 3. Criar a aplicação web apontando para o .NET 10
az webapp create \
  --name myapp-12345 \               # Deve ser globalmente único
  --resource-group myapp-rg \
  --plan myapp-plan \
  --runtime "DOTNETCORE:10.0"
 
# Sua aplicação já está disponível em: https://myapp-12345.azurewebsites.net

Uma armadilha que vale conhecer: --runtime usa a forma com dois-pontos (DOTNETCORE:10.0), mas o comando que lista o que sua região oferece imprime a forma com barra vertical — não os compare literalmente em um script:

# Imprime p. ex. DOTNETCORE|10.0 (com suporte até 2028-12-01), DOTNETCORE|8.0, ...
az webapp list-runtimes --os linux
Portal do Azure mostrando o grupo de recursos criado pelos comandos da CLI, com um App Service chamado jorgenhoc-sample-7305 e um plano do App Service chamado jorgenhoc-sample-plan, ambos em East US.
Os três comandos acima, vistos do portal: um grupo de recursos contendo o plano e a aplicação.

Níveis de preços

Os preços abaixo não são de memória — vêm da API pública Retail Prices do Azure (Linux, East US, pagamento por uso, consultados em 2026-08-18; mensal ≈ por hora × 730):

NívelPor hora~MensalRAMvCPUPrincipais recursos
F1 Free$0$01 GBCompartilhado60 CPU-min/dia, sem TLS de domínio personalizado
B1 Basic$0.017~$121,75 GB1Always On, TLS de domínio personalizado
B2 Basic$0.034~$253,5 GB2—
B3 Basic$0.067~$497 GB4—
S1 Standard$0.095~$691,75 GB1Slots de implantação, autoescala
S2 Standard$0.190~$1393,5 GB2—
P0v3 Premium$0.0775~$574 GB1Slots, autoescala, redundância de zona
P1v3 Premium$0.155~$1138 GB2—
P2v3 Premium$0.310~$22616 GB4—

Reproduza (ou consulte sua própria região) com uma única requisição — sem autenticação:

curl -sG "https://prices.azure.com/api/retail/prices" \
  --data-urlencode "\$filter=serviceName eq 'Azure App Service' and armRegionName eq 'eastus' and priceType eq 'Consumption'"
💡

Para aplicações de produção pequenas, o B1 ($12/mês) é excelente. Mas olhe com atenção antes de escolher Standard pelos slots de implantação: **o P0v3 ($57) é mais barato que o S1 (~$69)** com mais RAM, hardware mais novo e tudo o que o Standard oferece. Compare Premium v3 com Standard na sua região antes de assumir que Standard é o nível econômico com slots.

Implantando sua aplicação

Método 1: ZIP Deploy (o mais rápido)

# Compilar e publicar
dotnet publish -c Release -o ./publish
 
# Criar o ZIP
cd ./publish && zip -r ../app.zip . && cd ..
 
# Implantar
az webapp deploy \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --src-path app.zip \
  --type zip

Esse caminho exato — grupo, plano, app, app settings, deploy zip — está automatizado de ponta a ponta em samples/azure-app-service-dotnet (uma API mínima cuja resposta comprova cada afirmação de configuração deste artigo). Duas coisas medidas ao executá-lo, para calibrar expectativas:

  • A implantação completa levou ~2,5 minutos, e quase tudo foi a fase "Starting the site" do Kudu depois do upload — o zip em si foi aceito em cerca de um segundo. Não assuma que a implantação travou ao passar dos 90 segundos.
  • O Git Bash no Windows não inclui zip — e não recorra ao Compress-Archive. O Compress-Archive do PowerShell grava os nomes das entradas com barra invertida, e o rsync do Kudu no Linux os rejeita (Invalid argument (22)) assim que a saída de publish contém uma subpasta; uma saída plana sobrevive por sorte, e é por isso que esse bug se esconde. Use o bsdtar embutido do Windows: C:\Windows\System32\tar.exe -a -cf app.zip -C publish * produz um zip correto com barras normais.

Método 2: GitHub Actions (recomendado para produção)

Primeiro, baixe o perfil de publicação pelo portal ou pela CLI:

az webapp deployment list-publishing-profiles \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --xml > publish-profile.xml

Adicione o conteúdo como um segredo do GitHub com o nome AZURE_WEBAPP_PUBLISH_PROFILE, em seguida:

# .github/workflows/deploy.yml
name: Deploy to Azure App Service
 
on:
  push:
    branches: [main]
 
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
 
    steps:
      - uses: actions/checkout@v4
 
      - name: Setup .NET 10
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'
 
      - name: Restore dependencies
        run: dotnet restore
 
      - name: Build
        run: dotnet build --configuration Release --no-restore
 
      - name: Test
        run: dotnet test --no-build --verbosity normal
 
      - name: Publish
        run: dotnet publish -c Release -o ${{ github.workspace }}/publish
 
      - name: Deploy to Azure
        uses: azure/webapps-deploy@v3
        with:
          app-name: myapp-12345
          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
          package: ${{ github.workspace }}/publish

Método 3: Azure CLI pelo GitHub Actions (autenticação OIDC — sem segredos)

- name: Azure Login (OIDC)
  uses: azure/login@v2
  with:
    client-id: ${{ secrets.AZURE_CLIENT_ID }}
    tenant-id: ${{ secrets.AZURE_TENANT_ID }}
    subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
 
- name: Deploy
  run: |
    cd ./publish && zip -r ../app.zip . && cd ..
    az webapp deploy \
      --resource-group myapp-rg \
      --name myapp-12345 \
      --src-path ./app.zip \
      --type zip

Variáveis de ambiente e configuração

Definindo as configurações da aplicação

As configurações da aplicação tornam-se variáveis de ambiente dentro do seu app, substituindo o appsettings.json:

# Definir valores individuais
az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --settings \
    ASPNETCORE_ENVIRONMENT=Production \
    FeatureFlags__NewCheckout=true
 
# Definir a partir de um arquivo JSON
az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --settings @app-settings.json

O sublinhado duplo (FeatureFlags__NewCheckout) corresponde ao separador de hierarquia : do appsettings.json, e um App Setting vence a mesma chave no appsettings.json. A aplicação de exemplo implantada acima demonstra essa precedência — seu appsettings.json diz "from appsettings.json", mas a resposta implantada mostra o valor definido via CLI:

Resposta JSON da aplicação de exemplo implantada: environment Production, message 'set from App Settings via az cli' substituindo o valor do appsettings.json, o nome do site do App Service e um WEBSITE_INSTANCE_ID.
A resposta do exemplo implantado: ASPNETCORE_ENVIRONMENT chega como Production sem que o app o defina, e o App Setting substitui o valor incluído no appsettings.json.

Strings de conexão

As strings de conexão recebem tratamento especial — são acessíveis via ConnectionStrings:Name na configuração do .NET:

az webapp config connection-string set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --connection-string-type SQLAzure \
  --settings DefaultConnection="Server=myserver.database.windows.net;..."
// Na sua aplicação, lê primeiro do App Settings do Azure, depois do appsettings.json
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

Usando o Key Vault para segredos

Referencie segredos do Key Vault sem armazená-los no App Settings:

# Criar o Key Vault
az keyvault create --name myapp-kv --resource-group myapp-rg --location eastus
 
# Armazenar um segredo
az keyvault secret set --vault-name myapp-kv --name DbPassword --value "s3cr3t!"
 
# Habilitar identidade gerenciada atribuída pelo sistema na aplicação web
az webapp identity assign --resource-group myapp-rg --name myapp-12345
 
# Obter o ID do principal
principalId=$(az webapp identity show --resource-group myapp-rg --name myapp-12345 --query principalId -o tsv)
 
# Conceder à aplicação web acesso de leitura aos segredos do Key Vault
az keyvault set-policy --name myapp-kv --object-id $principalId --secret-permissions get list

Referencie os segredos no App Settings usando referências do Key Vault:

az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --settings "DbPassword=@Microsoft.KeyVault(VaultName=myapp-kv;SecretName=DbPassword)"

Slots de implantação

Os slots de implantação fornecem ambientes de staging e trocas sem tempo de inatividade. Disponíveis nos níveis S1 e superiores.

# Criar um slot de staging
az webapp deployment slot create \
  --name myapp-12345 \
  --resource-group myapp-rg \
  --slot staging
 
# Implantar no staging (não em produção)
az webapp deploy \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --slot staging \
  --src-path app.zip \
  --type zip
 
# Testar o staging em: https://myapp-12345-staging.azurewebsites.net
 
# Trocar staging com produção (quase sem tempo de inatividade)
az webapp deployment slot swap \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --slot staging \
  --target-slot production
 
# Reverter (trocar novamente)
az webapp deployment slot swap \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --slot staging \
  --target-slot production
💡

Configure definições específicas do slot (como strings de conexão) com o sinalizador "slot setting". Essas definições NÃO são trocadas junto com o slot — o slot de staging mantém sua conexão com o banco de dados de staging após a troca.

# Marcar uma configuração como específica do slot (não será trocada)
az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --slot staging \
  --slot-settings ConnectionStrings__DefaultConnection="staging-db-connection"

Escalabilidade

Escalonamento manual (nível Basic)

# Escalar verticalmente (alterar o tamanho da VM)
az appservice plan update \
  --name myapp-plan \
  --resource-group myapp-rg \
  --sku S2
 
# Escalar horizontalmente (adicionar instâncias) — o nível Basic suporta apenas escalonamento horizontal manual
az appservice plan update \
  --name myapp-plan \
  --resource-group myapp-rg \
  --number-of-workers 3

Autoescala (nível Standard e superiores)

# Habilitar autoescala para o plano do App Service
az monitor autoscale create \
  --resource-group myapp-rg \
  --resource myapp-plan \
  --resource-type Microsoft.Web/serverFarms \
  --name myapp-autoscale \
  --min-count 1 \
  --max-count 5 \
  --count 1
 
# Adicionar regra de escala para fora (adicionar instância quando CPU > 70%)
az monitor autoscale rule create \
  --resource-group myapp-rg \
  --autoscale-name myapp-autoscale \
  --condition "CpuPercentage > 70 avg 5m" \
  --scale out 1
 
# Adicionar regra de escala para dentro (remover instância quando CPU < 30%)
az monitor autoscale rule create \
  --resource-group myapp-rg \
  --autoscale-name myapp-autoscale \
  --condition "CpuPercentage < 30 avg 10m" \
  --scale in 1

Configurando o Always-On

Por padrão, as aplicações no nível Basic e superiores são encerradas após 20 minutos de inatividade. Habilite o Always On:

az webapp config set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --always-on true
⚠️

O Always On não está disponível no nível F1 Free. Se você estiver no nível Free e enfrentar cold starts, faça upgrade para pelo menos o nível B1.

Domínio personalizado e TLS

# Adicionar um domínio personalizado
az webapp config hostname add \
  --resource-group myapp-rg \
  --webapp-name myapp-12345 \
  --hostname www.yourdomain.com
 
# Criar um certificado TLS gerenciado (gratuito)
az webapp config ssl create \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --hostname www.yourdomain.com
 
# Vincular o certificado
az webapp config ssl bind \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --certificate-thumbprint <thumbprint-from-previous-command> \
  --ssl-type SNI

Monitoramento com Application Insights

# Criar o Application Insights
az monitor app-insights component create \
  --app myapp-insights \
  --resource-group myapp-rg \
  --location eastus
 
# Obter a chave de instrumentação
instrumentationKey=$(az monitor app-insights component show \
  --app myapp-insights \
  --resource-group myapp-rg \
  --query instrumentationKey -o tsv)
 
# Definir na aplicação web
az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --settings APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=$instrumentationKey"

Adicione o pacote NuGet à sua aplicação:

dotnet add package Microsoft.ApplicationInsights.AspNetCore
// Program.cs
builder.Services.AddApplicationInsightsTelemetry();

Referência útil da CLI

# Visualizar os logs da aplicação em tempo real
az webapp log tail --resource-group myapp-rg --name myapp-12345
 
# Exibir as configurações atuais da aplicação
az webapp config appsettings list --resource-group myapp-rg --name myapp-12345
 
# Reiniciar a aplicação
az webapp restart --resource-group myapp-rg --name myapp-12345
 
# Obter a URL da aplicação
az webapp show --resource-group myapp-rg --name myapp-12345 --query defaultHostName -o tsv
 
# Listar todas as aplicações em execução em um grupo de recursos
az webapp list --resource-group myapp-rg --query "[].{Name:name, State:state, URL:defaultHostName}" -o table

Como testei isto

Isto não é um passo a passo montado a partir da documentação. Criei um grupo de recursos, um plano de App Service e um App Service reais (jorgenhoc-sample-7305, Linux, East US) a partir do Azure CLI 2.88.0 na minha própria assinatura, implantei a minimal API de exemplo com az webapp deploy e tirei as duas capturas de tela acima do portal e da resposta do app implantado. O script de CLI que faz tudo isso de ponta a ponta está em samples/azure-app-service-dotnet.

Duas coisas que medi enquanto fazia isso, porque as duas me surpreenderam. O upload do zip foi aceito em cerca de um segundo, mas a implantação inteira levou cerca de 2,5 minutos, quase todos na fase "Starting the site" do Kudu. E o Git Bash no Windows não tem zip; quando recorri ao Compress-Archive do PowerShell, o Kudu no Linux rejeitou o arquivo com Invalid argument (22) porque os nomes das entradas usavam barras invertidas. A correção está na seção de implantação.

Os preços na tabela de níveis não são citados de memória nem de outros artigos. Eles vêm da API pública Retail Prices do Azure para Linux, East US, pay-as-you-go, obtidos em 2026-08-18, e a tabela diz isso. O Azure muda SKUs e preços sem aviso, então a consulta à API é o que merece confiança se a tabela e a sua fatura discordarem.

As afirmações sobre configuração (que ASPNETCORE_ENVIRONMENT chega como Production sem que o app o defina, e que um App Setting substitui a mesma chave no appsettings.json) são provadas pela própria resposta JSON do sample em vez de descritas. Se qualquer uma delas deixar de ser verdade em uma imagem futura do App Service, o app implantado vai dizer isso.

Para Onde Ir a Partir Daqui

O App Service é o caminho de menor resistência para uma aplicação .NET que já compila e roda, porque a plataforma cuida do runtime para você. Se preferir controlar o runtime — para fixar uma versão específica do .NET, adicionar dependências nativas ou manter o deploy portátil entre provedores — empacote a aplicação você mesmo com um Dockerfile para .NET.

Se ainda não escolheu um provedor, a comparação de hosting .NET coloca o App Service ao lado de Railway, Fly.io e Render em níveis de plano e trade-offs.

Leituras adicionais

Sobre o autor

Jorge Calderón

Engenheiro de software com mais de uma década construindo e operando aplicações .NET em produção — camadas de dados com EF Core, serviços intensivos em async e implantações em Azure e contêineres. Todos os benchmarks e projetos de exemplo destes guias estão publicados em um repositório público no GitHub para que você possa reproduzi-los.

Perfil no GitHub ↗LinkedIn ↗Benchmarks e código de exemplo ↗

Artigos relacionados