Ambiente dev seguro e sob demanda com AWS EC2 + AMI

2026-03-25

post-thumb

Índice

Tem situações em que você precisa de um ambiente de desenvolvimento completamente isolado da sua máquina principal: testar repositórios de terceiros, executar código que você não confia totalmente, trabalhar com ferramentas que exigem Linux nativo, ou simplesmente ter um desktop dev descartável que não compromete suas credenciais, chaves SSH ou tokens.

A ideia aqui é simples: montar uma vez, salvar como imagem e restaurar sob demanda. Quando você termina, destrói. Quando precisa de novo, recria em minutos. E o custo? Cerca de $1/mês quando parado.


Visão geral da arquitetura

Dia de uso:
  AMI Snapshot --> Lança EC2 --> Conecta via DCV --> Trabalha --> Termina EC2

Entre os usos:
  Apenas o snapshot AMI existe (~$1/mês para 20 GB)
  Sem instâncias rodando, sem volumes EBS, sem custo de computação

Por que essa abordagem:

  • Você paga ~$1/mês quando não está usando (apenas armazenamento da AMI)
  • Cada sessão começa a partir de uma imagem limpa e pré-configurada
  • Seu macOS fica completamente isolado, sem compartilhamento de arquivos ou rede
  • Amazon DCV fornece desktop remoto fluido (gratuito no EC2)
  • A instância é destruída após cada uso, sem dados residuais

Escolhendo a distro

Se suas ferramentas exigem glibc (a maioria exige), Alpine (musl libc) não é compatível.

Recomendado: Ubuntu Server 24.04 LTS + XFCE

Melhor equilíbrio entre compatibilidade, documentação e leveza.

DistroDesktopRAM ociosaPrósContras
Xubuntu 24.04XFCE~600-800 MBRepos Ubuntu/PPAs, comunidade enormeUm pouco mais pesado que Debian
Debian 12 + XFCEXFCE~400-600 MBMais leve, estávelPacotes mais antigos
Ubuntu 24.04GNOME~1.5-2 GBMelhor experiência out-of-boxPesado, desperdiça RAM
Fedora 41 XFCEXFCE~600-800 MBPacotes mais recentesdnf mais lento que apt

A estratégia: usar a AMI oficial do Ubuntu Server 24.04 (gratuita na AWS Marketplace) e instalar XFCE por cima. Desktop leve com total compatibilidade.


Pré-requisitos

Tudo isso é feito uma única vez no seu Mac.

1. Conta AWS e CLI

brew install awscli
aws configure
# Informe: Access Key ID, Secret Access Key, Região (ex: us-east-1), Output (json)

2. Cliente Amazon DCV

Baixe em docs.aws.amazon.com/dcv e instale o .dmg para macOS.

3. Par de chaves SSH

aws ec2 create-key-pair \
  --key-name devenv-key \
  --query 'KeyMaterial' \
  --output text > ~/.ssh/devenv-key.pem

chmod 400 ~/.ssh/devenv-key.pem

4. Security Group

# Criar o security group
aws ec2 create-security-group \
  --group-name devenv-sg \
  --description "Safe dev environment"

# Liberar 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"

# Liberar DCV (porta 8443)
aws ec2 authorize-security-group-ingress \
  --group-name devenv-sg \
  --protocol tcp --port 8443 \
  --cidr "$(curl -s https://checkip.amazonaws.com)/32"

Importante: Isso restringe o acesso ao seu IP público atual. Se seu IP mudar, atualize o security group antes de conectar.


Passo 1: Lançar a instância base

Encontrar a AMI do Ubuntu 24.04

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"

Lançar a instância

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 "Instância: $INSTANCE_ID"

# Aguardar a instância ficar rodando
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"

# Obter o IP público
PUBLIC_IP=$(aws ec2 describe-instances \
  --instance-ids "$INSTANCE_ID" \
  --query 'Reservations[0].Instances[0].PublicIpAddress' \
  --output text)

echo "IP: $PUBLIC_IP"

Opções de tipo de instância

InstânciavCPUsRAMCusto/horaObservação
t3.large28 GB$0.0832Mínimo viável: uma IDE por vez
t3.xlarge416 GB$0.1664Recomendado: confortável para a maioria dos cenários
t3.2xlarge832 GB$0.3328Compilação pesada (Rust), múltiplas IDEs
m5.xlarge416 GB$0.192CPU dedicada, sem limites de burst

Instâncias t3 são burstable: acumulam créditos de CPU quando ociosas. Para coding comum, t3 é suficiente e mais barato. Para compilação pesada de Rust ou suítes de testes, considere m5.


Passo 2: Instalar desktop e DCV

ssh -i ~/.ssh/devenv-key.pem ubuntu@$PUBLIC_IP

Aguarde ~60 segundos após a instância reportar “running” para o cloud-init finalizar. Se receber “Connection refused”, espere e tente novamente.

Atualizar o sistema

sudo apt update && sudo apt upgrade -y

Instalar XFCE

sudo apt install -y xfce4 xfce4-goodies xfce4-terminal dbus-x11

# Definir XFCE como sessão padrão
echo "xfce4-session" > ~/.xsession

Não instale ubuntu-desktop, isso puxa o GNOME e adiciona ~1.2 GB de overhead de RAM.

Display manager

sudo apt install -y lightdm
sudo systemctl enable lightdm

Senha do usuário ubuntu

sudo passwd ubuntu
# Escolha uma senha forte, essa é a credencial de login do DCV

Instalar Amazon DCV

# Importar chave GPG
wget https://d1uj6qtbmh3dt5.cloudfront.net/NICE-GPG-KEY
gpg --import NICE-GPG-KEY

# Baixar DCV server
wget https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-ubuntu2404-x86_64.tgz

# Extrair 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

# Limpar
cd ~ && rm -rf nice-dcv-* NICE-GPG-KEY

Configurar DCV

# Habilitar sessão console automática
sudo sed -i '/^\[session-management\/automatic-console-session\]/,/^\[/{s/^#\?owner=.*/owner="ubuntu"/}' \
  /etc/dcv/dcv.conf

# Se a seção não existir, adicionar
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

Verificar e conectar

sudo systemctl status dcv-server
dcv list-sessions
# Deve mostrar uma sessão "console" com owner "ubuntu"

No seu Mac: abra o Amazon DCV Client, conecte em $PUBLIC_IP:8443, aceite o certificado autoassinado e faça login com ubuntu e a senha definida.


Passo 3: Instalar ferramentas de desenvolvimento

Pacotes essenciais

sudo apt install -y \
  build-essential curl wget git unzip \
  software-properties-common apt-transport-https \
  ca-certificates gnupg lsb-release fontconfig libfuse2

VS Code

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

Warp Terminal

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

Node.js (via nvm)

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc

nvm install --lts
nvm use --lts

Go

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

Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env

JetBrains Toolbox (GoLand, WebStorm, etc.)

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*

Lance o JetBrains Toolbox a partir do terminal dentro da sessão DCV (não via SSH). Ele precisa de um display para a GUI.


Passo 4: Blindagem de segurança

Essa é a parte mais importante. O objetivo é garantir que a instância não tenha nenhum vínculo com suas contas pessoais.

Remover credenciais pessoais

# Sem chaves SSH pessoais
rm -rf ~/.ssh/id_* ~/.ssh/known_hosts

# Sem credenciais Git
git config --global --unset credential.helper 2>/dev/null
rm -f ~/.git-credentials ~/.gitconfig

# Identidade genérica
git config --global user.name "Dev"
git config --global user.email "dev@localhost"

# Sem credenciais AWS dentro da instância
rm -rf ~/.aws/credentials ~/.aws/config

# Sem perfis de navegador
rm -rf ~/.mozilla ~/.config/google-chrome ~/.config/chromium

Firewall interno

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 8443/tcp   # DCV
sudo ufw allow 22/tcp     # SSH (pode remover depois se quiser)
sudo ufw enable

Desabilitar serviços desnecessários

sudo systemctl disable cups-browsed 2>/dev/null
sudo systemctl disable avahi-daemon 2>/dev/null
sudo systemctl disable bluetooth 2>/dev/null

Script de verificação de isolamento

Salve como ~/check-isolation.sh dentro da instância:

#!/bin/bash
echo "=== Verificação de Isolamento ==="

ISSUES=0

check() {
  if [ -e "$1" ]; then
    echo "ALERTA: $1 existe -- $2"
    ISSUES=$((ISSUES + 1))
  fi
}

check ~/.ssh/id_rsa "Chave SSH pessoal encontrada"
check ~/.ssh/id_ed25519 "Chave SSH pessoal encontrada"
check ~/.git-credentials "Credenciais Git encontradas"
check ~/.aws/credentials "Credenciais AWS encontradas"
check ~/.docker/config.json "Login Docker encontrado"
check ~/.npmrc "Token NPM pode 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 é '$GIT_EMAIL' -- deveria ser dev@localhost"
  ISSUES=$((ISSUES + 1))
fi

echo ""
if [ $ISSUES -eq 0 ]; then
  echo "TUDO LIMPO -- ambiente está seguro."
else
  echo "$ISSUES problema(s) encontrado(s). Verifique acima."
fi
chmod +x ~/check-isolation.sh

Passo 5: Criar a Golden AMI

Essa é a imagem reutilizável. Tudo que você instalou fica gravado nela.

Limpar antes de criar o snapshot

# Limpar cache de pacotes
sudo apt clean
sudo apt autoremove -y

# Limpar arquivos temporários
rm -rf /tmp/* ~/.cache/*

# Limpar histórico do bash
history -c
> ~/.bash_history

# Desligar para snapshot limpo
sudo shutdown -h now

Criar a AMI (do seu Mac)

# Aguardar a instância parar
aws ec2 wait instance-stopped --instance-ids "$INSTANCE_ID"

# Criar a 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"

# Aguardar a AMI ficar disponível
aws ec2 wait image-available --image-ids "$AMI_DEVENV"
echo "AMI pronta."

Terminar a instância base

aws ec2 terminate-instances --instance-ids "$INSTANCE_ID"
echo "Instância terminada. Apenas o snapshot AMI permanece."

A partir daqui você paga apenas pelo snapshot: ~$0.05/GB/mês. Uma imagem de 80 GB com ~20 GB usados fica em torno de $1/mês.


Passo 6: Usar no dia a dia

Script de inicialização

Salve como ~/devenv-start.sh no seu Mac:

#!/bin/bash
set -euo pipefail

AMI_ID="ami-XXXXXXXXXXXXXXXXX"  # Sua Golden AMI
INSTANCE_TYPE="t3.xlarge"
KEY_NAME="devenv-key"
SG_NAME="devenv-sg"

echo "Lançando ambiente dev seguro..."

# Atualizar security group com IP atual
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"

# Lançar
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 "Instância: $INSTANCE_ID"
echo "Aguardando ficar pronta..."

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 pronto!"
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

Script de teardown

Salve como ~/devenv-stop.sh no seu Mac:

#!/bin/bash
set -euo pipefail

echo "Procurando instâncias do 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 "Nenhuma instância encontrada."
  exit 0
fi

echo "Terminando instância: $INSTANCE_ID"
aws ec2 terminate-instances --instance-ids "$INSTANCE_ID"

echo "Instância terminada. Você está pagando apenas pelo snapshot AMI."
chmod +x ~/devenv-stop.sh

Custo estimado

CenárioCusto mensal
AMI parada (sem uso)~$1/mês
4 sessões de 3h (t3.xlarge)$2 compute + $1 AMI = **$3**
Stop/Start (EBS sempre ativo, 80 GB)$6.40 EBS + $2 compute = **$8.40**
Sempre ligado (24/7)~$127/mês

A abordagem AMI economiza ~50% em relação a Stop/Start e ~97% em relação a manter ligado.


Troubleshooting

DCV mostra tela preta

sudo systemctl restart lightdm
sudo systemctl restart dcv-server
# Se persistir:
sudo reboot

Conexão recusada no DCV

# Verificar se está rodando
sudo systemctl status dcv-server

# Verificar porta
ss -tlnp | grep 8443

# Verificar se seu IP atual bate com o security group
curl -s https://checkip.amazonaws.com

Warp Terminal não abre (problema de GPU)

# Forçar renderização por software
LIBGL_ALWAYS_SOFTWARE=1 warp-terminal

# Para tornar permanente:
sudo sed -i 's|Exec=warp-terminal|Exec=env LIBGL_ALWAYS_SOFTWARE=1 warp-terminal|' \
  /usr/share/applications/dev.warp.Warp.desktop

Comandos rápidos

# === No seu Mac ===

# Iniciar ambiente
~/devenv-start.sh

# Encerrar ambiente
~/devenv-stop.sh

# Listar suas AMIs
aws ec2 describe-images --owners self \
  --query 'Images[*].[ImageId,Name,CreationDate]' \
  --output table

# Deletar AMI antiga
aws ec2 deregister-image --image-id ami-OLD
aws ec2 delete-snapshot --snapshot-id snap-OLD

# === Dentro da instância ===

# Verificar isolamento
~/check-isolation.sh

# Verificar sessões DCV
dcv list-sessions

# Reiniciar DCV
sudo systemctl restart dcv-server