Desplegar una Web Estática en AWS S3
Si tienes una página web estática (generada con HTML, CSS y JS o frameworks como Jekyll, Hugo o Astro), puedes publicarla en Internet gratis usando los servicios serverless de AWS. Aquí te ayudo a cómo hacerlo paso a paso, ya sea que tengas una cuenta Free Tier o una cuenta de estudiante en AWS Academy.
Para ello, usaremos:
- Amazon S3: Servicio de almacenamiento de archivos.
1. Entendiendo AWS Serverless
AWS Serverless en un modelo de desarrollo nativo de la nube que permite a los desarrolladores crear y ejecutar aplicaciones sin preocuparse por los servidores. Mientras los servidores sigan existiendo, AWS se encarga de su configuración, mantenimiento y escalado. Solo tienes que centrarte en el desarrollo de tu sitio web.
Serverless no significa que no haya servidores, sino que no tienes que administrarlos. AWS se encarga de todo y solo tienes que subir tus archivos y dejar que el servicio los sirva al mundo sin preocuparse por infraestructura.
2. ¿Qué son los buckets de Amazon S3?
En Amazon S3, un bucket es un contenedor donde se almacenan archivos en la nube. Estos archivos pueden ser de cualquier tipo, como documentos, imágenes, videos o recursos de un sitio web.
Podemos pensar en un bucket como si fuera una carpeta principal en internet, donde se organizan y guardan los archivos.
2.1 Cómo funciona un bucket
Dentro de un bucket se almacenan objetos, que son los archivos que subimos al servicio. Cada objeto incluye:
- el archivo (por ejemplo
imagen.pngoindex.html) - un nombre o clave
- metadatos asociados al archivo
Por ejemplo, un bucket podría contener archivos como el siguiente proyecto de ejemplo:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mis Certificados</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header>
<h1>Mis Certificados</h1>
<p class="subtitle">Una colección de mis logros y certificaciones profesionales obtenidas a lo largo de mi carrera.</p>
</header>
<div class="filters">
<button class="filter-btn active" data-filter="all">Todos</button>
<button class="filter-btn" data-filter="tecnologia">Tecnología</button>
<button class="filter-btn" data-filter="desarrollo">Desarrollo</button>
<button class="filter-btn" data-filter="diseño">Diseño</button>
<button class="filter-btn" data-filter="negocios">Negocios</button>
<button class="filter-btn" data-filter="idiomas">Idiomas</button>
</div>
<div class="certificates-grid" id="certificates-container">
<!-- Certificados se cargarán aquí con JavaScript -->
</div>
<footer>
<p>© <span id="current-year"></span> Mi Portafolio de Certificados</p>
<p>Total de certificados: <span id="certificate-count">0</span></p>
</footer>
</div>
<!-- Modal para vista detallada -->
<div class="modal" id="certificate-modal">
<div class="modal-content">
<div class="close-modal" id="close-modal">×</div>
<div class="modal-body" id="modal-body">
<!-- Contenido del modal se cargará con JavaScript -->
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
// Datos de los certificados
const certificates = [
{
id: 1,
title: "Desarrollo Web Full Stack",
issuer: "Coursera",
date: "Junio 2023",
description: "Certificación completa en desarrollo web full stack, incluyendo tecnologías frontend y backend, bases de datos y despliegue de aplicaciones.",
tags: ["tecnologia", "desarrollo"],
category: "tecnologia",
icon: "fas fa-laptop-code"
},
{
id: 2,
title: "Diseño UX/UI Avanzado",
issuer: "Google",
date: "Marzo 2023",
description: "Curso avanzado de diseño de experiencia de usuario e interfaz de usuario, incluyendo investigación, prototipado y pruebas de usabilidad.",
tags: ["diseño", "ux", "ui"],
category: "diseño",
icon: "fas fa-palette"
},
{
id: 3,
title: "JavaScript Moderno ES6+",
issuer: "Udemy",
date: "Enero 2023",
description: "Dominio de las características modernas de JavaScript, incluyendo ES6+, promesas, async/await y patrones de diseño.",
tags: ["tecnologia", "desarrollo"],
category: "tecnologia",
icon: "fab fa-js-square"
},
{
id: 4,
title: "Inglés Profesional C1",
issuer: "Cambridge University",
date: "Diciembre 2022",
description: "Certificación de nivel C1 (Avanzado) de inglés según el Marco Común Europeo de Referencia para las lenguas.",
tags: ["idiomas", "inglés"],
category: "idiomas",
icon: "fas fa-language"
},
{
id: 5,
title: "Gestión de Proyectos Ágiles",
issuer: "Scrum.org",
date: "Octubre 2022",
description: "Certificación en metodologías ágiles y Scrum para la gestión eficiente de proyectos de desarrollo de software.",
tags: ["negocios", "gestión"],
category: "negocios",
icon: "fas fa-tasks"
},
{
id: 6,
title: "React.js Avanzado",
issuer: "Meta",
date: "Agosto 2022",
description: "Curso avanzado de React.js que cubre hooks, context API, renderizado del lado del servidor y patrones avanzados.",
tags: ["tecnologia", "desarrollo"],
category: "desarrollo",
icon: "fab fa-react"
},
{
id: 7,
title: "Diseño Gráfico Digital",
issuer: "Adobe",
date: "Mayo 2022",
description: "Certificación en herramientas de diseño gráfico digital, incluyendo Photoshop, Illustrator y After Effects.",
tags: ["diseño", "gráfico"],
category: "diseño",
icon: "fas fa-paint-brush"
},
{
id: 8,
title: "Análisis de Datos con Python",
issuer: "IBM",
date: "Febrero 2022",
description: "Especialización en análisis de datos utilizando Python, pandas, numpy y visualización con matplotlib y seaborn.",
tags: ["tecnologia", "datos"],
category: "tecnologia",
icon: "fas fa-chart-line"
}
];
// Elementos del DOM
const certificatesContainer = document.getElementById('certificates-container');
const filterButtons = document.querySelectorAll('.filter-btn');
const modal = document.getElementById('certificate-modal');
const closeModal = document.getElementById('close-modal');
const modalBody = document.getElementById('modal-body');
const certificateCount = document.getElementById('certificate-count');
const currentYear = document.getElementById('current-year');
// Estado de filtro activo
let activeFilter = 'all';
// Inicializar la página
function init() {
// Establecer el año actual
currentYear.textContent = new Date().getFullYear();
// Mostrar todos los certificados
displayCertificates(certificates);
// Agregar eventos a los botones de filtro
filterButtons.forEach(button => {
button.addEventListener('click', () => {
// Remover clase activa de todos los botones
filterButtons.forEach(btn => btn.classList.remove('active'));
// Agregar clase activa al botón clickeado
button.classList.add('active');
// Actualizar filtro activo
activeFilter = button.getAttribute('data-filter');
// Filtrar certificados
filterCertificates(activeFilter);
});
});
// Cerrar modal al hacer clic en la X
closeModal.addEventListener('click', () => {
modal.style.display = 'none';
});
// Cerrar modal al hacer clic fuera del contenido
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.style.display = 'none';
}
});
}
// Mostrar certificados en el grid
function displayCertificates(certificatesToShow) {
certificatesContainer.innerHTML = '';
certificateCount.textContent = certificatesToShow.length;
certificatesToShow.forEach(cert => {
const card = document.createElement('div');
card.className = 'certificate-card';
card.dataset.id = cert.id;
card.dataset.category = cert.category;
card.innerHTML = `
<div class="certificate-img">
<i class="${cert.icon}"></i>
</div>
<div class="certificate-info">
<h3 class="certificate-title">${cert.title}</h3>
<div class="certificate-issuer">
<i class="fas fa-building"></i>
${cert.issuer}
</div>
<div class="certificate-date">
<i class="far fa-calendar-alt"></i>
${cert.date}
</div>
<div class="certificate-tags">
${cert.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
</div>
`;
// Agregar evento para abrir modal
card.addEventListener('click', () => openCertificateModal(cert));
certificatesContainer.appendChild(card);
});
}
// Filtrar certificados por categoría
function filterCertificates(filter) {
if (filter === 'all') {
displayCertificates(certificates);
} else {
const filteredCertificates = certificates.filter(cert =>
cert.category === filter || cert.tags.includes(filter)
);
displayCertificates(filteredCertificates);
}
}
// Abrir modal con detalles del certificado
function openCertificateModal(certificate) {
modalBody.innerHTML = `
<div class="modal-img">
<i class="${certificate.icon}" style="font-size: 6rem; color: #4a6491;"></i>
</div>
<h2 class="modal-title">${certificate.title}</h2>
<div class="modal-details">
<div class="detail-item">
<span class="detail-label">Institución</span>
<span>${certificate.issuer}</span>
</div>
<div class="detail-item">
<span class="detail-label">Fecha de emisión</span>
<span>${certificate.date}</span>
</div>
<div class="detail-item">
<span class="detail-label">Categoría</span>
<span>${certificate.category.charAt(0).toUpperCase() + certificate.category.slice(1)}</span>
</div>
<div class="detail-item">
<span class="detail-label">ID de certificado</span>
<span>#${certificate.id.toString().padStart(3, '0')}</span>
</div>
</div>
<div class="modal-description">
<p>${certificate.description}</p>
</div>
<div class="certificate-tags" style="margin-top: 20px;">
${certificate.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
`;
modal.style.display = 'flex';
}
// Inicializar cuando el DOM esté cargado
document.addEventListener('DOMContentLoaded', init);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f8f9fa;
color: #333;
line-height: 1.6;
/* Fondo con imagen elegante */
background-image: url('background.jpeg');
background-size: cover;
background-position: center;
background-attachment: fixed;
background-repeat: no-repeat;
position: relative;
min-height: 100vh;
}
/* Capa overlay para mejorar legibilidad */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(248, 249, 250, 0.62) 0%, rgba(248, 249, 250, 0.85) 100%);
z-index: -1;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
position: relative;
z-index: 1;
}
/* Header */
header {
text-align: center;
padding: 40px 0;
background: linear-gradient(135deg, rgba(44, 62, 80, 0.95), rgba(74, 100, 145, 0.95));
color: white;
border-radius: 10px;
margin-bottom: 40px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
font-weight: 300;
}
.subtitle {
font-size: 1.1rem;
opacity: 0.9;
max-width: 600px;
margin: 0 auto;
}
/* Filtros */
.filters {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 30px;
}
.filter-btn {
padding: 10px 20px;
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(221, 221, 221, 0.5);
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
backdrop-filter: blur(5px);
}
.filter-btn:hover {
background-color: rgba(240, 240, 240, 0.95);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.filter-btn.active {
background-color: rgba(44, 62, 80, 0.95);
color: white;
border-color: rgba(44, 62, 80, 0.8);
}
/* Grid de certificados */
.certificates-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 30px;
margin-bottom: 40px;
}
.certificate-card {
background-color: rgba(255, 255, 255, 0.95);
border-radius: 10px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
cursor: pointer;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
}
.certificate-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
background-color: rgba(255, 255, 255, 0.98);
}
.certificate-img {
height: 200px;
background: linear-gradient(135deg, rgba(233, 236, 239, 0.9), rgba(206, 212, 218, 0.9));
display: flex;
align-items: center;
justify-content: center;
color: #4a6491;
position: relative;
overflow: hidden;
}
.certificate-img::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.3) 50%, transparent 70%);
animation: shine 3s infinite linear;
}
@keyframes shine {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.certificate-img img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.certificate-card:hover .certificate-img img {
transform: scale(1.05);
}
.certificate-img i {
font-size: 4rem;
z-index: 1;
position: relative;
}
.certificate-info {
padding: 20px;
}
.certificate-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 8px;
color: #2c3e50;
}
.certificate-issuer {
color: #4a6491;
font-weight: 500;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 5px;
}
.certificate-date {
color: #6c757d;
font-size: 0.9rem;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 5px;
}
.certificate-tags {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.tag {
background-color: rgba(233, 236, 239, 0.8);
color: #495057;
padding: 4px 10px;
border-radius: 50px;
font-size: 0.8rem;
backdrop-filter: blur(5px);
}
/* Modal para vista detallada */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85);
z-index: 1000;
align-items: center;
justify-content: center;
padding: 20px;
backdrop-filter: blur(5px);
}
.modal-content {
background-color: rgba(255, 255, 255, 0.98);
border-radius: 10px;
max-width: 800px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
position: relative;
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(20px);
}
.close-modal {
position: absolute;
top: 15px;
right: 20px;
font-size: 1.8rem;
color: #333;
cursor: pointer;
z-index: 10;
background: rgba(255, 255, 255, 0.9);
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.close-modal:hover {
background: rgba(44, 62, 80, 0.9);
color: white;
transform: rotate(90deg);
}
.modal-body {
padding: 30px;
}
.modal-img {
width: 100%;
height: 300px;
background: linear-gradient(135deg, rgba(233, 236, 239, 0.9), rgba(206, 212, 218, 0.9));
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
position: relative;
}
.modal-img::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.2) 50%, transparent 70%);
animation: shine 3s infinite linear;
}
.modal-img img {
width: 100%;
height: 100%;
object-fit: contain;
z-index: 1;
position: relative;
}
.modal-title {
font-size: 1.8rem;
color: #2c3e50;
margin-bottom: 15px;
}
.modal-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 25px;
}
.detail-item {
display: flex;
flex-direction: column;
padding: 15px;
background-color: rgba(248, 249, 250, 0.7);
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.detail-label {
font-weight: 600;
color: #4a6491;
margin-bottom: 5px;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.modal-description {
line-height: 1.7;
color: #555;
padding: 20px;
background-color: rgba(248, 249, 250, 0.5);
border-radius: 8px;
border-left: 4px solid #4a6491;
}
/* Footer */
footer {
text-align: center;
padding: 20px 0;
color: #6c757d;
border-top: 1px solid rgba(233, 236, 239, 0.5);
margin-top: 40px;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 10px;
backdrop-filter: blur(10px);
}
/* Responsive */
@media (max-width: 768px) {
.certificates-grid {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
h1 {
font-size: 2rem;
}
.modal-body {
padding: 20px;
}
body {
background-attachment: scroll;
}
}
@media (max-width: 480px) {
.certificates-grid {
grid-template-columns: 1fr;
}
.filters {
justify-content: flex-start;
overflow-x: auto;
padding-bottom: 10px;
}
.modal-img {
height: 200px;
}
}
/* Animación de carga suave */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.certificate-card {
animation: fadeIn 0.5s ease forwards;
}
/* Desplazamiento suave */
html {
scroll-behavior: smooth;
}
Selecciona un archivo para ver su contenido
2.2 Características importantes de los buckets
- Cada bucket tiene un nombre único global dentro de Amazon Web Services.
- Los archivos pueden ser privados o públicos según la configuración de permisos.
- Permiten almacenar grandes cantidades de datos de forma escalable.
2.3 Uso común de los buckets
Los buckets de Amazon S3 se utilizan para muchas cosas, por ejemplo:
- almacenar archivos de aplicaciones
- guardar copias de seguridad
- almacenar imágenes o videos
- publicar sitios web estáticos (que lo realizaremos)
En resumen, un bucket es simplemente el contenedor donde se almacenan los archivos en Amazon S3, y es la unidad básica de organización dentro del servicio.
3. Ir a la consola de administración
3.1 Ingresar a tu cuenta de AWS
Si aún no tienes una cuenta, puedes registrate para acceder a una cuenta Free Tier en https://aws.amazon.com/free/.
3.2 Ingresar desde AWS Academy
Si eres estudiante en AWS Academy, inicia el AWS Learner Lab desde el portal de AWS Academy.
3.3 Buscar el servicio en el panel de inicio
Desde la consola principal de AWS, busca “S3” en la barra de búsqueda de servicios y haz clic en el servicio S3:
4. Crear un nuevo Bucket
En la página de inicio de S3, encontrará un botón para crear un nuevo bucket:
Puedes crear el bucket con la configuración predeterminada, pero ten en cuenta que el nombre no podrá cambiarse en el futuro.
4.1 Configuración general del bucket
Aquí definimos el nombre y el tipo de uso:
4.2 Configuración de acceso al bucket
Para un sitio web en S3 debemos permitir acceso público porque los visitantes necesitan poder leer los archivos HTML, CSS y JavaScript del bucket para que la página se muestre en el navegador.
4.3 Finalizar con la creación del bucket
El resto de la configuración la dejamos tal cual y procedemos a crear el bucket.
5. Cargar los archivos
Después de crear el contenedor, haz clic en él para acceder a la opción de subir archivos. Haz clic en el botón que dice Cargar.
Luego, sube los archivos arrastrando la carpeta.
6. Confugurar la política de acceso
Aunque hayamos deshabilitado el bloqueo de acceso público, los objetos del bucket siguen siendo privados por defecto en Amazon S3.
En el siguiente ejemplo podemos observar que la URL del objeto index.html, que debería llevarnos a la página web, no se mostrará debido a la falta de permisos.
Para solucionarlo, debemos ir al bucket y luego hacer clic sobre la pestaña Permisos y bajar hasya el botón para Editar las políticas del bucket.










