2026-03-25

Hay situaciones en las que necesitas un ambiente de desarrollo completamente aislado de tu máquina principal: probar repositorios de terceros, ejecutar código en el que no confías totalmente, trabajar con herramientas que requieren Linux nativo, o simplemente tener un desktop dev desechable que no comprometa tus credenciales, claves SSH o tokens.
La idea aquí es simple: montarlo una vez, guardarlo como imagen y restaurar bajo demanda. Cuando terminas, lo destruyes. Cuando lo necesitas de nuevo, lo recreas en minutos. ¿Y el costo? Cerca de $1/mes cuando está parado.
Día de uso:
AMI Snapshot --> Lanza EC2 --> Conecta vía DCV --> Trabaja --> Termina EC2
Entre usos:
Solo el snapshot AMI existe (~$1/mes para 20 GB)
Sin instancias corriendo, sin volúmenes EBS, sin costo de cómputo
¿Por qué este enfoque?
Si tus herramientas requieren glibc (la mayoría lo requiere), Alpine (musl libc) no es compatible.
Mejor equilibrio entre compatibilidad, documentación y ligereza.
| Distro | Desktop | RAM ociosa | Pros | Contras |
|---|---|---|---|---|
| Xubuntu 24.04 | XFCE | ~600-800 MB | Repos Ubuntu/PPAs, comunidad enorme | Un poco más pesado que Debian |
| Debian 12 + XFCE | XFCE | ~400-600 MB | Más liviano, estable | Paquetes más antiguos |
| Ubuntu 24.04 | GNOME | ~1.5-2 GB | Mejor experiencia out-of-box | Pesado, desperdicia RAM |
| Fedora 41 XFCE | XFCE | ~600-800 MB | Paquetes más recientes | dnf más lento que apt |
La estrategia: usar la AMI oficial de Ubuntu Server 24.04 (gratuita en el AWS Marketplace) e instalar XFCE encima. Desktop liviano con total compatibilidad.
Todo esto se hace una única vez en tu Mac.
brew install awscli
aws configure
# Ingresa: Access Key ID, Secret Access Key, Región (ej: us-east-1), Output (json)
Descárgalo en docs.aws.amazon.com/dcv e instala el .dmg para macOS.
aws ec2 create-key-pair \
--key-name devenv-key \
--query 'KeyMaterial' \
--output text > ~/.ssh/devenv-key.pem
chmod 400 ~/.ssh/devenv-key.pem
# Crear el security group
aws ec2 create-security-group \
--group-name devenv-sg \
--description "Safe dev environment"
# Permitir SSH (para setup inicial)
aws ec2 authorize-security-group-ingress \
--group-name devenv-sg \
--protocol tcp --port 22 \
--cidr "$(curl -s https://checkip.amazonaws.com)/32"
# Permitir DCV (puerto 8443)
aws ec2 authorize-security-group-ingress \
--group-name devenv-sg \
--protocol tcp --port 8443 \
--cidr "$(curl -s https://checkip.amazonaws.com)/32"
Importante: Esto restringe el acceso a tu IP pública actual. Si tu IP cambia, actualiza el security group antes de conectar.
AMI_ID=$(aws ec2 describe-images \
--owners 099720109477 \
--filters \
"Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*" \
"Name=state,Values=available" \
--query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
--output text)
echo "Usando AMI: $AMI_ID"
INSTANCE_ID=$(aws ec2 run-instances \
--image-id "$AMI_ID" \
--instance-type t3.xlarge \
--key-name devenv-key \
--security-groups devenv-sg \
--block-device-mappings '[{
"DeviceName": "/dev/sda1",
"Ebs": {
"VolumeSize": 80,
"VolumeType": "gp3",
"DeleteOnTermination": true
}
}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=safe-devenv}]' \
--query 'Instances[0].InstanceId' \
--output text)
echo "Instancia: $INSTANCE_ID"
# Esperar a que la instancia esté corriendo
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"
# Obtener la IP pública
PUBLIC_IP=$(aws ec2 describe-instances \
--instance-ids "$INSTANCE_ID" \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo "IP: $PUBLIC_IP"
| Instancia | vCPUs | RAM | Costo/hora | Observación |
|---|---|---|---|---|
t3.large | 2 | 8 GB | $0.0832 | Mínimo viable: una IDE a la vez |
t3.xlarge | 4 | 16 GB | $0.1664 | Recomendado: cómodo para la mayoría de escenarios |
t3.2xlarge | 8 | 32 GB | $0.3328 | Compilación pesada (Rust), múltiples IDEs |
m5.xlarge | 4 | 16 GB | $0.192 | CPU dedicada, sin límites de burst |
Las instancias t3 son burstable: acumulan créditos de CPU cuando están ociosas. Para coding común, t3 es suficiente y más barato. Para compilación pesada de Rust o suites de tests, considera m5.
ssh -i ~/.ssh/devenv-key.pem ubuntu@$PUBLIC_IP
Espera ~60 segundos después de que la instancia reporte “running” para que cloud-init finalice. Si recibes “Connection refused”, espera e intenta de nuevo.
sudo apt update && sudo apt upgrade -y
sudo apt install -y xfce4 xfce4-goodies xfce4-terminal dbus-x11
# Definir XFCE como sesión por defecto
echo "xfce4-session" > ~/.xsession
No instales
ubuntu-desktop, eso arrastra GNOME y agrega ~1.2 GB de overhead de RAM.
sudo apt install -y lightdm
sudo systemctl enable lightdm
sudo passwd ubuntu
# Elige una contraseña fuerte, esta es la credencial de login de DCV
# Importar clave GPG
wget https://d1uj6qtbmh3dt5.cloudfront.net/NICE-GPG-KEY
gpg --import NICE-GPG-KEY
# Descargar DCV server
wget https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-ubuntu2404-x86_64.tgz
# Extraer e instalar
tar -xvzf nice-dcv-ubuntu2404-x86_64.tgz
cd nice-dcv-*-x86_64
sudo apt install -y ./nice-dcv-server_*.deb ./nice-dcv-web-viewer_*.deb ./nice-xdcv_*.deb
# Limpiar
cd ~ && rm -rf nice-dcv-* NICE-GPG-KEY
# Habilitar sesión console automática
sudo sed -i '/^\[session-management\/automatic-console-session\]/,/^\[/{s/^#\?owner=.*/owner="ubuntu"/}' \
/etc/dcv/dcv.conf
# Si la sección no existe, agregarla
if ! grep -q "automatic-console-session" /etc/dcv/dcv.conf; then
sudo tee -a /etc/dcv/dcv.conf > /dev/null <<DCVEOF
[session-management/automatic-console-session]
owner="ubuntu"
DCVEOF
fi
# Habilitar e iniciar DCV
sudo systemctl enable dcv-server
sudo systemctl start dcv-server
sudo systemctl status dcv-server
dcv list-sessions
# Debe mostrar una sesión "console" con owner "ubuntu"
En tu Mac: abre el Amazon DCV Client, conéctate a $PUBLIC_IP:8443, acepta el certificado autofirmado e inicia sesión con ubuntu y la contraseña definida.
sudo apt install -y \
build-essential curl wget git unzip \
software-properties-common apt-transport-https \
ca-certificates gnupg lsb-release fontconfig libfuse2
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/microsoft-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-keyring.gpg] \
https://packages.microsoft.com/repos/code stable main" \
| sudo tee /etc/apt/sources.list.d/vscode.list
sudo apt update && sudo apt install -y code
curl -fsSL https://releases.warp.dev/linux/keys/warp.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/warp-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/warp-keyring.gpg] \
https://releases.warp.dev/linux/deb stable main" \
| sudo tee /etc/apt/sources.list.d/warp.list
sudo apt update && sudo apt install -y warp-terminal
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install --lts
nvm use --lts
GO_VERSION="1.23.6"
wget "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz"
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf "go${GO_VERSION}.linux-amd64.tar.gz"
rm "go${GO_VERSION}.linux-amd64.tar.gz"
echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> ~/.bashrc
source ~/.bashrc
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env
TOOLBOX_URL=$(curl -s "https://data.services.jetbrains.com/products/releases?code=TBA&latest=true&type=release" \
| grep -oP '"linux":{"link":"\K[^"]+')
wget -O jetbrains-toolbox.tar.gz "$TOOLBOX_URL"
tar -xzf jetbrains-toolbox.tar.gz
sudo mv jetbrains-toolbox-*/jetbrains-toolbox /usr/local/bin/
rm -rf jetbrains-toolbox*
Lanza JetBrains Toolbox desde la terminal dentro de la sesión DCV (no vía SSH). Necesita un display para la GUI.
Esta es la parte más importante. El objetivo es garantizar que la instancia no tenga ningún vínculo con tus cuentas personales.
# Sin claves SSH personales
rm -rf ~/.ssh/id_* ~/.ssh/known_hosts
# Sin credenciales Git
git config --global --unset credential.helper 2>/dev/null
rm -f ~/.git-credentials ~/.gitconfig
# Identidad genérica
git config --global user.name "Dev"
git config --global user.email "dev@localhost"
# Sin credenciales AWS dentro de la instancia
rm -rf ~/.aws/credentials ~/.aws/config
# Sin perfiles de navegador
rm -rf ~/.mozilla ~/.config/google-chrome ~/.config/chromium
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 8443/tcp # DCV
sudo ufw allow 22/tcp # SSH (puedes quitarlo después si quieres)
sudo ufw enable
sudo systemctl disable cups-browsed 2>/dev/null
sudo systemctl disable avahi-daemon 2>/dev/null
sudo systemctl disable bluetooth 2>/dev/null
Guarda como ~/check-isolation.sh dentro de la instancia:
#!/bin/bash
echo "=== Verificación de Aislamiento ==="
ISSUES=0
check() {
if [ -e "$1" ]; then
echo "ALERTA: $1 existe -- $2"
ISSUES=$((ISSUES + 1))
fi
}
check ~/.ssh/id_rsa "Clave SSH personal encontrada"
check ~/.ssh/id_ed25519 "Clave SSH personal encontrada"
check ~/.git-credentials "Credenciales Git encontradas"
check ~/.aws/credentials "Credenciales AWS encontradas"
check ~/.docker/config.json "Login Docker encontrado"
check ~/.npmrc "Token NPM puede estar presente"
check ~/.config/gcloud "Config Google Cloud encontrada"
check ~/.azure "Config Azure encontrada"
GIT_EMAIL=$(git config --global user.email 2>/dev/null || echo "")
if [ -n "$GIT_EMAIL" ] && [ "$GIT_EMAIL" != "dev@localhost" ]; then
echo "ALERTA: Email Git es '$GIT_EMAIL' -- debería ser dev@localhost"
ISSUES=$((ISSUES + 1))
fi
echo ""
if [ $ISSUES -eq 0 ]; then
echo "TODO LIMPIO -- el ambiente está seguro."
else
echo "$ISSUES problema(s) encontrado(s). Revisa arriba."
fi
chmod +x ~/check-isolation.sh
Esta es la imagen reutilizable. Todo lo que instalaste queda grabado en ella.
# Limpiar cache de paquetes
sudo apt clean
sudo apt autoremove -y
# Limpiar archivos temporales
rm -rf /tmp/* ~/.cache/*
# Limpiar historial del bash
history -c
> ~/.bash_history
# Apagar para snapshot limpio
sudo shutdown -h now
# Esperar a que la instancia se detenga
aws ec2 wait instance-stopped --instance-ids "$INSTANCE_ID"
# Crear la AMI
AMI_DEVENV=$(aws ec2 create-image \
--instance-id "$INSTANCE_ID" \
--name "safe-devenv-$(date +%Y%m%d)" \
--description "Safe dev environment: Xubuntu 24.04 + XFCE + DCV + Node/Go/Rust" \
--no-reboot \
--query 'ImageId' \
--output text)
echo "Golden AMI: $AMI_DEVENV"
# Esperar a que la AMI esté disponible
aws ec2 wait image-available --image-ids "$AMI_DEVENV"
echo "AMI lista."
aws ec2 terminate-instances --instance-ids "$INSTANCE_ID"
echo "Instancia terminada. Solo el snapshot AMI permanece."
A partir de aquí solo pagas por el snapshot: ~$0.05/GB/mes. Una imagen de 80 GB con ~20 GB usados cuesta alrededor de $1/mes.
Guarda como ~/devenv-start.sh en tu Mac:
#!/bin/bash
set -euo pipefail
AMI_ID="ami-XXXXXXXXXXXXXXXXX" # Tu Golden AMI
INSTANCE_TYPE="t3.xlarge"
KEY_NAME="devenv-key"
SG_NAME="devenv-sg"
echo "Lanzando ambiente dev seguro..."
# Actualizar security group con IP actual
MY_IP=$(curl -s https://checkip.amazonaws.com)
SG_ID=$(aws ec2 describe-security-groups \
--group-names "$SG_NAME" \
--query 'SecurityGroups[0].GroupId' \
--output text)
aws ec2 revoke-security-group-ingress --group-id "$SG_ID" \
--protocol tcp --port 22 --cidr "0.0.0.0/0" 2>/dev/null || true
aws ec2 revoke-security-group-ingress --group-id "$SG_ID" \
--protocol tcp --port 8443 --cidr "0.0.0.0/0" 2>/dev/null || true
aws ec2 authorize-security-group-ingress --group-id "$SG_ID" \
--protocol tcp --port 22 --cidr "${MY_IP}/32"
aws ec2 authorize-security-group-ingress --group-id "$SG_ID" \
--protocol tcp --port 8443 --cidr "${MY_IP}/32"
# Lanzar
INSTANCE_ID=$(aws ec2 run-instances \
--image-id "$AMI_ID" \
--instance-type "$INSTANCE_TYPE" \
--key-name "$KEY_NAME" \
--security-group-ids "$SG_ID" \
--block-device-mappings '[{
"DeviceName": "/dev/sda1",
"Ebs": {
"VolumeSize": 80,
"VolumeType": "gp3",
"DeleteOnTermination": true
}
}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=safe-devenv}]' \
--query 'Instances[0].InstanceId' \
--output text)
echo "Instancia: $INSTANCE_ID"
echo "Esperando a que esté lista..."
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"
PUBLIC_IP=$(aws ec2 describe-instances \
--instance-ids "$INSTANCE_ID" \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo ""
echo "========================================"
echo " ¡Ambiente listo!"
echo " DCV: ${PUBLIC_IP}:8443"
echo " SSH: ssh -i ~/.ssh/devenv-key.pem ubuntu@${PUBLIC_IP}"
echo " Instance ID: ${INSTANCE_ID}"
echo "========================================"
chmod +x ~/devenv-start.sh
Guarda como ~/devenv-stop.sh en tu Mac:
#!/bin/bash
set -euo pipefail
echo "Buscando instancias del ambiente dev..."
INSTANCE_ID=$(aws ec2 describe-instances \
--filters \
"Name=tag:Name,Values=safe-devenv" \
"Name=instance-state-name,Values=running,stopped" \
--query 'Reservations[0].Instances[0].InstanceId' \
--output text)
if [ "$INSTANCE_ID" = "None" ] || [ -z "$INSTANCE_ID" ]; then
echo "Ninguna instancia encontrada."
exit 0
fi
echo "Terminando instancia: $INSTANCE_ID"
aws ec2 terminate-instances --instance-ids "$INSTANCE_ID"
echo "Instancia terminada. Solo estás pagando por el snapshot AMI."
chmod +x ~/devenv-stop.sh
| Escenario | Costo mensual |
|---|---|
| AMI parada (sin uso) | ~$1/mes |
| 4 sesiones de 3h (t3.xlarge) | |
| Stop/Start (EBS siempre activo, 80 GB) | |
| Siempre encendido (24/7) | ~$127/mes |
El enfoque AMI ahorra ~50% comparado con Stop/Start y ~97% comparado con mantenerlo siempre encendido.
sudo systemctl restart lightdm
sudo systemctl restart dcv-server
# Si persiste:
sudo reboot
# Verificar si está corriendo
sudo systemctl status dcv-server
# Verificar puerto
ss -tlnp | grep 8443
# Verificar si tu IP actual coincide con el security group
curl -s https://checkip.amazonaws.com
# Forzar renderización por software
LIBGL_ALWAYS_SOFTWARE=1 warp-terminal
# Para hacerlo permanente:
sudo sed -i 's|Exec=warp-terminal|Exec=env LIBGL_ALWAYS_SOFTWARE=1 warp-terminal|' \
/usr/share/applications/dev.warp.Warp.desktop
# === En tu Mac ===
# Iniciar ambiente
~/devenv-start.sh
# Apagar ambiente
~/devenv-stop.sh
# Listar tus AMIs
aws ec2 describe-images --owners self \
--query 'Images[*].[ImageId,Name,CreationDate]' \
--output table
# Eliminar AMI antigua
aws ec2 deregister-image --image-id ami-OLD
aws ec2 delete-snapshot --snapshot-id snap-OLD
# === Dentro de la instancia ===
# Verificar aislamiento
~/check-isolation.sh
# Verificar sesiones DCV
dcv list-sessions
# Reiniciar DCV
sudo systemctl restart dcv-server