2026-03-25

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.
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:
Se suas ferramentas exigem glibc (a maioria exige), Alpine (musl libc) não é compatível.
Melhor equilíbrio entre compatibilidade, documentação e leveza.
| Distro | Desktop | RAM ociosa | Prós | Contras |
|---|---|---|---|---|
| Xubuntu 24.04 | XFCE | ~600-800 MB | Repos Ubuntu/PPAs, comunidade enorme | Um pouco mais pesado que Debian |
| Debian 12 + XFCE | XFCE | ~400-600 MB | Mais leve, estável | Pacotes mais antigos |
| Ubuntu 24.04 | GNOME | ~1.5-2 GB | Melhor experiência out-of-box | Pesado, desperdiça RAM |
| Fedora 41 XFCE | XFCE | ~600-800 MB | Pacotes mais recentes | dnf 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.
Tudo isso é feito uma única vez no seu Mac.
brew install awscli
aws configure
# Informe: Access Key ID, Secret Access Key, Região (ex: us-east-1), Output (json)
Baixe em docs.aws.amazon.com/dcv e instale o .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
# 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.
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 "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"
| Instância | vCPUs | RAM | Custo/hora | Observação |
|---|---|---|---|---|
t3.large | 2 | 8 GB | $0.0832 | Mínimo viável: uma IDE por vez |
t3.xlarge | 4 | 16 GB | $0.1664 | Recomendado: confortável para a maioria dos cenários |
t3.2xlarge | 8 | 32 GB | $0.3328 | Compilação pesada (Rust), múltiplas IDEs |
m5.xlarge | 4 | 16 GB | $0.192 | CPU 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.
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.
sudo apt update && sudo apt upgrade -y
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.
sudo apt install -y lightdm
sudo systemctl enable lightdm
sudo passwd ubuntu
# Escolha uma senha forte, essa é a credencial de login do 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
# 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
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.
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*
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.
Essa é a parte mais importante. O objetivo é garantir que a instância não tenha nenhum vínculo com suas contas 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
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
sudo systemctl disable cups-browsed 2>/dev/null
sudo systemctl disable avahi-daemon 2>/dev/null
sudo systemctl disable bluetooth 2>/dev/null
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
Essa é a imagem reutilizável. Tudo que você instalou fica gravado nela.
# 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
# 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."
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.
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
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
| Cenário | Custo mensal |
|---|---|
| AMI parada (sem uso) | ~$1/mês |
| 4 sessões de 3h (t3.xlarge) | |
| Stop/Start (EBS sempre ativo, 80 GB) | |
| 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.
sudo systemctl restart lightdm
sudo systemctl restart dcv-server
# Se persistir:
sudo reboot
# 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
# 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
# === 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