Cómo desplegar tu sitio web con Azure Static Web Apps y Terraform
Daniel J. Saldaña
- 25 de abril de 2024
- Puntuación de feedback

Bienvenidos a un tutorial paso a paso sobre cómo desplegar un sitio web estático utilizando Azure Static Web Apps mediante Terraform. Esta guía es ideal tanto para desarrolladores que están comenzando con infraestructura como código, como para aquellos que buscan una solución robusta y escalable para la entrega de sitios web.
Requisitos previos
Antes de comenzar, asegúrate de tener lo siguiente:
- Una cuenta de Azure.
- Una cuenta de GitHub.
- Terraform instalado en tu máquina local.
- Una zona DNS ya creada en Azure para tu dominio.
- Asegúrate de que tu dominio está configurado con los DNS de Azure para permitir la gestión de registros DNS desde Azure.
Configuración inicial
Primero, vamos a configurar nuestros proveedores en Terraform. Esto incluye tanto Azure como GitHub. Asegúrate de tener configuradas las credenciales de autenticación para Azure y el token de acceso para GitHub.
main.tf:
provider "azurerm" { features {} # Asegúrate de configurar la autenticación de Azure, # puede ser mediante variables de entorno, archivos de configuración, etc.}
provider "github" { token = var.github_token owner = var.github_owner}
Definición de recursos
El siguiente paso es definir el recurso de Azure Static Web App en Terraform. Esto incluye especificaciones como el nombre, grupo de recursos, ubicación, entre otros. Además, configuraremos los secretos de GitHub Actions para integrar nuestro flujo de trabajo de CI/CD directamente desde GitHub.
main.tf (continuación):
resource "azurerm_static_web_app" "static_front" { count = var.create_resource ? 1 : 0
name = var.name resource_group_name = var.resource_group_name location = var.location preview_environments_enabled = var.preview_environments_enabled sku_tier = var.sku_tier sku_size = var.sku_size tags = var.tags
app_settings = { environment = var.app_settings_environment app_location = var.app_settings_app_location api_location = var.app_settings_api_location output_location = var.app_settings_output_location }}
resource "github_actions_secret" "publishprofile" { count = length(azurerm_static_web_app.static_front) > 0 ? 1 : 0
repository = var.app_repository secret_name = var.github_actions_secret_publishprofile_secret_name plaintext_value = azurerm_static_web_app.static_front[0].api_key}
resource "github_repository_file" "azure_static_web_app_yml" { count = length(azurerm_static_web_app.static_front) > 0 ? 1 : 0
repository = var.github_repository_file_repository branch = var.github_repository_file_branch file = ".github/workflows/azure-static-web-apps-${local.app_identifier}.yml" content = templatefile("./azure-static-web-app.tpl", { app_location = var.app_settings_app_location api_location = var.app_settings_api_location output_location = var.app_settings_output_location } ) commit_message = "Add workflow (by Terraform)" commit_author = "Daniel Saldana" commit_email = "danieljesus.sp@gmail.com" overwrite_on_create = true}
# Custom domain validationresource "azurerm_static_web_app_custom_domain" "validation_domain" { count = try(!empty(var.azure_dns_record.name) && length(var.azure_dns_record.zone_name) > 0 && length(azurerm_static_web_app.static_front) > 0, false) ? 0 : 1 depends_on = [azurerm_static_web_app.static_front]
static_web_app_id = azurerm_static_web_app.static_front[0].id domain_name = var.azure_dns_record.name != "" ? format("%s.%s", var.azure_dns_record.name, var.azure_dns_record.zone_name) : var.azure_dns_record.zone_name validation_type = "dns-txt-token"}
resource "azurerm_static_web_app_custom_domain" "validation_domain_www" { count = try(!empty(var.azure_dns_record.name) && length(var.azure_dns_record.zone_name) > 0 && length(azurerm_static_web_app.static_front) > 0, false) ? 0 : 1 depends_on = [azurerm_static_web_app.static_front]
static_web_app_id = azurerm_static_web_app.static_front[0].id domain_name = var.azure_dns_record.name != "" ? format("www.%s.%s", var.azure_dns_record.name, var.azure_dns_record.zone_name) : format("www.%s", var.azure_dns_record.zone_name) validation_type = "dns-txt-token"}
# Custom domain validationresource "azurerm_dns_txt_record" "dns_ip_record" { count = try(!empty(var.azure_dns_record.name) && length(var.azure_dns_record.zone_name) > 0 && length(azurerm_static_web_app.static_front) > 0, false) ? 0 : 1 depends_on = [azurerm_static_web_app_custom_domain.validation_domain]
name = var.azure_dns_record.name != "" ? var.azure_dns_record.name : "@" zone_name = var.azure_dns_record.zone_name resource_group_name = var.azure_dns_record.resource_group_name ttl = 300 record { value = azurerm_static_web_app_custom_domain.validation_domain[0].validation_token }}
resource "azurerm_dns_txt_record" "dns_ip_record_www" { count = try(!empty(var.azure_dns_record.name) && length(var.azure_dns_record.zone_name) > 0 && length(azurerm_static_web_app.static_front) > 0, false) ? 0 : 1 depends_on = [azurerm_static_web_app_custom_domain.validation_domain_www]
name = var.azure_dns_record.name != "" ? format("www.%s", var.azure_dns_record.name) : "www" zone_name = var.azure_dns_record.zone_name resource_group_name = var.azure_dns_record.resource_group_name ttl = 300 record { value = azurerm_static_web_app_custom_domain.validation_domain_www[0].validation_token }}
Variables y Outputs
Para hacer nuestro código más reutilizable y parametrizable, definiremos variables para todos los inputs necesarios y configuraremos algunas salidas para obtener información relevante una vez que se aplique el Terraform.
variables.tf:
variable "github_token" { description = "GitHub token" type = string
validation { condition = length(var.github_token) > 0 error_message = "El token de GitHub debe tener al menos un carácter." }}
variable "github_owner" { description = "GitHub owner" type = string
validation { condition = length(var.github_owner) > 0 error_message = "El propietario de GitHub debe tener al menos un carácter." }}
variable "app_repository" { description = "The repository for the application" type = string
validation { condition = length(var.app_repository) > 0 error_message = "El repositorio de la aplicación debe tener al menos un carácter." }}
variable "create_resource" { description = "Create the resource" type = bool
validation { condition = var.create_resource == true || var.create_resource == false error_message = "El valor de create_resource debe ser verdadero o falso." }}
variable "name" { description = "El nombre del servicio de la aplicación." type = string}
variable "resource_group_name" { description = "El nombre del grupo de recursos donde se creará el servicio de la aplicación." type = string
validation { condition = length(var.resource_group_name) > 0 error_message = "El nombre del grupo de recursos del servicio de la aplicación debe tener al menos un carácter." }}
variable "location" { description = "La ubicación de Azure donde se desplegará el servicio de la aplicación." type = string
validation { condition = length(var.location) > 0 error_message = "La ubicación del servicio de la aplicación debe tener al menos un carácter." }}
variable "preview_environments_enabled" { description = "Habilitar la creación de entornos de vista previa." type = bool
validation { condition = var.preview_environments_enabled == true || var.preview_environments_enabled == false error_message = "El valor de preview_environments_enabled debe ser verdadero o falso." }}
variable "sku_tier" { description = "El nivel de SKU del servicio de la aplicación." type = string
validation { condition = var.sku_tier == "Free" || var.sku_tier == "Basic" error_message = "El nivel de SKU del servicio de la aplicación debe ser Free o Basic." }}
variable "sku_size" { description = "El tamaño de SKU del servicio de la aplicación." type = string
validation { condition = var.sku_size == "Free" || var.sku_size == "Small" error_message = "El tamaño de SKU del servicio de la aplicación debe ser Free o Small." }}
variable "tags" { description = "Los tags del servicio de la aplicación." type = map(string)
validation { condition = length(var.tags) > 0 error_message = "Los tags del servicio de la aplicación deben tener al menos un carácter." }}
variable "app_settings_environment" { description = "La variable de entorno ENVIRONMENT del servicio de la aplicación." type = string
validation { condition = length(var.app_settings_environment) > 0 error_message = "La variable de entorno ENVIRONMENT del servicio de la aplicación debe tener al menos un carácter." }}
variable "app_settings_app_location" { description = "La variable de entorno app_location del servicio de la aplicación." type = string
validation { condition = length(var.app_settings_app_location) > 0 error_message = "La variable de entorno app_location del servicio de la aplicación debe tener al menos un carácter." }}
variable "app_settings_api_location" { description = "La variable de entorno api_location del servicio de la aplicación." type = string
validation { condition = length(var.app_settings_api_location) > 0 error_message = "La variable de entorno api_location del servicio de la aplicación debe tener al menos un carácter." }}
variable "app_settings_output_location" { description = "La variable de entorno output_location del servicio de la aplicación." type = string
validation { condition = length(var.app_settings_output_location) > 0 error_message = "La variable de entorno output_location del servicio de la aplicación debe tener al menos un carácter." }}
# GitHub Actions secretvariable "github_actions_secret_publishprofile_repository" { description = "El repositorio donde se almacenará el secreto de GitHub Actions." type = string
validation { condition = length(var.github_actions_secret_publishprofile_repository) > 0 error_message = "El repositorio donde se almacenará el secreto de GitHub Actions debe tener al menos un carácter." }}
variable "github_actions_secret_publishprofile_secret_name" { description = "El nombre del secreto de GitHub Actions." type = string
validation { condition = length(var.github_actions_secret_publishprofile_secret_name) > 0 error_message = "El nombre del secreto de GitHub Actions debe tener al menos un carácter." }}
# GitHub Actions filevariable "github_repository_file_repository" { description = "El repositorio donde se almacenará el archivo de GitHub." type = string
validation { condition = length(var.github_repository_file_repository) > 0 error_message = "El repositorio donde se almacenará el archivo de GitHub debe tener al menos un carácter." }}
variable "github_repository_file_branch" { description = "La rama donde se almacenará el archivo de GitHub." type = string
validation { condition = length(var.github_repository_file_branch) > 0 error_message = "La rama donde se almacenará el archivo de GitHub debe tener al menos un carácter." }}
# Domain customizations for the static web appvariable "azure_dns_record" { description = "Los registros DNS personalizados para el servicio de la aplicación." type = map(string)
validation { condition = length(var.azure_dns_record) > 0 error_message = "Los registros DNS personalizados para el servicio de la aplicación deben tener al menos un carácter." }}
outputs.tf:
output "default_host_name" { value = length(azurerm_static_web_app.static_front) > 0 ? azurerm_static_web_app.static_front[0].default_host_name : ""}
Configuración Local
Configuramos algunos valores locales que serán útiles para referencias internas dentro de nuestras configuraciones de Terraform.
local.tf:
locals { app_identifier = length(azurerm_static_web_app.static_front) > 0 ? element(split(".", azurerm_static_web_app.static_front[0].default_host_name), 0) : ""}
Archivo de Workflow para GitHub Actions
Para automatizar el proceso de build y deploy de nuestro sitio, utilizaremos GitHub Actions. A continuación, se muestra cómo configurar el archivo de workflow que reside en tu repositorio de GitHub.
azure-static-web-app.tpl:
name: Azure Static Web Apps CI/CD
on: push: branches: - production pull_request: types: [opened, synchronize, reopened, closed] branches: - production
jobs: build_and_deploy_job: if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') runs-on: ubuntu-latest name: Build and Deploy Job steps: - uses: actions/checkout@v3 with: submodules: true lfs: false - name: Build And Deploy id: builddeploy uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: $${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: $${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) action: 'upload' ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig app_location: ${ app_location } # App source code path api_location: ${ api_location } # Api source code path - optional output_location: ${ output_location } # Built app content directory - optional ###### End of Repository/Build Configurations ######
close_pull_request_job: if: github.event_name == 'pull_request' && github.event.action == 'closed' runs-on: ubuntu-latest name: Close Pull Request Job steps: - name: Close Pull Request id: closepullrequest uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: $${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} action: 'close'
Configuración de variables de Terraform
Finalmente, debemos crear un archivo terraform.tfvars
donde especificaremos los valores de las variables que hemos definido. Asegúrate de ajustar los valores según tus necesidades antes de aplicar Terraform.
terraform.tfvars:
# This file contains the variables that will be used in the main configuration filecreate_resource = true
# GitHub tokengithub_token = "YOUR_GITHUB"
# GitHub ownergithub_owner = "danieljsaldana"
# GitHub repositoryapp_repository = "danieljsaldana-portfolio"
# Azure app service namename = "danieljsaldana-static-front"resource_group_name = "danieljsaldana_dev"location = "westeurope"preview_environments_enabled = truesku_tier = "Free"sku_size = "Free"tags = { Project = "Daniel J. Saldaña", Tier = "Pago por uso", Environment = "Producción"}
# App settingsapp_settings_environment = "production"app_settings_app_location = "/"app_settings_api_location = "/"app_settings_output_location = "dist/"
# GitHub Actions secretgithub_actions_secret_publishprofile_repository = "danieljsaldana-portfolio"github_actions_secret_publishprofile_secret_name = "AZURE_STATIC_WEB_APPS_API_TOKEN"
# GitHub Actions filegithub_repository_file_repository = "danieljsaldana-portfolio"github_repository_file_branch = "production"
# Domain customizations for the static web appazure_dns_record = { name = "goliat" # "subdomain" zone_name = "ocrend.dev" # "domain" resource_group_name = "danieljsaldana_dev" # "resource_group"}
Aplicando la configuración
Una vez que hayas revisado y configurado todos los archivos, ejecuta los siguientes comandos en tu terminal para inicializar Terraform y aplicar la configuración:
terraform initterraform apply
Espera a que Terraform termine de desplegar todos los recursos y, si todo va bien, verás los detalles de tu nuevo sitio web estático en Azure, incluyendo su URL en la salida del comando.
¡Y eso es todo! Has configurado con éxito un sitio web estático en Azure utilizando Terraform y GitHub Actions. Esta configuración no solo te permite desplegar rápidamente sitios web, sino que también aprovecha las prácticas modernas de DevOps para mantener y escalar tus proyectos de manera eficiente.
Espero que este tutorial te haya sido útil y que ahora sientas mayor confianza al trabajar con Azure, GitHub y Terraform. Si tienes alguna pregunta o comentario, no dudes en dejar un comentario abajo. ¡Feliz codificación!