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

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

    Não quer configurar manualmente? Implante o VPS em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.

    Perguntas frequentes