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 loginVersõ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.netUma 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
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ível | Por hora | ~Mensal | RAM | vCPU | Principais recursos |
|---|---|---|---|---|---|
| F1 Free | $0 | $0 | 1 GB | Compartilhado | 60 CPU-min/dia, sem TLS de domínio personalizado |
| B1 Basic | $0.017 | ~$12 | 1,75 GB | 1 | Always On, TLS de domínio personalizado |
| B2 Basic | $0.034 | ~$25 | 3,5 GB | 2 | — |
| B3 Basic | $0.067 | ~$49 | 7 GB | 4 | — |
| S1 Standard | $0.095 | ~$69 | 1,75 GB | 1 | Slots de implantação, autoescala |
| S2 Standard | $0.190 | ~$139 | 3,5 GB | 2 | — |
| P0v3 Premium | $0.0775 | ~$57 | 4 GB | 1 | Slots, autoescala, redundância de zona |
| P1v3 Premium | $0.155 | ~$113 | 8 GB | 2 | — |
| P2v3 Premium | $0.310 | ~$226 | 16 GB | 4 | — |
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 zipEsse 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 aoCompress-Archive. OCompress-Archivedo 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.xmlAdicione 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 }}/publishMé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 zipVariá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.jsonO 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:

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 listReferencie 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 productionConfigure 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 3Autoescala (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 1Configurando 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 trueO 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 SNIMonitoramento 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 tableComo 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.