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 :
# Asegúrate de configurar la autenticación de Azure,
# puede ser mediante variables de entorno, archivos de configuración, etc.
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
resource_group_name = var . resource_group_name
preview_environments_enabled = var . preview_environments_enabled
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 validation
resource "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 validation
resource "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
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
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"
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"
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"
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"
condition = var . create_resource == true || var . create_resource == false
error_message = "El valor de create_resource debe ser verdadero o falso."
description = "El nombre del servicio de la aplicación."
variable "resource_group_name" {
description = "El nombre del grupo de recursos donde se creará el servicio de la aplicación."
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."
description = "La ubicación de Azure donde se desplegará el servicio de la aplicación."
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."
condition = var . preview_environments_enabled == true || var . preview_environments_enabled == false
error_message = "El valor de preview_environments_enabled debe ser verdadero o falso."
description = "El nivel de SKU del servicio de la aplicación."
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."
description = "El tamaño de SKU del servicio de la aplicación."
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."
description = "Los tags del servicio de la aplicación."
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."
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."
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."
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."
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."
variable "github_actions_secret_publishprofile_repository" {
description = "El repositorio donde se almacenará el secreto de GitHub Actions."
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."
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."
variable "github_repository_file_repository" {
description = "El repositorio donde se almacenará el archivo de GitHub."
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."
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 app
variable "azure_dns_record" {
description = "Los registros DNS personalizados para el servicio de la aplicación."
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 :
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
types : [ opened , synchronize , reopened , closed ]
if : github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
name : Build and Deploy Job
- uses : actions/checkout@v3
uses : Azure/static-web-apps-deploy@v1
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)
###### 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 ######
if : github.event_name == 'pull_request' && github.event.action == 'closed'
name : Close Pull Request Job
- name : Close Pull Request
uses : Azure/static-web-apps-deploy@v1
azure_static_web_apps_api_token : $${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
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 file
github_token = "YOUR_GITHUB"
github_owner = "danieljsaldana"
app_repository = "danieljsaldana-portfolio"
name = "danieljsaldana-static-front"
resource_group_name = "danieljsaldana_dev"
preview_environments_enabled = true
Project = "Daniel J. Saldaña" ,
Environment = "Producción"
app_settings_environment = "production"
app_settings_app_location = "/"
app_settings_api_location = "/"
app_settings_output_location = "dist/"
github_actions_secret_publishprofile_repository = "danieljsaldana-portfolio"
github_actions_secret_publishprofile_secret_name = "AZURE_STATIC_WEB_APPS_API_TOKEN"
github_repository_file_repository = "danieljsaldana-portfolio"
github_repository_file_branch = "production"
# Domain customizations for the static web app
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:
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!