Docker security hardening: containers seguros
Um container Docker rodando como root com filesystem gravável e sem limites de recursos tem superfície de ataque enorme — se comprometido, pode afetar o host. Aplicar as camadas de segurança corretas isola o container ao ponto em que um comprometimento da aplicação não significa comprometimento do servidor.
Rodar containers como usuário não-root
A mudança de maior impacto: nunca rodar aplicação como root no container:
# Dockerfile com usuário não-root (Node.js):
FROM node:22-alpine
# Criar usuário dedicado para a aplicação:
RUN addgroup -g 1001 -S nodejs && adduser -S -u 1001 -G nodejs nodejs
WORKDIR /app
# Copiar dependências primeiro (cache de build):
COPY --chown=nodejs:nodejs package*.json ./
RUN npm ci --only=production
# Copiar código fonte:
COPY --chown=nodejs:nodejs . .
# Trocar para usuário não-root ANTES de expor porta:
USER nodejs
EXPOSE 3000
CMD ["node", "server.js"]
# Verificar que o container não roda como root:
docker run --rm minha-app whoami # deve retornar "nodejs", não "root"
docker run --rm minha-app id # uid=1001(nodejs) gid=1001(nodejs)
# Forçar usuário não-root em runtime (mesmo que Dockerfile não defina):
docker run --user 1001:1001 minha-app
# docker-compose.yml:
# services:
# app:
# image: minha-app
# user: "1001:1001"Filesystem read-only e tmpfs
Montar o filesystem do container como somente leitura:
# docker run com read-only filesystem:
docker run --read-only --tmpfs /tmp:size=100m,noexec --tmpfs /var/run:size=10m -v /data/app:/app/data:rw minha-app
# docker-compose.yml:
services:
app:
image: minha-app
read_only: true
tmpfs:
- /tmp:size=100m,noexec,nosuid
- /var/run:size=10m
volumes:
- app-data:/app/data # volume específico para dados graváveis
security_opt:
- no-new-privileges:true # processo não pode ganhar privilégios adicionais
# Benefício: se a app for comprometida, atacante não pode:
# - instalar malware no filesystem do container
# - modificar binários da aplicação
# - criar arquivos de persistência
# Para Node.js com read-only: ajuste o working dir:
# WORKDIR /app
# RUN chown nodejs:nodejs /app
# Volume para logs: --tmpfs /app/logs:size=50mLimites de recursos e capabilities
Restringir CPU, memória e capabilities do Linux:
# docker-compose.yml — limites de recursos e segurança:
services:
app:
image: minha-app
read_only: true
# Limites de recursos (evitar DoS por consumo excessivo):
deploy:
resources:
limits:
cpus: '0.5' # máximo 50% de 1 CPU
memory: 512M
reservations:
cpus: '0.1'
memory: 128M
# Remover TODAS as capabilities e adicionar apenas as necessárias:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # necessário para bind em portas < 1024 (ex: porta 80)
# A maioria das apps Node.js não precisa de nenhuma capability
security_opt:
- no-new-privileges:true # bloquear setuid/setgid
- seccomp:./seccomp.json # perfil seccomp customizado
# Desabilitar acesso a devices do host:
devices: []
# Impedir modificação de capabilities em runtime:
ulimits:
nofile:
soft: 65536
hard: 65536
nproc:
soft: 100
hard: 100
tmpfs:
- /tmp:size=64m,noexec,nosuid,nodev
restart: unless-stoppedVarredura de vulnerabilidades com Trivy
Escanear imagens Docker em busca de CVEs antes de fazer deploy:
# Instalar Trivy:
sudo apt-get install -y wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivy
# Escanear imagem local:
trivy image minha-app:latest
# Apenas vulnerabilidades críticas e altas:
trivy image --severity CRITICAL,HIGH minha-app:latest
# Formato JSON para CI/CD:
trivy image --format json --output trivy-report.json minha-app:latest
# Falhar o build se encontrar CRITICAL:
trivy image --exit-code 1 --severity CRITICAL minha-app:latest
# GitHub Actions:
# - name: Scan image
# uses: aquasecurity/trivy-action@master
# with:
# image-ref: minha-app:latest
# format: sarif
# output: trivy-results.sarif
# severity: CRITICAL,HIGH
# Escanear filesystem do Dockerfile (sem precisar buildar):
trivy fs --severity HIGH,CRITICAL .
# Manter base images atualizadas (principal fonte de CVEs):
# node:22-alpine tem menos CVEs que node:22-slim que tem menos que node:22
docker pull node:22-alpine && docker build --no-cache -t minha-app .Daemon do Docker: hardening do host
Configurar o daemon Docker com opções de segurança:
# /etc/docker/daemon.json — configurações de segurança do daemon:
sudo tee /etc/docker/daemon.json << 'EOF'
{
"icc": false,
"no-new-privileges": true,
"live-restore": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"userns-remap": "default",
"seccomp-profile": "/etc/docker/seccomp.json"
}
EOF
# Explicando as opções:
# icc: false → desabilitar comunicação direta entre containers
# (apenas via redes definidas explicitamente)
# no-new-privileges → containers não podem elevar privilégios
# live-restore → containers continuam rodando se o daemon reiniciar
# userns-remap → mapear UID 0 do container para UID não-root no host
# (root no container = usuário limitado no host)
# IMPORTANTE: userns-remap pode quebrar volumes com permissões específicas
# Testar antes em ambiente de staging
# Restringir acesso ao socket Docker:
# Nunca expor /var/run/docker.sock em containers de produção!
# docker run -v /var/run/docker.sock:/var/run/docker.sock ← PERIGOSO
# Qualquer container com acesso ao socket pode escapar para o host
sudo systemctl restart docker$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS para Docker em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.
Perguntas frequentes
Conteúdos relacionados