S3 + CloudFront: de cero a Terraform — guía completa

¿Cuánto pagas por tu hosting? Esta web cuesta menos de 50 céntimos al mes. Aquí cubrimos todo: qué es S3 + CloudFront, cómo montarlo paso a paso desde la consola de AWS y cómo automatizarlo con Terraform para replicarlo en tres comandos. Con código descargable.


Descarga el código Terraform

Todo el código mencionado en el artículo lo podrás encontrar en los siguientes enlaces:

Descargar .zip Ver en GitHub

El problema del hosting tradicional

Un hosting tradicional te da una máquina virtual corriendo Linux, con Apache o Nginx, PHP, MySQL y un panel de control lleno de opciones que no vas a tocar jamás. Tiene sentido si tienes WordPress, una tienda con base de datos, o cualquier aplicación con lógica en el servidor.

Pero si tu web es una landing — HTML, CSS, algo de JavaScript, unas imágenes — no necesitas nada de eso. Estás pagando infraestructura de servidor para que sirva archivos estáticos. Para ese caso existe S3 + CloudFront.

Coste real para una landing con 10.000 visitas al mes: entre 30 y 50 céntimos. No al día. Al mes.

Qué es cada pieza

S3 — el almacén

Amazon S3 es almacenamiento de objetos en la nube. Ahí subes tus archivos. El bucket lo dejamos privado — CloudFront accede a él mediante OAC (Origin Access Control), no los usuarios directamente. Así evitas que alguien llegue a S3 saltándose la CDN.

El coste es de fracciones de céntimo por GB almacenado y por petición. Para una landing normal, S3 es prácticamente gratis.

CloudFront — la CDN

CloudFront es la red de distribución de contenido (CDN) de AWS. Con más de 400 puntos de presencia en todo el mundo, cuando alguien visita tu web desde Madrid, el HTML lo sirve un servidor en Madrid, no en us-east-1. Latencia mínima, carga casi instantánea.

Además, CloudFront gestiona el certificado SSL automáticamente a través de AWS Certificate Manager. HTTPS incluido, sin renovaciones manuales ni configuración extra.

Terraform IaC · .tf files crea ACM us-east-1 · TLS Route 53 DNS · CNAME CloudFront CDN · HTTPS · OAC PriceClass_100 S3 Bucket Usuario Browser AWS Cloud TLS cert DNS OAC deploy.sh s3 sync
Arquitectura completa: el usuario llega por Route 53, CloudFront sirve los archivos cacheados y accede a S3 mediante OAC. Terraform gestiona toda la infraestructura.

Cuándo NO usar esto

Antes de lanzarte, verifica que tu caso encaja. Esto no sirve para:

  • WordPress: necesita PHP y MySQL. Hosting compartido o VPS.
  • Tiendas online con carrito y pagos: necesitas backend. Shopify, WooCommerce, o similar.
  • Aplicaciones con autenticación o sesiones en servidor: necesitas compute.
  • Next.js en modo SSR: si exportas en modo estático (next export), perfecto. Si hay server-side rendering, necesitas un servidor.

La regla: si al abrir el inspector de red todo son archivos .html, .css, .js y assets estáticos, es candidato para este stack.

Parte 1 — Configuración manual desde la consola de AWS

Esta sección cubre la configuración paso a paso desde la consola de AWS y el CLI. Si prefieres ir directamente a Terraform, salta a la Parte 2.

1. Crear el bucket S3

aws s3 mb s3://tu-dominio-com --region eu-west-1

En la consola de S3, activa Static website hosting y configura index.html como documento raíz. Deja el bucket privado — CloudFront accede mediante OAC, no los usuarios directamente.

2. Subir los archivos

aws s3 sync ./dist s3://tu-dominio-com --delete

El flag --delete elimina del bucket lo que ya no existe en local. Imprescindible al hacer redespliegues para no acumular archivos viejos.

3. Crear la distribución de CloudFront

En la consola de AWS, crea una nueva distribución con estos valores:

  • Origin: tu bucket S3 (el endpoint de S3, no el de website hosting)
  • Origin Access Control (OAC): actívalo y aplica la bucket policy que genera AWS — permite a CloudFront leer el bucket privado
  • Viewer Protocol Policy: Redirect HTTP to HTTPS
  • Price Class: "Use Only North America and Europe" si tu audiencia es Europa/América — reduce costes sin impacto real
  • Default root object: index.html

Al crear la distribución, AWS te asigna un dominio tipo d1234abcd.cloudfront.net. Ya funciona antes de añadir tu dominio propio.

4. Dominio propio + HTTPS

Solicita el certificado en AWS Certificate Manager (ACM). Atención: tiene que ser en la región us-east-1 obligatoriamente — CloudFront solo acepta certificados de esa región, independientemente de dónde tengas el resto.

Valida el certificado por DNS añadiendo el CNAME que indica ACM en tu proveedor (Route 53, Cloudflare, lo que uses). Una vez validado, en la distribución:

  • En Alternate domain names (CNAMEs), añade tu dominio (ej: www.tudominio.com)
  • En Custom SSL certificate, selecciona el certificado recién creado

Por último, crea un registro CNAME (o ALIAS si usas Route 53) en tu DNS apuntando a d1234abcd.cloudfront.net. Listo: HTTPS con tu dominio.

5. Invalidar caché al hacer deploy

CloudFront cachea los archivos. Al publicar cambios, invalida para que los usuarios reciban la versión actualizada de inmediato:

aws cloudfront create-invalidation \
  --distribution-id TU_DISTRIBUTION_ID \
  --paths "/*"

Las primeras 1.000 invalidaciones al mes son gratuitas.

El flujo de deploy completo (dos comandos)

# Subir los archivos actualizados
aws s3 sync ./dist s3://tu-dominio-com --delete

# Limpiar caché de CloudFront
aws cloudfront create-invalidation \
  --distribution-id TU_DISTRIBUTION_ID \
  --paths "/*"

Una vez configurado, publicar cambios se reduce a esto. Puedes meterlo en un script o en un pipeline de GitHub Actions para que cada push a main despliegue automáticamente.

Parte 2 — Automatizar con Terraform

La consola funciona, pero tiene un problema: la próxima vez que necesites replicar el entorno — para un cliente, para staging, para un compañero — tienes que recordar cada clic. Y los clics no se versionan en git.

Con Terraform defines la infraestructura en archivos .tf, la subes a un repositorio y cualquiera puede levantar el mismo entorno exacto con tres comandos. Es la diferencia entre una receta escrita y "lo hago de memoria".

Infrastructure as Code no es solo automatización. Es documentación ejecutable que nunca queda desactualizada.

Requisitos previos

Necesitas tener instalado:

  • Terraform ≥ 1.5 — descárgalo en terraform.io
  • AWS CLI configurado con credenciales que tengan permisos de S3, CloudFront, IAM y ACM

Verifica que todo está en orden:

terraform -version
aws sts get-caller-identity

Si ambos comandos responden sin errores, estás listo.

Estructura de archivos

terraform-cloudfront-s3/
├── main.tf          # Recursos principales: S3 + CloudFront
├── variables.tf     # Variables configurables
├── outputs.tf       # Salidas: URL de CloudFront, ID de distribución
├── providers.tf     # Configuración de AWS provider
└── deploy.sh        # Script de deploy (sync + invalidación)

Paso 1 — Configurar las variables

Edita variables.tf (o crea un archivo terraform.tfvars) con tus valores:

# terraform.tfvars
bucket_name    = "mi-web-com"          # Nombre único para el bucket S3
aws_region     = "eu-west-1"           # Región donde crear el bucket
environment    = "production"          # Etiqueta de entorno

# Opcional: dominio propio
# domain_name         = "www.midominio.com"
# acm_certificate_arn = "arn:aws:acm:us-east-1:..."

El bucket_name tiene que ser único globalmente en AWS. Si tu dominio es midominio.com, un buen nombre es midominio-com-web.

Paso 2 — Inicializar Terraform

terraform init

Terraform descarga el provider de AWS y prepara el directorio. Solo hay que hacerlo la primera vez o cuando cambias las versiones de los providers.

Paso 3 — Revisar el plan

terraform plan

Verás una lista de recursos que se van a crear: el bucket S3, la distribución CloudFront, las policies, etc. Revísalo antes de aplicar — es tu última oportunidad de detectar algo raro.

Paso 4 — Aplicar la infraestructura

terraform apply

Terraform te pedirá confirmación. Escribe yes y espera. El bucket S3 tarda segundos; la distribución de CloudFront tarda entre 5 y 15 minutos en propagarse por los edge locations de AWS.

Al terminar, verás las salidas definidas en outputs.tf:

Outputs:

cloudfront_domain          = "d3abc123xyz.cloudfront.net"
cloudfront_distribution_id = "E3XXXXXXXXXX"
s3_bucket_name             = "mi-web-com"

Paso 5 — Subir los archivos y publicar

Con la infraestructura creada, el script deploy.sh incluido hace el resto:

./deploy.sh ./dist

El script sincroniza tu carpeta local con S3 y lanza una invalidación de caché en CloudFront para que los cambios sean inmediatos. Solo necesitas pasarle la ruta a tu carpeta de build.

Cómo funciona el código por dentro

El bucket S3 (main.tf)

resource "aws_s3_bucket" "web" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_s3_bucket_public_access_block" "web" {
  bucket = aws_s3_bucket.web.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

El bucket se crea completamente privado. El acceso público bloqueado por las cuatro vías. CloudFront accede mediante OAC, no mediante URL pública de S3.

La distribución CloudFront

resource "aws_cloudfront_distribution" "web" {
  origin {
    domain_name              = aws_s3_bucket.web.bucket_regional_domain_name
    origin_id                = "s3-${var.bucket_name}"
    origin_access_control_id = aws_cloudfront_origin_access_control.web.id
  }

  enabled             = true
  default_root_object = "index.html"
  price_class         = "PriceClass_100"

  default_cache_behavior {
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "s3-${var.bucket_name}"

    forwarded_values {
      query_string = false
      cookies { forward = "none" }
    }
  }

  restrictions {
    geo_restriction { restriction_type = "none" }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

PriceClass_100 limita la distribución a edge locations de Norteamérica y Europa — suficiente para la mayoría de webs y más barato que la distribución global. Si tu audiencia es global, cámbialo a PriceClass_All.

Dominio propio con HTTPS (Terraform)

El código incluye soporte para dominio personalizado. Descomenta las variables domain_name y acm_certificate_arn en tu terraform.tfvars.

El certificado ACM debe estar en us-east-1 — es un requisito de CloudFront, independientemente de dónde tengas el resto de la infraestructura. Créalo a mano en la consola de ACM antes de hacer el apply, luego añade el ARN a tus variables.

Una vez desplegado, crea un registro CNAME en tu DNS apuntando tu dominio al cloudfront_domain que aparece en los outputs.

Destruir la infraestructura

# Primero vacía el bucket (Terraform no puede borrar buckets con contenido)
aws s3 rm s3://TU_BUCKET_NAME --recursive

# Luego destruye la infraestructura
terraform destroy

Terraform eliminará la distribución CloudFront y el bucket. La distribución puede tardar varios minutos en deshabilitarse antes de poder borrarse.

Comparativa real de costes

                     Hosting tradicional    S3 + CloudFront
─────────────────────────────────────────────────────────────
Coste mensual        15-20 €                < 0,50 €
HTTPS                Manual / cPanel        Automático (ACM)
CDN global           Extra o no incluida    Incluida (400+ PoP)
Escalabilidad        Limitada por plan      Ilimitada
Mantenimiento        Actualizaciones, SSH   Ninguno
Uptime SLA           Variable               99,9 %+ (AWS)

Descarga el código Terraform

Todos los archivos mencionados en este post están disponibles para descargar. El paquete incluye main.tf, variables.tf, outputs.tf, providers.tf y el script deploy.sh, listos para adaptar a tu proyecto.

Descargar terraform-cloudfront-s3.zip Ver en GitHub

Conclusión

S3 + CloudFront es objetivamente mejor que cualquier hosting compartido para webs estáticas en todos los ejes que importan: rendimiento, coste, fiabilidad y mantenimiento cero. La configuración manual cuesta unas horas la primera vez. Con Terraform, la segunda vez son tres comandos.

La pregunta relevante no es si merece la pena. Es cuántos meses llevas pagando 15€ por algo que podría costarte 50 céntimos.

Con Terraform defines la infraestructura en archivos .tf, la subes a un repositorio y cualquiera puede levantar el mismo entorno exacto con tres comandos. Es la diferencia entre una receta escrita y "lo hago de memoria".

Infrastructure as Code no es solo automatización. Es documentación ejecutable que nunca queda desactualizada.

Lo que vas a desplegar

El código de este post crea automáticamente:

  • Un bucket S3 privado para los archivos de la web
  • Una distribución de CloudFront con OAC (Origin Access Control)
  • La bucket policy que permite a CloudFront leer S3
  • Redirección automática de HTTP a HTTPS
  • Soporte para dominio propio y certificado ACM (opcional)

Si ya leíste el post anterior sobre S3 + CloudFront, esto es exactamente lo mismo pero sin tocar la consola de AWS.

Requisitos previos

Necesitas tener instalado:

  • Terraform ≥ 1.5 — descárgalo en terraform.io
  • AWS CLI configurado con credenciales que tengan permisos de S3, CloudFront, IAM y ACM

Verifica que todo está en orden:

terraform -version
aws sts get-caller-identity

Si ambos comandos responden sin errores, estás listo.

Estructura de archivos

El proyecto Terraform tiene esta estructura:

terraform-cloudfront-s3/
├── main.tf          # Recursos principales: S3 + CloudFront
├── variables.tf     # Variables configurables
├── outputs.tf       # Salidas: URL de CloudFront, ID de distribución
├── providers.tf     # Configuración de AWS provider
└── deploy.sh        # Script de deploy (sync + invalidación)

Paso 1 — Configurar las variables

Edita variables.tf (o crea un archivo terraform.tfvars) con tus valores:

# terraform.tfvars
bucket_name    = "mi-web-com"          # Nombre único para el bucket S3
aws_region     = "eu-west-1"           # Región donde crear el bucket
environment    = "production"          # Etiqueta de entorno

# Opcional: dominio propio
# domain_name       = "www.midominio.com"
# acm_certificate_arn = "arn:aws:acm:us-east-1:..."

El bucket_name tiene que ser único globalmente en AWS. Si tu dominio es midominio.com, un buen nombre es midominio-com-web.

Paso 2 — Inicializar Terraform

Desde la carpeta del proyecto, inicializa los providers:

terraform init

Terraform descarga el provider de AWS y prepara el directorio. Solo hay que hacerlo la primera vez o cuando cambias las versiones de los providers.

Paso 3 — Revisar el plan

Antes de crear nada, Terraform muestra exactamente qué va a hacer:

terraform plan

Verás una lista de recursos que se van a crear: el bucket S3, la distribución CloudFront, las policies, etc. Revísalo antes de aplicar — es tu última oportunidad de detectar algo raro.

Paso 4 — Aplicar la infraestructura

terraform apply

Terraform te pedirá confirmación. Escribe yes y espera. El bucket S3 tarda segundos; la distribución de CloudFront tarda entre 5 y 15 minutos en propagarse por los edge locations de AWS.

Al terminar, verás las salidas definidas en outputs.tf:

Outputs:

cloudfront_domain     = "d3abc123xyz.cloudfront.net"
cloudfront_distribution_id = "E3XXXXXXXXXX"
s3_bucket_name        = "mi-web-com"

Paso 5 — Subir los archivos y publicar

Con la infraestructura creada, el script deploy.sh incluido hace el resto:

./deploy.sh ./dist

El script hace dos cosas: sincroniza tu carpeta local con S3 y lanza una invalidación de caché en CloudFront para que los cambios sean inmediatos. Solo necesitas pasarle la ruta a tu carpeta de build.

Cómo funciona el código por dentro

El bucket S3 (main.tf)

resource "aws_s3_bucket" "web" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_s3_bucket_public_access_block" "web" {
  bucket = aws_s3_bucket.web.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

El bucket se crea completamente privado. El acceso público bloqueado por las cuatro vías. CloudFront accede mediante OAC, no mediante URL pública de S3.

La distribución CloudFront

resource "aws_cloudfront_distribution" "web" {
  origin {
    domain_name              = aws_s3_bucket.web.bucket_regional_domain_name
    origin_id                = "s3-${var.bucket_name}"
    origin_access_control_id = aws_cloudfront_origin_access_control.web.id
  }

  enabled             = true
  default_root_object = "index.html"
  price_class         = "PriceClass_100"

  default_cache_behavior {
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "s3-${var.bucket_name}"

    forwarded_values {
      query_string = false
      cookies { forward = "none" }
    }
  }

  restrictions {
    geo_restriction { restriction_type = "none" }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

PriceClass_100 limita la distribución a edge locations de Norteamérica y Europa — suficiente para la mayoría de webs y más barato que la distribución global. Si tu audiencia es global, cámbialo a PriceClass_All.

Dominio propio con HTTPS

El código incluye soporte para dominio personalizado. Descomenta las variables domain_name y acm_certificate_arn en tu terraform.tfvars.

El certificado ACM debe estar en us-east-1 — es un requisito de CloudFront, independientemente de dónde tengas el resto de la infraestructura. Créalo a mano en la consola de ACM (o añade el recurso aws_acm_certificate al código) antes de hacer el apply.

Una vez desplegado, crea un registro CNAME en tu DNS apuntando tu dominio al cloudfront_domain que aparece en los outputs.

Destruir la infraestructura

Si en algún momento quieres eliminar todo lo creado:

# Primero vacía el bucket (Terraform no puede borrar buckets con contenido)
aws s3 rm s3://TU_BUCKET_NAME --recursive

# Luego destruye la infraestructura
terraform destroy

Terraform eliminará la distribución CloudFront y el bucket. La distribución puede tardar varios minutos en deshabilitarse antes de poder borrarse.

Descarga el código

Todos los archivos mencionados en este post están disponibles para descargar. El paquete incluye main.tf, variables.tf, outputs.tf, providers.tf y el script deploy.sh, listos para adaptar a tu proyecto.

Descargar terraform-cloudfront-s3.zip Ver en GitHub

Conclusión

Con Terraform, la infraestructura que antes configurabas clic a clic pasa a ser código versionado, revisable y replicable. La primera vez tarda lo mismo que hacerlo a mano. La segunda vez son tres comandos.

Si ya tienes el stack de S3 + CloudFront montado a mano y quieres pasarlo a Terraform, existe el comando terraform import para importar recursos existentes — pero eso da para otro post.