Docker seguro em produção na VPS

    Docker com configuração padrão tem vários riscos de segurança: containers rodando como root, imagens com vulnerabilidades conhecidas, portas expostas desnecessariamente e secrets em variáveis de ambiente. Com poucos ajustes no Dockerfile e docker-compose.yml, você elimina a maioria dos vetores de ataque sem mudar o comportamento da aplicação.

    Rodar containers como usuário não-root

    Por padrão, processos dentro de containers Docker rodam como root — mesmo que o host tenha proteções, um escape de container com root tem impacto maior:

    dockerfile
    # Dockerfile — criar e usar usuário não-root
    FROM node:22-alpine
    
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --only=production
    COPY . .
    
    # Criar usuário não-root
    RUN addgroup -S appgroup && adduser -S appuser -G appgroup
    # Ou em Debian/Ubuntu:
    # RUN useradd --create-home --no-log-init appuser
    
    # Dar ownership dos arquivos ao novo usuário
    RUN chown -R appuser:appgroup /app
    
    USER appuser
    EXPOSE 3000
    CMD ["node", "dist/index.js"]
    
    # Verificar que o container não roda como root:
    # docker compose exec app id
    # uid=1000(appuser) gid=1000(appgroup)
    Dica
    Alguns serviços precisam de root para bind em portas < 1024. Solução: configure o serviço para usar porta >= 1024 internamente (ex: 8080) e deixe o Nginx fazer o proxy para a porta 443 no host.

    Limitar recursos e filesystem read-only

    Limite CPU, memória e torne o filesystem do container somente-leitura:

    yaml
    services:
      app:
        build: .
        restart: always
        user: "1000:1000"           # UID:GID do usuário não-root
    
        # Limitar recursos
        deploy:
          resources:
            limits:
              cpus: "1.0"           # máximo 1 vCPU
              memory: 512M          # máximo 512 MB RAM
            reservations:
              memory: 128M
    
        # Filesystem somente-leitura (exceto volumes explícitos)
        read_only: true
        tmpfs:
          - /tmp                    # /tmp ainda precisa ser gravável
          - /run
    
        # Segurança adicional
        security_opt:
          - no-new-privileges:true  # impedir escalonamento de privilégios
        cap_drop:
          - ALL                     # remover todas as capabilities Linux
        cap_add:
          - NET_BIND_SERVICE        # adicionar apenas o necessário

    Redes Docker isoladas

    Por padrão, todos os containers na mesma rede podem se comunicar. Isole por responsabilidade:

    yaml
    services:
      app:
        networks:
          - frontend_net   # acessa o Nginx
          - backend_net    # acessa o banco
    
      nginx:
        networks:
          - frontend_net   # apenas frontend
    
      postgres:
        networks:
          - backend_net    # apenas backend — sem acesso do Nginx direto
    
      redis:
        networks:
          - backend_net
    
    networks:
      frontend_net:
        driver: bridge
      backend_net:
        driver: bridge
        internal: true     # sem acesso à internet (apenas comunicação interna)
    Dica
    internal: true em redes backend isola os containers de banco e cache da internet. O container app pode acessar postgres e redis (mesma rede), mas postgres não pode fazer requests externos.

    Secrets e variáveis sensíveis

    Variáveis de ambiente aparecem em docker inspect. Para secrets críticos, use Docker Secrets (modo Swarm) ou monte arquivos:

    yaml
    # Opção 1: variáveis de ambiente (aceitável, não ideal)
    # Aparecem em: docker inspect, /proc/PID/environ
    # Mitigação: não expor a porta Docker daemon; usar env_file
    
    # Opção 2: arquivo montado como secret (sem Swarm)
    services:
      app:
        environment:
          DB_PASSWORD_FILE: /run/secrets/db_password
        volumes:
          - /opt/secrets/db_password:/run/secrets/db_password:ro
    
    # Na aplicação Node.js — ler do arquivo:
    const dbPassword = process.env.DB_PASSWORD_FILE
      ? require('fs').readFileSync(process.env.DB_PASSWORD_FILE, 'utf8').trim()
      : process.env.DB_PASSWORD
    
    # Opção 3: Docker Secrets (modo Swarm)
    # docker secret create db_password /path/to/file
    # No compose: secrets: [db_password]

    Escanear imagens por vulnerabilidades

    Use Trivy para detectar CVEs em imagens Docker antes de subir para produção:

    bash
    # Instalar Trivy (scanner de vulnerabilidades)
    apt install -y wget apt-transport-https gnupg lsb-release
    wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | tee /usr/share/keyrings/trivy.gpg > /dev/null
    echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/trivy.list
    apt update && apt install trivy
    
    # Escanear imagem local
    trivy image minha-api:latest
    
    # Escanear e falhar se houver CVE crítica (bom para CI/CD)
    trivy image --exit-code 1 --severity CRITICAL minha-api:latest
    
    # Escanear Dockerfile por má configuração
    trivy config ./Dockerfile
    
    # Escanear diretório (IaC)
    trivy config ./

    $ 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