AWS EC2 Deployment Guide for Web Applications

Complete AWS EC2 deployment guide: instance selection, security groups, Node.js deployment, Nginx reverse proxy, SSL, auto-scaling, CloudWatch monitoring, and cost optimization.

E
ECOSIRE Research and Development Team
|19 de marzo de 202611 min de lectura2.4k Palabras|

Guía de implementación de AWS EC2 para aplicaciones web

EC2 sigue siendo la opción informática más flexible de AWS para aplicaciones web que necesitan un rendimiento constante, pilas de software personalizadas y precios predecibles. Si bien ECS, EKS y Lambda reciben más atención en el mundo nativo de la nube, EC2 le brinda un servidor que usted controla por completo: sin complejidad de orquestación de contenedores, sin latencia de arranque en frío, sin costos de invocación sorpresa.

Esta guía cubre la implementación de una aplicación web Node.js de producción en EC2: selección de instancias, configuración del grupo de seguridad, implementación de aplicaciones, proxy inverso de Nginx, SSL con Cloudflare, monitoreo con CloudWatch y los patrones de mantenimiento continuo que mantienen saludable una implementación de EC2.

Conclusiones clave

  • t3.large es el punto de partida correcto para una implementación completa de Node.js + PostgreSQL
  • Utilice Ubuntu 24.04 LTS: compatible hasta 2029, ampliamente documentado, excelente disponibilidad de paquetes
  • La IP elástica es obligatoria: su IP de EC2 cambia en cada parada/inicio sin ella
  • Los grupos de seguridad tienen estado: solo necesita reglas de entrada; la salida normalmente es permitir todo
  • Almacene su clave SSH de implementación en un archivo .pem separado; nunca lo comprometas con git
  • Utilice la conexión de instancia EC2 o el Administrador de sesión en lugar de SSH directo cuando sea posible (administración de claves cero)
  • El agente de CloudWatch le brinda métricas de memoria y disco (no disponible de forma predeterminada)
  • Las instancias reservadas o los planes de ahorro reducen los costos de EC2 entre un 40 y un 60 % frente a los bajo demanda

Selección de instancia

El tipo de instancia correcto depende de su carga de trabajo:

Carga de trabajoInstancia recomendadaCPU virtualRAMCosto/mes
Light (blog, pequeña aplicación)t3.pequeño22 GB~$18
Medio (aplicación completa)t3.medio24 GB~$35
Producción (multiservicio)t3.grande28 GB~$70
Pesado (API de alto tráfico)c6i.xlarge48 GB~$140
Memoria pesada (ML/caché)r6i.grande216GB~$120

Para un monorepo con 5 aplicaciones Node.js (Next.js, NestJS, Docusaurus, 2 sitios de marca) más infraestructura Docker (PostgreSQL, Redis, Authentik), t3.large es la configuración mínima viable. La familia t3 utiliza un rendimiento "ampliable": el rendimiento es excelente durante el funcionamiento normal, pero un nivel elevado sostenido de CPU provoca una aceleración.

Para cargas de trabajo de CPU consistentemente altas (procesamiento de video, inferencia de aprendizaje automático, criptografía pesada), use instancias c6i (optimizadas para computación) en su lugar.


Configuración inicial del servidor

Después de iniciar su instancia EC2 con Ubuntu 24.04:

# Connect via SSH
ssh -i your-key.pem [email protected]

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install essential tools
sudo apt install -y \
  git curl wget unzip \
  build-essential \
  nginx \
  certbot python3-certbot-nginx \
  docker.io docker-compose-plugin \
  htop ncdu iotop

# Install Node.js via NVM (allows easy version management)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
nvm alias default 22

# Install pnpm
curl -fsSL https://get.pnpm.io/install.sh | sh -
source ~/.bashrc

# Install PM2 globally
npm install -g pm2

# Install PM2 log rotation immediately
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true

Configuración del grupo de seguridad

El grupo de seguridad es el firewall de su instancia EC2. Configúrelo con cuidado:

Inbound Rules:
┌─────────┬──────────┬─────────────┬──────────────────────────────────┐
│ Type    │ Protocol │ Port Range  │ Source                           │
├─────────┼──────────┼─────────────┼──────────────────────────────────┤
│ SSH     │ TCP      │ 22          │ Your IP only (not 0.0.0.0/0!)    │
│ HTTP    │ TCP      │ 80          │ 0.0.0.0/0 (Cloudflare IPs only)  │
│ HTTPS   │ TCP      │ 443         │ 0.0.0.0/0 (Cloudflare IPs only)  │
└─────────┴──────────┴─────────────┴──────────────────────────────────┘

Note: Internal app ports (3000, 3001, 3002, etc.) should NOT be
      in the security group — traffic goes through Nginx only

Para dominios proxy de Cloudflare, restrinja HTTP/HTTPS a los rangos de IP de Cloudflare:

# Cloudflare IPv4 ranges — restrict port 80/443 source to these
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
104.16.0.0/13
104.24.0.0/14
108.162.192.0/18
131.0.72.0/22
141.101.64.0/18
162.158.0.0/15
172.64.0.0/13
173.245.48.0/20
188.114.96.0/20
190.93.240.0/20
197.234.240.0/22
198.41.128.0/17

Esto evita el acceso directo a su servidor, evitando la protección WAF y DDoS de Cloudflare.


Implementación de aplicaciones

# Create application directory
sudo mkdir -p /opt/ecosire/app
sudo chown ubuntu:ubuntu /opt/ecosire/app

# Clone the repository
git clone https://github.com/your-org/your-repo.git /opt/ecosire/app
cd /opt/ecosire/app

# Create .env.local from template
cp .env.example .env.local
# Edit with production values
nano .env.local

# Install dependencies
pnpm install --frozen-lockfile

# Build everything
npx turbo run build

# Run database migrations
pnpm --filter @ecosire/db db:migrate

# Start infrastructure (PostgreSQL, Redis, Authentik)
docker compose -f infrastructure/docker-compose.dev.yml up -d

# Wait for services to be healthy
sleep 30

# Start Node.js applications
pm2 start ecosystem.config.cjs

# Save process list for reboot persistence
pm2 save

# Configure PM2 to start on system boot
pm2 startup
# Run the command it outputs

IP elástica y DNS

La IP pública de una instancia EC2 cambia cada vez que la detiene y la inicia. Elastic IP proporciona una IP permanente:

# In AWS Console:
# 1. EC2 > Network & Security > Elastic IPs
# 2. Allocate Elastic IP address
# 3. Associate it with your instance

# Your IP is now permanent — update Cloudflare DNS to point to it
# A record: ecosire.com → 13.223.116.181 (your Elastic IP)
# A record: api.ecosire.com → 13.223.116.181
# A record: auth.ecosire.com → 13.223.116.181

En Cloudflare, establezca estos registros en "Proxied" (nube naranja) para el tráfico web. El proxy de Cloudflare oculta su IP EC2 real, brindando protección DDoS.


Almacenamiento: Gestión de volúmenes de EBS

Las instancias EC2 incluyen un volumen raíz de EBS. Para la producción, necesita suficiente espacio para crear artefactos, registros y datos de Docker:

# Check current disk usage
df -h

# Check which directories are consuming space
ncdu /

# Typical space requirements for a 5-app monorepo:
# - /opt/ecosire/app: ~2GB (code + node_modules + .next builds)
# - Docker data (/var/lib/docker): ~5GB
# - PM2 logs (/var/log/pm2): ~1GB (with rotation)
# - System: ~5GB
# Total: ~13GB minimum, recommend 30GB+ root volume

# If you need to resize an EBS volume (no downtime needed):
# 1. In AWS Console: EC2 > Volumes > Modify Volume
# 2. After resize completes, grow the filesystem:
sudo growpart /dev/xvda 1
sudo resize2fs /dev/xvda1

Monitoreo de CloudWatch

EC2 proporciona métricas básicas de red y CPU de forma predeterminada. Para métricas de memoria y disco, instale el agente de CloudWatch:

# Download and install CloudWatch agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i amazon-cloudwatch-agent.deb

# Create configuration
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
// /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
{
  "agent": {
    "metrics_collection_interval": 60,
    "run_as_user": "cwagent"
  },
  "metrics": {
    "append_dimensions": {
      "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
      "ImageId": "${aws:ImageId}",
      "InstanceId": "${aws:InstanceId}",
      "InstanceType": "${aws:InstanceType}"
    },
    "metrics_collected": {
      "mem": {
        "measurement": ["mem_used_percent"],
        "metrics_collection_interval": 60
      },
      "disk": {
        "measurement": ["used_percent"],
        "metrics_collection_interval": 60,
        "resources": ["/", "/opt/ecosire"]
      }
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/pm2/ecosire-api.err.log",
            "log_group_name": "/ec2/ecosire/api-errors",
            "log_stream_name": "{instance_id}"
          },
          {
            "file_path": "/var/log/nginx/ecosire-error.log",
            "log_group_name": "/ec2/ecosire/nginx-errors",
            "log_stream_name": "{instance_id}"
          }
        ]
      }
    }
  }
}
# Start the agent
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config \
  -m ec2 \
  -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json \
  -s

Copias de seguridad automatizadas

Configure copias de seguridad automatizadas de PostgreSQL en S3:

# Create backup script
cat > /opt/ecosire/scripts/backup-db.sh << 'EOF'
#!/bin/bash
set -e

DATE=$(date +%Y-%m-%d-%H%M%S)
BACKUP_FILE="/tmp/ecosire-db-${DATE}.sql.gz"
S3_BUCKET="s3://your-backups-bucket/postgres"

# Dump the database (connects via Docker network)
docker exec ecosire-postgres pg_dump \
  -U ecosire \
  -d ecosire_dev \
  --no-owner \
  --no-privileges \
  | gzip > "$BACKUP_FILE"

# Upload to S3
aws s3 cp "$BACKUP_FILE" "$S3_BUCKET/"

# Clean up local file
rm "$BACKUP_FILE"

# Delete backups older than 30 days from S3
aws s3 ls "$S3_BUCKET/" \
  | awk '{print $4}' \
  | sort \
  | head -n -30 \
  | xargs -I {} aws s3 rm "$S3_BUCKET/{}" 2>/dev/null || true

echo "Backup complete: ${DATE}"
EOF

chmod +x /opt/ecosire/scripts/backup-db.sh

# Schedule daily backups at 3 AM UTC
(crontab -l 2>/dev/null; echo "0 3 * * * /opt/ecosire/scripts/backup-db.sh >> /var/log/db-backup.log 2>&1") | crontab -

Configuración de roles de IAM

Adjunte una función de IAM a su instancia EC2 para acceder al servicio de AWS (S3, CloudWatch, SES):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-products-bucket",
        "arn:aws:s3:::your-products-bucket/*",
        "arn:aws:s3:::your-backups-bucket",
        "arn:aws:s3:::your-backups-bucket/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail",
        "ses:SendRawEmail"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "cloudwatch:PutMetricData",
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

Con la función de IAM adjunta a su instancia, las llamadas al SDK de AWS utilizan las credenciales de la instancia automáticamente: no se necesita clave de acceso ni clave secreta en las variables de su entorno.


Errores y soluciones comunes

Error 1: No hay IP elástica: la IP cambia al reiniciar

Detener e iniciar (no reiniciar) una instancia EC2 asigna una nueva IP pública. Sin una IP elástica, tu DNS se rompe. Asigne y asocie una IP elástica inmediatamente después de lanzar su instancia.

Error 2: acceso SSH bloqueado

Si pierde su clave SSH o se bloquea al configurar mal los grupos de seguridad, use EC2 Instance Connect (SSH basado en navegador) desde la consola de AWS o Session Manager (requiere el agente SSM instalado, que viene con Ubuntu de forma predeterminada). Como último recurso, desconecte el volumen raíz de EBS, asócielo a otra instancia, corrija el archivo autorizado_keys y vuelva a adjuntarlo.

Error 3: quedarse sin espacio en disco durante la implementación

La caché de compilación .next y node_modules crecen sustancialmente durante el desarrollo. Supervise el uso del disco con df -h y configure una alarma de CloudWatch en disk_used_percent > 80%. El comando ncdu (ncdu /) identifica qué directorios están consumiendo espacio.

Error 4: Agotamiento de la memoria de Node.js OOM

Node.js tiene un límite de memoria predeterminado (1,4 GB en 64 bits) que puede provocar fallos de OOM en aplicaciones grandes. Configure node_args: '--max-old-space-size=1024' en su archivo de ecosistema PM2 para limitar explícitamente el uso de memoria. Establezca max_memory_restart ligeramente por encima de esto para reiniciar automáticamente si se excede.

Error 5: aceleración de la CPU T3 bajo carga sostenida

Las instancias T3 utilizan "créditos de CPU" para un rendimiento ampliable. Las operaciones extendidas con un alto consumo de CPU (compilaciones grandes, consultas intensas a bases de datos) agotan los créditos, lo que provoca una limitación del rendimiento "básico". Supervise CPUCreditBalance en CloudWatch. Si los créditos se agotan constantemente, actualice a una instancia c6i o habilite el modo "ilimitado" (costo adicional por hora de CPU por encima del valor inicial).


Preguntas frecuentes

¿Debo utilizar EC2 o un servicio administrado como AWS Elastic Beanstalk?

EC2 le brinda control total: la versión exacta de Node.js, acceso al sistema de archivos, capacidad para ejecutar contenedores Docker sidecar y configuración personalizada de Nginx. Elastic Beanstalk administra la infraestructura subyacente, pero limita sus opciones y agrega complejidad para la resolución de problemas. Para un equipo que se siente cómodo con la administración de servidores Linux, EC2 con PM2 + Nginx es más simple y predecible que las plataformas administradas. Utilice Beanstalk si desea que la plataforma maneje el escalado y la gestión del estado automáticamente.

¿Cómo manejo las implementaciones sin tiempo de inactividad en EC2?

El comando PM2 pm2 reload proporciona tiempo de inactividad cero para los procesos en modo clúster (API NestJS). Para Next.js (modo bifurcación), primero cree la nueva versión y luego vuelva a cargar PM2. Durante los pocos segundos que tarda PM2 en cambiar de proceso, Nginx pone en cola las solicitudes entrantes (con un pequeño tiempo de espera). Para lograr un verdadero tiempo de inactividad cero, utilice dos instancias EC2 detrás de un ALB (Application Load Balancer) e impleméntelas en una mientras la otra atiende el tráfico.

¿Cuándo debo utilizar el escalado automático?

El escalado automático agrega una complejidad operativa significativa: controles de estado, plantillas de lanzamiento, balanceadores de carga y consideraciones de afinidad de sesiones. Para aplicaciones con tráfico predecible, una instancia EC2 de tamaño adecuado con escalamiento vertical (tipo de instancia más grande) es más simple y, a menudo, más económica que el escalado automático horizontal. Considere el escalado automático cuando tenga picos de tráfico de más de 5 veces la línea base y el costo de ejecutar una instancia permanentemente más grande exceda la complejidad del escalado automático.

¿Cómo migro de EC2 a contenedores más adelante?

Comience por contener su aplicación con Docker (escriba un Dockerfile para cada aplicación). Pruébelo localmente con Docker Compose. Luego elige entre ECS Fargate (contenedores sin servidor, más sencillos) o EKS (Kubernetes, más potente pero complejo). La migración no es disruptiva si utiliza contenedores de forma incremental: ejecute la versión en contenedores detrás de la misma configuración de Nginx/Cloudflare, verifique el comportamiento y luego corte.

¿Cuál es la forma más rentable de ejecutar EC2 en producción?

Compre una instancia reservada de 1 año (sin pago inicial ni parcial) para su instancia básica: un 40 % más barata que la instancia según demanda. Para obtener capacidad adicional durante los picos de tráfico, utilice instancias puntuales (hasta un 90 % más baratas) si su aplicación puede soportar las interrupciones. Configure una alarma de facturación de CloudWatch al 80 % de su presupuesto mensual para detectar con anticipación los aumentos inesperados de costos. Para aplicaciones web de producción, las instancias reservadas proporcionan el mejor equilibrio entre costo y confiabilidad.


Próximos pasos

La ejecución de una aplicación web de producción en EC2 requiere atención operativa continua: parches de seguridad, administración de discos, monitoreo del rendimiento y automatización de la implementación. ECOSIRE ejecuta una instancia EC2 t3.large de producción que presta servicios a 5 aplicaciones en múltiples dominios, con copias de seguridad automatizadas, monitoreo de CloudWatch e implementaciones de PM2 sin tiempo de inactividad.

Ya sea que necesite consultoría de infraestructura de AWS, configuración de implementación de EC2 o soporte completo de DevOps para su aplicación Node.js, explore nuestros servicios para ver cómo podemos ayudarlo.

E

Escrito por

ECOSIRE Research and Development Team

Construyendo productos digitales de nivel empresarial en ECOSIRE. Compartiendo perspectivas sobre integraciones Odoo, automatización de eCommerce y soluciones empresariales impulsadas por IA.

Chatea en whatsapp