//JorgenHoc
← Todos los artículos
.NET HostingPor Jorge CalderónActualizado 12 min read

Azure App Service para .NET — Configuración, Precios y Ajustes

Guía paso a paso para desplegar aplicaciones .NET 10 en Azure App Service: configuración con CLI, niveles de precios, slots de despliegue, variables de entorno, escalado y CI/CD con GitHub Actions.

#azure#dotnet#cloud#devops

Azure App Service es la vía más rápida para llevar código .NET a una URL de producción en funcionamiento si ya trabajas dentro del ecosistema Microsoft. Se encarga del parcheo del sistema operativo, las actualizaciones del runtime, los certificados TLS y el balanceo de carga — tú solo despliega tu aplicación.

Requisitos previos

# Instalar Azure CLI
winget install Microsoft.AzureCLI
 
# Iniciar sesión
az login
💡

Las versiones recientes de Azure CLI cambiaron el flujo de login (me lo encontré en la 2.88.0): az login ahora lista todas las suscripciones a las que tu cuenta tiene acceso y te pide elegir una ahí mismo, en lugar de asumir una por defecto en silencio. Así que el az account set de abajo ya no es parte del ritual del primer login — solo lo necesitas para cambiar de suscripción más adelante.

# Establecer tu suscripción (si tienes varias y quieres cambiar después)
az account set --subscription "My Subscription"

Crear un App Service desde la CLI

# 1. Crear un grupo de recursos (contenedor lógico para recursos relacionados)
az group create \
  --name myapp-rg \
  --location eastus
 
# 2. Crear un plan de App Service (define el nivel de cómputo)
az appservice plan create \
  --name myapp-plan \
  --resource-group myapp-rg \
  --sku B1 \
  --is-linux
 
# 3. Crear la aplicación web apuntando a .NET 10
az webapp create \
  --name myapp-12345 \               # Debe ser globalmente único
  --resource-group myapp-rg \
  --plan myapp-plan \
  --runtime "DOTNETCORE:10.0"
 
# Tu aplicación ya está disponible en: https://myapp-12345.azurewebsites.net

Una trampa que conviene conocer: --runtime usa la forma con dos puntos (DOTNETCORE:10.0), pero el comando que lista lo que ofrece tu región imprime la forma con barra vertical — no los compares literalmente en un script:

# Imprime p. ej. DOTNETCORE|10.0 (con soporte hasta 2028-12-01), DOTNETCORE|8.0, ...
az webapp list-runtimes --os linux
Portal de Azure mostrando el grupo de recursos creado por los comandos de la CLI, con un App Service llamado jorgenhoc-sample-7305 y un plan de App Service llamado jorgenhoc-sample-plan, ambos en East US.
Los tres comandos anteriores, vistos desde el portal: un grupo de recursos que contiene el plan y la aplicación.

Niveles de precios

Los precios siguientes no son de memoria — provienen de la API pública Retail Prices de Azure (Linux, East US, pago por uso, consultados el 2026-08-18; mensual ≈ por hora × 730):

NivelPor hora~MensualRAMvCPUCaracterísticas principales
F1 Free$0$01 GBCompartido60 CPU-min/día, sin TLS de dominio personalizado
B1 Basic$0.017~$121,75 GB1Always On, TLS de dominio personalizado
B2 Basic$0.034~$253,5 GB2
B3 Basic$0.067~$497 GB4
S1 Standard$0.095~$691,75 GB1Slots de despliegue, autoescalado
S2 Standard$0.190~$1393,5 GB2
P0v3 Premium$0.0775~$574 GB1Slots, autoescalado, redundancia de zona
P1v3 Premium$0.155~$1138 GB2
P2v3 Premium$0.310~$22616 GB4

Reprodúcelo (o consulta tu propia región) con una sola petición — sin autenticación:

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 aplicaciones de producción pequeñas, B1 ($12/mes) es una excelente opción. Pero mira bien antes de elegir Standard por los slots de despliegue: **P0v3 ($57) es más barato que S1 (~$69)** con más RAM, hardware más nuevo y todo lo que ofrece Standard. Compara Premium v3 contra Standard en tu región antes de asumir que Standard es el nivel económico con slots.

Desplegar tu aplicación

Método 1: ZIP Deploy (el más rápido)

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

Este camino exacto — grupo, plan, app, app settings, despliegue zip — está automatizado de principio a fin en samples/azure-app-service-dotnet (una API mínima cuya respuesta demuestra cada afirmación de configuración de este artículo). Dos cosas medidas al ejecutarlo, para calibrar expectativas:

  • El despliegue completo tomó ~2,5 minutos, y casi todo fue la fase "Starting the site" de Kudu después de la subida — el zip en sí se aceptó en cerca de un segundo. No asumas que el despliegue se colgó al pasar los 90 segundos.
  • Git Bash en Windows no incluye zip — y no recurras a Compress-Archive. El Compress-Archive de PowerShell escribe los nombres de entrada con barra invertida, y el rsync de Kudu en Linux los rechaza (Invalid argument (22)) en cuanto la salida de publish contiene una subcarpeta; una salida plana sobrevive de casualidad, y por eso este bug se esconde. Usa el bsdtar integrado de Windows: C:\Windows\System32\tar.exe -a -cf app.zip -C publish * produce un zip correcto con barras normales.

Método 2: GitHub Actions (recomendado para producción)

Primero, descarga el perfil de publicación desde el portal o la CLI:

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

Agrega el contenido como un secreto de GitHub con el nombre AZURE_WEBAPP_PUBLISH_PROFILE, luego:

# .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 desde GitHub Actions (autenticación OIDC — sin secretos)

- 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

Variables de entorno y configuración

Establecer configuraciones de la aplicación

Las configuraciones de la aplicación se convierten en variables de entorno dentro de tu app, sobreescribiendo appsettings.json:

# Establecer valores individuales
az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --settings \
    ASPNETCORE_ENVIRONMENT=Production \
    FeatureFlags__NewCheckout=true
 
# Establecer desde un archivo JSON
az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --settings @app-settings.json

El doble guion bajo (FeatureFlags__NewCheckout) corresponde al separador de jerarquía : de appsettings.json, y un App Setting gana sobre la misma clave en appsettings.json. La aplicación de ejemplo desplegada arriba demuestra esta precedencia — su appsettings.json dice "from appsettings.json", pero la respuesta desplegada muestra el valor establecido vía CLI:

Respuesta JSON de la aplicación de ejemplo desplegada: environment Production, message 'set from App Settings via az cli' sobreescribiendo el valor de appsettings.json, el nombre del sitio de App Service y un WEBSITE_INSTANCE_ID.
La respuesta del ejemplo desplegado: ASPNETCORE_ENVIRONMENT llega como Production sin que la app lo establezca, y el App Setting sobreescribe el valor incluido en appsettings.json.

Cadenas de conexión

Las cadenas de conexión reciben un tratamiento especial — son accesibles mediante ConnectionStrings:Name en la configuración de .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;..."
// En tu app, primero lee de App Settings de Azure, luego de appsettings.json
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

Usar Key Vault para secretos

Referencia secretos desde Key Vault sin almacenarlos en App Settings:

# Crear Key Vault
az keyvault create --name myapp-kv --resource-group myapp-rg --location eastus
 
# Guardar un secreto
az keyvault secret set --vault-name myapp-kv --name DbPassword --value "s3cr3t!"
 
# Habilitar identidad administrada asignada por el sistema en la aplicación web
az webapp identity assign --resource-group myapp-rg --name myapp-12345
 
# Obtener el ID del principal
principalId=$(az webapp identity show --resource-group myapp-rg --name myapp-12345 --query principalId -o tsv)
 
# Otorgar a la aplicación web acceso de lectura a los secretos de Key Vault
az keyvault set-policy --name myapp-kv --object-id $principalId --secret-permissions get list

Referencia los secretos en App Settings usando referencias de 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 despliegue

Los slots de despliegue te ofrecen entornos de staging e intercambios sin tiempo de inactividad. Disponibles en los niveles S1 y superiores.

# Crear un slot de staging
az webapp deployment slot create \
  --name myapp-12345 \
  --resource-group myapp-rg \
  --slot staging
 
# Desplegar en staging (no en producción)
az webapp deploy \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --slot staging \
  --src-path app.zip \
  --type zip
 
# Probar staging en: https://myapp-12345-staging.azurewebsites.net
 
# Intercambiar staging con producción (casi sin tiempo de inactividad)
az webapp deployment slot swap \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --slot staging \
  --target-slot production
 
# Revertir (intercambiar de nuevo)
az webapp deployment slot swap \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --slot staging \
  --target-slot production
💡

Configura ajustes específicos del slot (como cadenas de conexión) con el indicador "slot setting". Estos ajustes NO se intercambian con el slot — el slot de staging conserva su conexión a la base de datos de staging después del intercambio.

# Marcar un ajuste como específico del slot (no se intercambiará)
az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --slot staging \
  --slot-settings ConnectionStrings__DefaultConnection="staging-db-connection"

Escalado

Escalado manual (nivel Basic)

# Escalar verticalmente (cambiar el tamaño de la VM)
az appservice plan update \
  --name myapp-plan \
  --resource-group myapp-rg \
  --sku S2
 
# Escalar horizontalmente (agregar instancias) — el nivel Basic solo admite escalado horizontal manual
az appservice plan update \
  --name myapp-plan \
  --resource-group myapp-rg \
  --number-of-workers 3

Autoescalado (nivel Standard y superiores)

# Habilitar el autoescalado para el plan de 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
 
# Agregar regla de escala hacia afuera (agregar instancia cuando CPU > 70%)
az monitor autoscale rule create \
  --resource-group myapp-rg \
  --autoscale-name myapp-autoscale \
  --condition "CpuPercentage > 70 avg 5m" \
  --scale out 1
 
# Agregar regla de escala hacia adentro (quitar instancia cuando CPU < 30%)
az monitor autoscale rule create \
  --resource-group myapp-rg \
  --autoscale-name myapp-autoscale \
  --condition "CpuPercentage < 30 avg 10m" \
  --scale in 1

Configurar Always-On

De manera predeterminada, las aplicaciones en el nivel Basic y superiores se detienen tras 20 minutos de inactividad. Habilita Always On:

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

Always On no está disponible en el nivel F1 Free. Si estás en el nivel Free y experimentas arranques en frío, actualiza al menos al nivel B1.

Dominio personalizado y TLS

# Agregar un dominio personalizado
az webapp config hostname add \
  --resource-group myapp-rg \
  --webapp-name myapp-12345 \
  --hostname www.yourdomain.com
 
# Crear un certificado TLS administrado (gratuito)
az webapp config ssl create \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --hostname www.yourdomain.com
 
# Vincular el certificado
az webapp config ssl bind \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --certificate-thumbprint <thumbprint-from-previous-command> \
  --ssl-type SNI

Monitorización con Application Insights

# Crear Application Insights
az monitor app-insights component create \
  --app myapp-insights \
  --resource-group myapp-rg \
  --location eastus
 
# Obtener la clave de instrumentación
instrumentationKey=$(az monitor app-insights component show \
  --app myapp-insights \
  --resource-group myapp-rg \
  --query instrumentationKey -o tsv)
 
# Establecerla en la aplicación web
az webapp config appsettings set \
  --resource-group myapp-rg \
  --name myapp-12345 \
  --settings APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=$instrumentationKey"

Agrega el paquete NuGet a tu aplicación:

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

Referencia útil de la CLI

# Ver los logs de la aplicación en tiempo real
az webapp log tail --resource-group myapp-rg --name myapp-12345
 
# Mostrar la configuración actual de la aplicación
az webapp config appsettings list --resource-group myapp-rg --name myapp-12345
 
# Reiniciar la aplicación
az webapp restart --resource-group myapp-rg --name myapp-12345
 
# Obtener la URL de la aplicación
az webapp show --resource-group myapp-rg --name myapp-12345 --query defaultHostName -o tsv
 
# Mostrar todas las aplicaciones en ejecución en un grupo de recursos
az webapp list --resource-group myapp-rg --query "[].{Name:name, State:state, URL:defaultHostName}" -o table

Hacia Dónde Seguir

App Service es el camino de menor resistencia para una app .NET que ya compila y corre, porque la plataforma gestiona el runtime por ti. Si prefieres controlar el runtime — para fijar una versión concreta de .NET, añadir dependencias nativas o mantener el despliegue portable entre proveedores — empaqueta la app tú mismo con un Dockerfile para .NET.

Si aún no has elegido proveedor, la comparativa de hosting .NET pone App Service junto a Railway, Fly.io y Render en niveles de plan y trade-offs.

Lecturas adicionales

Sobre el autor

Jorge Calderón

Ingeniero de software con más de una década construyendo y operando aplicaciones .NET en producción — capas de datos con EF Core, servicios intensivos en async y despliegues en Azure y contenedores. Cada benchmark y proyecto de ejemplo de estas guías está publicado en un repositorio público de GitHub para que puedas reproducirlo.

Perfil de GitHubLinkedIn ↗Benchmarks y código de ejemplo

Artículos relacionados