Primera vez usando AWS para algo más que la consola. Documentando para no olvidar cómo lo hice.
Subí mi CV/portfolio que tenía en GitHub Pages a AWS usando S3 y CloudFront. La idea era aprender cómo funciona AWS sin complicarme mucho, y sin gastar dinero (todo dentro de la capa gratuita).
URL final: dmbykj4puqhhs.cloudfront.net
GitHub Pages (original): Eduardo Cruz García – Presentación
Porque quería aprender. GitHub Pages funciona perfecto para esto, pero quería:
- Entender cómo funciona AWS y los despliegues con archivos sencillos
- Practicar con servicios AWS reales
- Tener algo en mi CV que diga "tengo experiencia con AWS" (aunque sea básica)
- Ver si era tan complicado como dicen y perderle el miedo
Spoiler: No es tan difícil, pero hay que leer bien la documentación.
- S3: Para guardar los archivos (HTML, CSS, JS, imágenes)
- CloudFront: Para servir el contenido con HTTPS y que sea más rápido
- OAC: Para que solo CloudFront pueda acceder al bucket (seguridad básica)
# Crear bucket
aws s3 mb s3://cv.ecg.practice2025 --region us-east-1O desde la consola:
- S3 → Create bucket
- Configurar:
- Nombre:
cv.ecg.practice2025(tiene que ser único) - Region:
us-east-1 - Block Public Access: Dejar activado (esto me confundió al inicio)
- Nombre:
- Create bucket
cd mi-carpeta-del-cv/
# Subir todo
aws s3 sync . s3://cv.ecg.practice2025/ \
--exclude ".git/*" \
--exclude "README.md"
# Verificar que subió
aws s3 ls s3://cv.ecg.practice2025/ --recursiveEsto fue lo más confuso. En la consola:
CloudFront → Create distribution
Configuración importante:
Origin Settings:
Origin domain: cv.ecg.practice2025.s3.us-east-1.amazonaws.com
(NO usar el website endpoint, usar el REST API endpoint)
Origin access: Origin access control settings (recommended)
→ Create new OAC
→ Me generó una política que copié después
Cache Behavior:
Viewer protocol policy: Redirect HTTP to HTTPS
Settings:
Default root object: index.html
WAF: Do not enable (para no gastar)
Esperé como 15 minutos a que se desplegara.
CloudFront me dio una política en un banner azul después de crear la distribución.
Pasos:
- Copiar la política del banner
- S3 → Mi bucket → Permissions → Bucket policy
- Pegar → Save changes
La política se ve así:
{
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::cv.ecg.practice2025/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::707687890159:distribution/E1DKTWCG4KJ0JQ"
}
}
}
]
}CloudFront → Mi distribution → Error pages tab
Agregué dos custom error responses:
Error 403:
HTTP error code: 403
Response page path: /index.html
HTTP response code: 200
Error 404:
HTTP error code: 404
Response page path: /index.html
HTTP response code: 200
Abrí la URL de CloudFront y... ¡funcionó! 🎉
https://dmbykj4puqhhs.cloudfront.net
Error: Un XML diciendo "Access Denied"
Qué hice:
- Verificar que la bucket policy estaba correcta
- Asegurarme de NO tener "Static website hosting" habilitado en S3
- Invalidar el caché de CloudFront
aws cloudfront create-invalidation \
--distribution-id E1DKTWCG4KJ0JQ \
--paths "/*"Problema: Actualicé archivos pero seguía viendo lo viejo
Solución: Invalidar caché siempre que actualices contenido
aws cloudfront create-invalidation \
--distribution-id E1DKTWCG4KJ0JQ \
--paths "/*"# 1. Subir cambios a S3
aws s3 sync . s3://cv.ecg.practice2025/ --delete
# 2. Invalidar caché de CloudFront
aws cloudfront create-invalidation \
--distribution-id E1DKTWCG4KJ0JQ \
--paths "/*"
# Esperar 1-2 minutos y refrescar con Ctrl+F5Para no generar costos después de la práctica:
# 1. Deshabilitar CloudFront (desde la consola)
# CloudFront → Distribution → General → Edit
# Distribution state: Disabled → Save changes
# ESPERAR 15-20 minutos hasta que status sea "Deployed"
# 2. Eliminar distribución
# CloudFront → Distribution → Delete
# (solo aparece después de estar Disabled)
# 3. Vaciar bucket S3
aws s3 rm s3://cv.ecg.practice2025 --recursive
# 4. Eliminar bucket S3
aws s3 rb s3://cv.ecg.practice2025
# 5. Verificar en la consola que no queden recursosConfiguré una alarma para que me avise si paso de $1 USD:
aws cloudwatch put-metric-alarm \
--alarm-name billing-alarm-1usd \
--alarm-description "Alerta si el costo supera $1" \
--metric-name EstimatedCharges \
--namespace AWS/Billing \
--statistic Maximum \
--period 21600 \
--evaluation-periods 1 \
--threshold 1.0 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=Currency,Value=USD| Servicio | Límite Gratuito | Mi Uso |
|---|---|---|
| S3 Storage | 5GB | ~5MB |
| S3 Requests | 20,000 GET/mes | ~1,000/mes |
| CloudFront Transfer | 1TB/mes | ~10GB/mes |
| CloudFront Requests | 10M/mes | ~50,000/mes |
Resultado actual: $0.00 mensuales
Después del free tier: Estimado ~$0.50/mes para un sitio pequeño con tráfico bajo.
✅ S3 es solo storage, no es un servidor web
✅ CloudFront es el que sirve el contenido como CDN/servidor web
✅ OAC es la forma moderna de dar acceso (no usar OAI que es legacy)
✅ NUNCA hacer el bucket público, CloudFront accede internamente con OAC
✅ El caché de CloudFront es agresivo (24h por defecto, hay que invalidar)
❓ Por qué NO usar Static Website Hosting
Respuesta: Porque no es compatible con OAC
❓ La diferencia entre website endpoint y REST endpoint
Respuesta: Website endpoint es para acceso público directo, REST endpoint es para CloudFront
❓ Por qué tardaba tanto en desplegarse CloudFront
Respuesta: Tiene que propagarse a 200+ edge locations globalmente
🤔 Cómo optimizar costos más allá de lo básico
🤔 Lambda@Edge (suena cool pero no lo necesito aún)
🤔 Diferencias entre todas las cache policies disponibles
🤔 Cuándo conviene usar CloudFront Functions vs Lambda@Edge
| AWS (S3 + CloudFront) | GitHub Pages | |
|---|---|---|
| Dificultad | Media | Muy fácil |
| Tiempo setup | ~30 min | ~2 min |
| Costo | $0 (por ahora) | $0 siempre |
| Performance | Mejor (CDN global) | Suficientemente bueno |
| Control | Total | Limitado |
| Aprendizaje | Mucho | Poco |
Conclusión: Para mi CV no necesitaba AWS, pero valió la pena el aprendizaje.
#!/bin/bash
BUCKET="cv.ecg.practice2025"
DIST_ID="E1DKTWCG4KJ0JQ"
echo "📤 Subiendo a S3..."
aws s3 sync . s3://$BUCKET --delete
echo "🔄 Invalidando caché..."
aws cloudfront create-invalidation \
--distribution-id $DIST_ID \
--paths "/*"
echo "✅ Listo!"aws ce get-cost-and-usage \
--time-period Start=2024-11-01,End=2024-11-30 \
--granularity MONTHLY \
--metrics BlendedCostaws cloudfront list-invalidations \
--distribution-id E1DKTWCG4KJ0JQ- Documentación oficial de S3
- Documentación de CloudFront
- AWS CLI Reference
- Stack Overflow (obvio)
- YouTube: varios tutoriales que más o menos explicaban esto
Si le sigo a esto, podría:
- Agregar un dominio custom (comprar uno barato)
- Configurar GitHub Actions para deploy automático
- Meter todo en Terraform (para que sea reproducible)
- Aprender más sobre CloudWatch y métricas
- Probar Lambda@Edge para alguna funcionalidad dinámica
- Implementar cache busting con versionado de assets
Pero por ahora, esto funciona y cumple el objetivo de aprender.
Esta fue una práctica de aprendizaje. No soy experto en AWS, solo quería entender cómo funcionan estos servicios básicos sin morir en el intento.
Si encuentras algo mal o tienes sugerencias, ¡son bienvenidas!
- Tiempo total: ~2 horas (incluyendo errores y labor de investigación)
- Nivel: Principiante en AWS
- Status: Funcionando ✅
- Errores encontrados: 3 (AccessDenied, caché, bucket policy)
- Veces que consulté la documentación: Muchas 😅
Fecha: Noviembre 2025
Tags: aws s3 cloudfront aprendizaje práctica beginner-friendly cdn static-site
P.D.: No olvidar borrar todo después de la práctica para no generar costos! (O al menos configurar billing alerts)










