Hardening de segurança do Docker

Docker facilita o deploy mas introduz superfície de ataque nova, containers rodando como root, imagens com vulnerabilidades conhecidas, portas expostas desnecessariamente e acesso ao socket Docker. Aplicar hardening reduz drasticamente o risco de um container comprometido escalar para o host.

Rodar containers como usuário não-root

A configuração de segurança mais impactante no Docker:

dockerfile
# No Dockerfile — criar usuário não-root:
FROM node:22-alpine

# Criar grupo e usuário:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --omit=dev

# Trocar para o usuário não-root:
USER appuser

EXPOSE 3000
CMD ["node", "src/index.js"]

# Verificar que o container não roda como root:
docker run --rm minha-imagem id
# Deve mostrar: uid=1000(appuser) gid=1000(appgroup)

# No docker-compose.yml — usar user: diretiva:
services:
  api:
    image: minha-imagem
    user: "1000:1000"   # UID:GID

# Verificar usuário de containers em execução:
docker ps -q | xargs docker inspect --format '{{.Name}} {{.Config.User}}'

Limitar capabilities e recursos dos containers

Remover permissões desnecessárias dos containers:

yaml
# docker-compose.yml — configuração segura:
services:
  api:
    image: minha-api:latest
    # Remover todas as capabilities, adicionar apenas as necessárias:
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE     # se precisar bindar porta < 1024
    # Tornar sistema de arquivos somente leitura:
    read_only: true
    # tmpfs para diretórios que precisam de escrita:
    tmpfs:
      - /tmp
      - /var/run
    # Sem privilégios extras:
    security_opt:
      - no-new-privileges:true
    # Limitar recursos:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          memory: 128M
    # Sem acesso ao socket Docker:
    # NÃO monte /var/run/docker.sock a menos que seja absolutamente necessário

Redes isoladas e configuração do daemon

Isolar containers em redes separadas e configurar o Docker daemon:

yaml
# docker-compose.yml — redes isoladas:
services:
  api:
    networks:
      - frontend     # acessa nginx
      - backend      # acessa banco
  nginx:
    networks:
      - frontend     # apenas nginx fica na rede pública
  postgres:
    networks:
      - backend      # banco só acessível pela api
  redis:
    networks:
      - backend

networks:
  frontend:
  backend:
    internal: true   # sem acesso à internet — apenas comunicação interna

# /etc/docker/daemon.json — configuração segura do daemon:
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "no-new-privileges": true,
  "live-restore": true,
  "userland-proxy": false,
  "default-address-pools": [
    { "base": "172.17.0.0/16", "size": 24 }
  ]
}

# Aplicar mudanças no daemon:
sudo systemctl reload docker

Gerenciar secrets com segurança

Nunca colocar credenciais em variáveis de ambiente ou imagens:

yaml
# Antipadrão — NUNCA faça isso no Dockerfile:
ENV DATABASE_PASSWORD=minhaSenha    # fica na imagem para sempre

# Correto — usar arquivos .env (não commitados no git):
# docker-compose.yml:
services:
  api:
    env_file: .env          # carregado do arquivo .env local
    environment:
      # Apenas variáveis não-sensíveis inline:
      NODE_ENV: production
      PORT: 3000

# .env (no .gitignore):
DATABASE_URL=postgresql://user:senha@postgres:5432/db
JWT_SECRET=segredo_jwt

# Docker Secrets (modo Swarm) — mais seguro que env vars:
# echo "minha_senha_forte" | docker secret create db_password -

# Para VPS sem Swarm: usar bind mount de arquivo secreto:
services:
  api:
    volumes:
      - /run/secrets/db_password:/run/secrets/db_password:ro
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password

# Verificar se imagem expõe secrets:
docker history --no-trunc minha-imagem | grep -i "password|secret|key"

Scanning de vulnerabilidades em imagens

Detectar vulnerabilidades conhecidas antes de colocar em produção:

bash
# Trivy — scanner de vulnerabilidades de containers (gratuito):
# Instalar:
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
  | sh -s -- -b /usr/local/bin

# Escanear imagem:
trivy image nginx:latest

# Escanear apenas vulnerabilidades críticas/altas:
trivy image --severity HIGH,CRITICAL minha-api:latest

# Escanear a imagem e retornar erro se houver vuln crítica (útil no CI):
trivy image --exit-code 1 --severity CRITICAL minha-api:latest

# No GitHub Actions:
      - name: Scan de vulnerabilidades
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'ghcr.io/usuario/minha-api:${{ github.sha }}'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'

# docker scout (integrado ao Docker Desktop):
docker scout cves minha-imagem:latest
docker scout recommendations minha-imagem:latest

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Risco real: se o processo dentro do container for comprometido por uma vulnerabilidade, ele roda com os mesmos privilégios que root no host, especialmente perigoso se o container tem volumes montados no host ou acesso ao socket Docker. Em imagens de sistemas base (Ubuntu, Debian) que rodam como root: use somente como base e troque o usuário no Dockerfile.

Montar /var/run/docker.sock num container dá a esse container controle total do Docker daemon, e portanto do host inteiro. É equivalente ao root. Portainer, Traefik, Watchtower precisam do socket mas devem ser os únicos. Se um desses containers for comprometido: game over. Avalie bem antes de montar o socket.

Trivy é a ferramenta padrão da indústria: trivy image minha-imagem. Integre no CI/CD para bloquear deploy de imagens com vulnerabilidades críticas. Prefira imagens base mínimas (alpine, distroless), menos pacotes = menos superficie de ataque. Atualize imagens regularmente com docker compose pull + up -d.

Docker rootless (daemon rodando sem root) é mais seguro, um container escapando não tem acesso root ao host. Limitações: não suporta algumas funcionalidades (bind ports < 1024 sem configuração extra, alguns storage drivers). Para VPS com workloads simples: vale considerar. Instalar: curl -fsSL https://get.docker.com/rootless | sh.

Watchtower monitora containers em execução e faz pull + restart automático quando uma nova imagem é publicada: docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --interval 86400 (verifica diariamente). Para produção: use update controlado via CI/CD em vez de atualização automática para ter janela de testes.