Percio Castelo.
EN Início
Docker compose: desvendando a causa e solução do erro `non-string key at top level`
← Voltar para blog

Docker compose: desvendando a causa e solução do erro `non-string key at top level`

14/06/2026 · 5 min · Desenvolvimento · 374 visualizações

Se você subiu um conjunto de containers rodando docker compose up e o interpretador cuspiu um erro como non-string key at top level: 404, não se desespere: seu Docker Engine está perfeitamente saudável. O problema é puramente uma barbeiragem de sintaxe no arquivo YAML.

O YAML é um formato prático, mas extremamente melindroso com espaços e tipos de dados. Esse erro específico significa que o parser encontrou uma chave na raiz (top level) do documento que não é uma string válida. De acordo com a especificação do Compose, chaves principais (version, services, volumes, networks) e os nomes de serviços precisam ser lidos estritamente como strings de texto.


1. Parse de YAML vs. validação de schema: a raiz do problema#

Para diagnosticar qualquer erro em arquivos do Docker Compose, é fundamental entender que o processo de leitura ocorre em duas etapas distintas:

  1. Fase de Parse do YAML (YAML Parsing): O interpretador (como a biblioteca PyYAML em Python ou gopkg.in/yaml em Go) lê o arquivo de texto plano e o converte em estruturas de dados nativas da linguagem (dicionários, listas, inteiros, booleanos). Se houver chaves mal indentadas, colchetes errados ou chaves numéricas no topo do arquivo, o erro é disparado aqui.
  • Erro Típico: non-string key at top level: 404 (o parser encontrou um inteiro/objeto onde deveria haver uma chave em string na raiz do documento).
  1. Fase de Validação de Schema (Schema Validation): Uma vez carregado o objeto na memória, o Docker Compose valida se a estrutura segue as especificações do Compose Spec (por exemplo, se as portas estão no formato correto, se campos obrigatórios existem).
  • Erro Típico: services.web.ports must be a list of strings (o YAML foi parseado com sucesso, mas a estrutura fornecida não bate com o tipo esperado).

O fluxo abaixo demonstra essa distinção:

flowchart TD A[docker-compose.yml] --> B{Fase 1: Parse YAML} B -- Falha: Chave Numérica no Top Level / Erro Sintaxe --> C[Erro: non-string key at top level] B -- Sucesso: Estrutura YAML Carregada --> D{Fase 2: Validação de Schema} D -- Falha: Tipo de Dado Inválido / Campo Rejeitado --> E[Erro: services.app.ports must be a list of strings] D -- Sucesso: Validação Ok --> F[Containers Inicializados com Sucesso]

2. Causas comuns de erro de parse e schema#

Além do recuo incorreto, existem diversos tipos de dados que enganam o interpretador do YAML se não forem declarados de forma explícita como strings:

A. Portas declaradas sem aspas (o caso clássico)#

No YAML, chaves numéricas ou sequências de portas sem aspas confundem o interpretador. Existem dois cenários comuns desse erro:

# ❌ INCORRETO (mapeamento implícito sem hífen)
ports:
  404:404      # O YAML interpreta como o dicionário {404: 404} com chave numérica
# Erro de Schema: services.app.ports must be a list of strings

# ❌ INCORRETO (lista sem aspas)
ports:
  - 404:404    # O parser tenta resolver como {404: 404} e falha pois espera uma string
# Erro de Schema: services.app.ports[0] must be a string, not a number

# ✅ CORRETO
ports:
  - "404:404"  # Lido estritamente como string

B. Portas compostas e complexas#

Qualquer variação de mapeamento de portas que contenha IPs ou faixas numéricas precisa obrigatoriamente estar protegida por aspas:

ports:
  - "8080:80"             # Porta externa mapeada para porta interna
  - "80:80"               # Porta simples
  - "127.0.0.1:8080:80"   # Bind de IP específico
  - "8080-8090:80-90"     # Faixa de portas (ranges)

C. Diferença entre ports e expose#

O Docker Compose possui diretivas separadas para exposição pública e comunicação interna:

  • ports: Publica as portas no host de execução (requer aspas devido ao parser YAML).
  • expose: Apenas expõe a porta internamente para outros containers na mesma rede virtual. Embora menos suscetível ao erro por não conter dois-pontos (:), também é boa prática declará-la como string.
services:
  app:
    ports:
      - "8080:80"   # Mapeamento externo para o host (use aspas)
    expose:
      - "80"         # Comunicação privada interna (use aspas)

D. Tipos especiais YAML: booleanos, nulos e datas#

O parser YAML converte implicitamente certas palavras reservadas para tipos nativos. Se você tentar passar esses valores em variáveis de ambiente, volumes ou labels sem aspas, receberá erros de tipo no schema validation:

# ❌ INCORRETO
environment:
  DEBUG: true             # Interpretado como booleano puro
# Erro de Schema: services.app.environment.DEBUG must be a string, not a boolean

labels:
  cache: null             # Interpretado como valor nulo (null)
# Erro de Schema: services.app.labels.cache must be a string, not null

volumes:
  2026-06-15: /data       # Interpretado como um objeto de data
# Erro de Parse ou Schema: non-string key / invalid date type

# ✅ CORRETO
environment:
  DEBUG: "true"           # String pura
labels:
  cache: "null"           # String pura
volumes:
  "2026-06-15": /data     # String pura

E. Recuo (indentação) sujo que empurra chaves para a raiz#

O YAML não perdoa erros de indentação. Se você errar a quantidade de espaços em uma chave de serviço ou de porta, ela pode ser jogada acidentalmente para o início da linha (margem esquerda), fazendo o parser achar que ela pertence à raiz do documento:

# ❌ INCORRETO
services:
  app:
    image: node:18
  ports: # <-- Recuo incorreto jogou ports para o "top level"
    - "80:80"
# Erro de Schema: additional property ports is not allowed

services:
  app:
    image: node:18
404:404: # <-- Sem aspas e na raiz
  - "80:80"
# Erro de Parse YAML: non-string key at top level: 404

Como o interpretador lê o arquivo de cima para baixo, ele bate com chaves de raiz inválidas e falha na hora, porque essa chave não faz sentido solta no topo do arquivo sem estar associada a um serviço.

A regra de ouro: mantenha o recuo perfeitamente alinhado com espaços (nunca use tabulação):

# ✅ CORRETO
services:
  app:
    image: node:18
    ports:
      - "80:80"

F. Depreciação do campo version#

Nas versões mais recentes do Docker Compose (Compose v2 / especificação unificada), a diretiva version no topo do arquivo foi descontinuada e agora é opcional. Mantê-la pode gerar warnings em suas execuções. A boa prática atual é removê-la inteiramente do topo do arquivo:

# ✅ CORRETO (Sem a tag version - Válido no Compose v2)
services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"

Ferramentas de validação e depuração#

Sempre que bater esse erro, você pode usar uma série de estratégias para encontrar a causa raiz e automatizar a prevenção:

1. Depuração completa com docker compose config#

Esse comando valida as configurações do arquivo contra o JSON schema oficial sem precisar subir nenhum container.

  • Se houver um erro, a saída apontará a linha exata e a falha de schema:
  $ docker compose config
  # Saída típica de erro:
  services.web.ports must be a list of strings
  services.web.ports[0] must be a string, not 404:404
  • Para ver o arquivo final consolidado e parseado pelo interpretador em formato estruturado, use a flag --format json:
  docker compose config --format json

2. Linters de YAML locais#

Para evitar enviar arquivos inválidos ao repositório, instale e execute um linter local em sua máquina de desenvolvimento:

# Instalar linter YAML de linha de comando
pip install yamllint

# Rodar validação no arquivo
yamllint docker-compose.yml

No VS Code, instale a extensão YAML da Red Hat, que adiciona validação automática e autocompletar baseado em schemas em tempo real durante a digitação.

3. Automação de validação em CI/CD#

Adicione uma etapa de validação em seu pipeline de integração contínua (como GitHub Actions) para garantir que nenhum commit quebre o deploy:

# .github/workflows/validate-compose.yml
name: Validate Docker Compose

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Validate Compose Syntax
        run: docker compose config

      - name: Run YAML Lint
        run: |
          pip install yamllint
          yamllint docker-compose.yml

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

374 leituras
Creative Commons Atribuição-NãoComercial

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários