SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-10-30

Docker Compose com vários ficheiros: ordem e override

Entenda como compose.override.yaml é carregado sozinho, como a ordem dos ficheiros faz merge, por que ports mantém uma porta aberta e quando usar include.

O que o Compose faz com mais de um ficheiro

O Docker Compose pode criar um projeto a partir de vários ficheiros. Lê-os pela ordem em que os recebe e junta-os num único modelo. Por isso, o ficheiro posterior prevalece sempre que exista um conflito de valores. A linha de comandos disponibiliza dois mecanismos para isso: um ficheiro de substituição que o Compose carrega automaticamente e a opção -f, que indica manualmente. O terceiro mecanismo está no próprio ficheiro: o elemento include. O seu funcionamento é diferente do dos outros dois.

A junção não é uma simples substituição. Os mapeamentos são combinados chave a chave, as sequências são acrescentadas e um pequeno conjunto de campos é substituído por completo. É esta diferença que causa surpresas. A lista ports é a que apanha quase toda a gente.

Tudo o que se segue pressupõe o Compose v2 ou posterior, com o plugin docker compose, e não o antigo script docker-compose. As versões atuais são numeradas como v5, e o plugin funciona da mesma forma. Execute docker compose version para verificar. Se ainda não criou um ficheiro Compose, comece pelo guia básico do Docker Compose e volte depois.

O ficheiro de override que o Compose carrega sem indicação

Execute docker compose up sem a flag -f. O Compose procura no diretório de trabalho e, depois, nos diretórios superiores, por compose.yaml ou docker-compose.yaml. Se existir um ficheiro de override junto ao ficheiro base, o Compose carrega-o automaticamente.

ls compose.yaml compose.override.yaml
docker compose up -d

Com os dois ficheiros presentes, o resultado é o mesmo que escrever ambos manualmente.

docker compose -f compose.yaml -f compose.override.yaml up -d

Os nomes reconhecidos pelo Compose são compose.override.yaml, compose.override.yml, e os nomes mais antigos docker-compose.override.yml e docker-compose.override.yaml. Qualquer outro nome, como compose.dev.yaml, só é carregado quando é indicado com -f.

Assim que passa um -f, o carregamento automático para. docker compose -f compose.yaml up lê exatamente esse ficheiro e ignora o override. É esta propriedade que permite o padrão de dev e prod usado mais à frente neste guia.

Isto pode funcionar nos dois sentidos num servidor. Um ficheiro de substituição deixado no diretório de deploy é carregado por todos os comandos docker compose executados nesse diretório, incluindo o comando executado pelo cron. É assim que uma stack de produção acaba por montar um diretório de código-fonte que ninguém pretendia incluir na entrega. Execute docker compose config depois de cada deploy e leia o resultado. Quando o deploy é não interativo, a verificação só é útil se alguém o informar de que algo correu mal. Essa é a função de um canal push, como um servidor ntfy autoalojado, para o qual um job do cron ou uma unidade systemd OnFailure pode enviar uma notificação.

Ordenação com -f e onde os caminhos relativos são resolvidos

O Compose constrói a configuração pela ordem em que fornece os ficheiros. Os ficheiros seguintes substituem e acrescentam conteúdo aos anteriores. Da esquerda para a direita, o último prevalece.

docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -d

Todos os comandos desse projeto precisam da mesma lista de ficheiros. Execute up com dois ficheiros e logs com um, e estará a comunicar com um modelo combinado diferente. É uma forma rápida de obter um serviço que o Compose diz não existir. O risco aumenta numa stack cujas atualizações são executadas como comandos isolados, como o passo de migração da base de dados em uma plataforma de suporte Chatwoot alojada pelo próprio, onde um docker compose run emitido com uma lista de ficheiros incorreta é direcionado silenciosamente para um modelo diferente daquele que os seus serviços já utilizam. Defina a lista uma única vez com a variável de ambiente COMPOSE_FILE.

export COMPOSE_FILE=compose.yaml:compose.prod.yaml
docker compose config
docker compose up -d

O separador é : no Linux, e COMPOSE_PATH_SEPARATOR altera esse separador. COMPOSE_FILE também pode ficar no ficheiro .env do projeto. Assim, passa a fazer parte do checkout, em vez de ficar no histórico da shell. Qualquer valor definido explicitamente na linha de comandos prevalece sobre a variável de ambiente.

Agora, a regra que causa problemas nos bind mounts. Quando usa vários ficheiros com -f, todos os caminhos relativos desses ficheiros são resolvidos relativamente ao diretório do primeiro ficheiro. Não são resolvidos relativamente ao ficheiro que os contém. Escreva ./data:/var/lib/postgresql/data dentro de deploy/prod/compose.prod.yaml e o Compose continuará a procurar ./data junto do ficheiro base. O Docker cria então um diretório vazio nesse caminho incorreto e o contentor arranca sem conteúdo. Isto parece uma perda de dados, mas não é. Passe --project-directory para definir manualmente o caminho base ou use include, que resolve cada ficheiro relativamente ao seu próprio diretório.

O nome do projeto vem desse mesmo diretório base. Por isso, alterar o ficheiro que aparece primeiro pode renomear o projeto. Um projeto renomeado implica novos nomes de contentores e de volumes. O volume antigo continua no disco com o nome antigo. Fixe o nome com name: no nível superior do ficheiro base.

name: myapp

Quais campos são mesclados e quais são substituídos

O Compose faz a mesclagem com base no tipo do valor, não no nome do campo.

  • Os campos com valor único são substituídos. image, command, entrypoint e mem_limit assumem diretamente o valor posterior. Não é possível acrescentar um argumento a um command, porque a substituição reescreve a linha inteira.
  • Os mapeamentos são mesclados chave a chave. environment, labels, volumes e devices mantêm todas as chaves dos dois ficheiros, e o ficheiro posterior prevalece quando uma chave existe em ambos. Em environment e labels, a chave é o nome da variável ou da etiqueta. Em volumes e devices, a chave é o caminho no contentor.
  • As sequências são acrescentadas. dns, dns_search, expose, tmpfs e external_links são concatenadas. Uma configuração base que contém expose: ["3000"], mesclada com uma substituição que contém ["4000", "5000"], produz ["3000", "4000", "5000"].

Quatro sequências têm uma chave de identidade. Por isso, as entradas que coincidem nessa chave são mescladas em vez de acrescentadas. volumes, secrets e configs usam target para fazer a correspondência. ports usa a combinação de ip, target, published e protocol.

Leia essa regra de ports duas vezes, porque é onde ocorre o erro mais comum. Duas entradas de portas só são a mesma entrada quando todas essas quatro partes coincidem. Se alterar qualquer uma delas, o Compose interpreta a entrada como uma segunda porta sem relação com a primeira e mantém ambas.

Por que sua porta continua publicada após o override

Um arquivo base que publica um serviço em todas as interfaces:

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"

Um override escrito para vinculá-lo apenas ao localhost, porque um reverse proxy ficará à frente dele:

services:
  web:
    ports:
      - "127.0.0.1:8080:80"

Verifique o resultado antes de presumir que funcionou.

docker compose -f compose.yaml -f compose.prod.yaml config

As duas entradas aparecem na saída. A parte ip é diferente, 0.0.0.0 contra 127.0.0.1, portanto são duas portas diferentes para fins do merge, e o vínculo público que você tentou remover continua no modelo. Isso é mais importante no Docker do que em outros contextos, porque uma porta publicada é gravada no iptables antes das regras do seu firewall. O mecanismo é explicado em por que as portas publicadas do Docker passam pelo ufw.

Há duas correções. A explícita é a tag !override, que substitui todo o atributo e ignora as regras de merge:

services:
  web:
    ports: !override
      - "127.0.0.1:8080:80"

!override exige o Compose v2.24.4 ou mais recente. A correção portável não exige nenhuma tag: mantenha ports completamente fora do arquivo base e declare-o apenas nos arquivos específicos do ambiente. Sem nada para fazer merge, nada pode vazar. Esse é o padrão usado no exemplo completo abaixo.

Eliminar um valor definido pelo ficheiro base

!reset remove um atributo, fazendo-o regressar ao valor predefinido ou a null. Recebe um valor e ignora-o, por isso escreva algo válido e vazio.

services:
  web:
    ports: !reset []
    environment:
      DEBUG: !reset null

!reset funciona no Compose v2.18.0 ou posterior, várias versões antes de !override surgir. Use-o quando não pode editar o ficheiro base, por exemplo, um fragmento de um fornecedor que inclui. Uma stack publicada pelo projeto upstream é exatamente esse caso: o ficheiro Compose por trás de um workspace AFFiNE autoalojado declara quatro contentores que não escreveu, e !reset permite limpar um atributo num deles sem criar um fork do ficheiro e ficar responsável por o acompanhar.

include, para stacks montadas a partir de componentes

include incorpora outra aplicação Compose no seu modelo. É um elemento de nível superior, não uma flag.

include:
  - path: ../commons/compose.yaml

Cada caminho em include é carregado como o seu próprio modelo de aplicação Compose, com o seu próprio diretório de projeto. Por isso, os caminhos relativos dentro desse ficheiro são resolvidos relativamente ao diretório desse ficheiro. Essa é a diferença real em relação a -f e o motivo pelo qual include é a ferramenta correta quando o fragmento está noutra pasta ou noutro repositório. Essa é a estrutura habitual de uma stack de um fornecedor que não foi escrita por si: o ficheiro Compose com vários serviços por trás de uma instalação SSO self-hosted do Authentik pode ficar no seu próprio diretório, mantendo intactos os seus caminhos relativos, enquanto o seu ficheiro continua focado nos seus próprios serviços.

A forma longa aceita subopções.

include:
  - path:
      - ../monitoring/compose.yaml
      - ../monitoring/compose.vps.yaml
    project_directory: ../monitoring
    env_file: ../monitoring/.env

path aceita uma lista, e esses ficheiros são fundidos segundo as regras normais antes de o resultado ser incorporado no seu modelo. project_directory define o caminho base usado para resolver caminhos relativos no ficheiro incluído. env_file indica o ficheiro a partir do qual o ficheiro Compose incluído obtém os valores predefinidos para interpolação. Por predefinição, esse ficheiro já é o .env no próprio diretório de projeto do ficheiro incluído, e o ambiente do seu projeto local continua a ter precedência. Assim, pode substituir uma das variáveis sem editar o fragmento. include requer Compose v2.20.0 ou posterior. As mesmas opções são adequadas para adicionar um único contentor a uma stack que já executa, por exemplo Halcyon, que transforma uma biblioteca Jellyfin numa loja de aluguer dos anos 90: o ficheiro mantém a sua própria tag de imagem e o seu próprio env_file, pelo que atualizá-lo nunca implica alterar o ficheiro onde a sua stack multimédia está definida.

A documentação do Docker afirma que um nome de recurso declarado tanto no seu ficheiro como num ficheiro incluído é rejeitado por conflito. O loader da versão atual não faz isso: na versão Compose v5.6.1, verificada no código-fonte do loader incluído nessa versão em 2026-10-27, as duas definições são fundidas. Em qualquer valor conflitante, o seu ficheiro prevalece, em vez de a operação terminar com um erro. Portanto, não conte com uma falha explícita para detetar nomes duplicados. Para alterar algo declarado por um ficheiro incluído, coloque a alteração em compose.override.yaml. A substituição é aplicada ao modelo montado, pelo que a alteração fica explícita e não depende da forma como uma determinada versão trata o conflito. Este hábito é especialmente útil numa stack cujo ficheiro upstream é reescrito em cada release, como os servidores de fotografias com vários contentores comparados em PhotoPrism versus Immich, onde um binding para localhost ou um volume adicional deve ficar na sua substituição, e não no ficheiro que a próxima atualização irá substituir.

A versão curta: include compõe aplicações separadas; -f aplica camadas de configuração a uma única aplicação.

Separação entre desenvolvimento e produção em um único VPS

Este é o padrão completo em três ficheiros. O ficheiro base declara o que é válido em todo o lado e não publica nenhuma porta.

name: myapp

services:
  app:
    image: ghcr.io/example/app:1.4.2
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
      LOG_LEVEL: info
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  db_data:

A condição depends_on faz a aplicação esperar por uma base de dados que responde, e não apenas por um contentor que existe, conforme explicado em healthchecks e condições de depends_on. POSTGRES_PASSWORD é interpolado a partir do ficheiro .env do projeto, que nunca deve estar no git. Consulte ficheiros env e secrets do Compose para obter variantes mais seguras.

Em seguida, compose.override.yaml, que o Compose carrega automaticamente. Este é o ficheiro do desenvolvedor.

services:
  app:
    build: .
    command: npm run dev
    environment:
      LOG_LEVEL: debug
    ports:
      - "3000:3000"
    volumes:
      - ./src:/app/src

  db:
    ports:
      - "127.0.0.1:5432:5432"

Num laptop, um docker compose up sem argumentos combina esses dois ficheiros. command substitui o valor padrão da imagem porque é um valor único. LOG_LEVEL substitui info porque environment combina os valores por chave. O bind mount e as duas portas publicadas são apenas adições, e a porta da base de dados fica ligada a localhost para que um laptop numa rede partilhada não disponibilize o PostgreSQL a toda a rede.

Por fim, compose.prod.yaml. O nome não é um dos nomes que o Compose procura, portanto o ficheiro nunca é carregado por acidente.

services:
  app:
    ports:
      - "127.0.0.1:8000:3000"
    deploy:
      resources:
        limits:
          memory: 512M

No VPS, indique os dois ficheiros. É essa indicação que exclui exatamente o override.

docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose -f compose.yaml -f compose.prod.yaml ps

ps deve listar os dois serviços como em execução, com db a apresentar (healthy). Como passou -f, compose.override.yaml não foi lido. Por isso, o comando de desenvolvimento, o bind mount do código-fonte e a porta pública 3000 não podem chegar à produção, mesmo que o ficheiro esteja no mesmo diretório. A porta 8000 está disponível apenas em localhost, pronta para um proxy. Consulte executar várias aplicações atrás do Traefik quando adicionar o segundo serviço.

Defina COMPOSE_FILE=compose.yaml:compose.prod.yaml no .env do servidor. Depois disso, os restantes comandos voltam a ser apenas docker compose logs -f app.

Uma stack com um único serviço segue a mesma estrutura, porque um rastreador de treinos openGym autoalojado tem de responder através de TLS atrás de um proxy antes de registar a primeira passkey. Um ficheiro base sem ports é o que impede uma publicação pública acidental de ultrapassar o proxy.

Leia o modelo mesclado antes de fazer o deploy

docker compose config imprime o modelo totalmente mesclado e com todas as interpolações aplicadas. Não é uma pré-visualização. É a entrada exata que o Compose vai processar. Por isso, quando a saída não corresponde ao que esperava, a saída está correta.

docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml config --no-interpolate
docker compose -f compose.yaml -f compose.prod.yaml config --services

--no-interpolate mantém ${VAR} sem expansão. Use-o antes de colar a saída em qualquer lugar, porque config simples imprime todos os segredos resolvidos em texto claro. --services lista apenas os nomes dos serviços. É uma forma rápida de confirmar que um include incluiu o que esperava.

Modos de falha e o que verá

no configuration file provided: not found. O Compose não encontrou nada para ler. Está fora do diretório do projeto ou COMPOSE_FILE indica um caminho que não existe. O Compose procura o ficheiro base predefinido nos diretórios-pai, mas não procura em qualquer localização um ficheiro que tenha indicado explicitamente.

WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. A interpolação é resolvida com base no ficheiro .env do projeto e no ambiente da shell. Neste caso, o diretório do projeto é o diretório do primeiro ficheiro -f. Fazer o deploy a partir de um diretório diferente daquele que contém .env gera este aviso e, em seguida, uma base de dados que recusa todas as ligações.

A sua alteração no override não aparece em docker compose config. Ou passou -f, o que desativa o carregamento automático do override, ou o Compose encontrou compose.yaml num diretório-pai e o seu ficheiro de override não está junto dele. Executar docker compose config sem outros argumentos mostra qual é o modelo que o Compose está realmente a construir.

Um bind mount está vazio e o Docker criou um diretório que não pediu. O caminho relativo foi resolvido com base no diretório do primeiro ficheiro. Corrija o caminho, passe --project-directory ou mova o fragmento para depois de include.

Os contentores voltam com nomes novos e um volume parece vazio. O nome do projeto mudou, porque segue o diretório do primeiro ficheiro. Adicione um name: de nível superior ao ficheiro base para impedir que os nomes continuem a mudar. O volume antigo continua disponível com o prefixo antigo, e docker volume ls irá mostrá-lo.

Uma porta que removeu no override continua aberta. A ports merge acrescentou a entrada em vez de a substituir. Confirme com docker compose config e, em seguida, use !override ou mova ports para fora do ficheiro base.

FAQ

O Compose carrega automaticamente o compose.override.yaml?

Sim, quando executa docker compose sem a flag -f. O Compose procura compose.yaml ou docker-compose.yaml no diretório de trabalho e nos diretórios pai. Se existir um ficheiro de substituição junto dele, esse ficheiro é carregado em segundo lugar. Os nomes reconhecidos são compose.override.yaml, compose.override.yml, docker-compose.override.yml e docker-compose.override.yaml. A passagem de qualquer -f desativa este comportamento. Nesse caso, docker compose -f compose.yaml up lê apenas um ficheiro.

Em que ordem são combinados vários ficheiros -f?

Da esquerda para a direita. O Compose constrói a configuração pela ordem em que fornece os ficheiros. Cada ficheiro substitui e acrescenta valores aos ficheiros anteriores. Por isso, o último ficheiro na linha prevalece em qualquer conflito. A mesma lista deve ser usada em todos os comandos desse projeto. É para isso que serve COMPOSE_FILE=compose.yaml:compose.prod.yaml.

Por que motivo a minha porta continua publicada depois de a substituir?

Porque as entradas ports são identificadas pelo conjunto completo de ip, target, published e protocol. Uma substituição de 127.0.0.1:8080:80 sobre uma base de 8080:80 difere na parte ip. Por isso, o Compose trata-a como uma segunda porta e mantém ambas. Execute docker compose config para ver as duas entradas. Use ports: !override no Compose v2.24.4 ou posterior, ou não inclua ports no ficheiro base para que não exista nada a combinar.

Qual é a diferença entre include e -f?

-f combina vários ficheiros numa única aplicação. Os caminhos relativos de todos os ficheiros são resolvidos relativamente ao diretório do primeiro ficheiro. include inclui uma aplicação Compose separada. Cada ficheiro incluído mantém o seu próprio diretório do projeto, pelo que os caminhos relativos são resolvidos relativamente a esse diretório. Use -f para camadas de ambiente da sua própria stack e include para um fragmento mantido noutro local. include requer Compose v2.20.0 ou posterior.

Como removo um valor definido pelo ficheiro base?

Use a tag !reset no Compose v2.18.0 ou posterior. Escreva ports: !reset [] ou MY_VAR: !reset null no ficheiro de substituição. O atributo volta ao valor predefinido ou a null. O valor fornecido à tag é obrigatório, mas é ignorado. Se quiser substituir um atributo em vez de o limpar, !override faz isso. Esta opção requer v2.24.4 ou posterior.

Este artigo foi útil?