A montagem do volume do Docker falha depois que a VPN conecta: verifique o daemon
Seu bind mount está certo. Uma VPN muda qual daemon responde ao cliente, então imprima o endpoint, prove o caminho lá e pare de editar o volume.
Quase todo texto sobre este problema culpa o volume, e é exatamente por isso que a correção demora tanto. Você tinha um bind mount que funcionava, a VPN corporativa conectou, e docker compose up agora falha com um caminho de origem ausente, um diretório vazio ou uma montagem que o motor se recusa a criar. Nos ambientes que eu limpei, o arquivo Compose não tinha mudado e o caminho estava correto. O que mudou foi qual daemon recebeu a requisição, e o erro de montagem foi apenas o primeiro lugar onde essa diferença ficou visível.
O volume nunca se moveu; a requisição sim
A -v /srv/app:/app não é uma cópia de diretório feita pelo seu shell. O cliente a transforma em uma requisição de API, e o daemon que responde resolve /srv/app contra o próprio sistema de arquivos. Quando os dois lados rodam no mesmo kernel, esse caminho é o que você vê no gerenciador de arquivos. Quando o endpoint muda, a mesma string é procurada em uma máquina que nunca ouviu falar do diretório do seu projeto, e o daemon ou informa que a origem está ausente ou cria silenciosamente um diretório vazio no lugar.
É também por isso que um volume nomeado continua funcionando enquanto um bind mount não. -v appdata:/app nomeia armazenamento que o daemon possui, então nenhum caminho do host precisa existir em um lugar específico. Se trocar um serviço de um bind mount para um volume nomeado faz o erro desaparecer, você não corrigiu o problema. Você confirmou que o cliente e o daemon discordam sobre a qual sistema de arquivos o caminho pertence, e essa discordância volta no momento em que você precisa de um diretório real do host outra vez.
Imprima o endpoint antes de tocar na montagem
Várias configurações podem apontar o cliente para um endpoint, e elas nem sempre concordam: a opção --context , DOCKER_CONTEXT, DOCKER_HOST no shell atual, e o contexto salvo na configuração do cliente. Em vez de raciocinar sobre precedência, imprima todas e compare as respostas.
docker context show
docker context ls
docker version
docker info --format "{{.Name}} {{.OperatingSystem}} {{.ServerVersion}}"
A configuração do cliente fica em ~/.docker/config.json no Linux e no macOS e em %USERPROFILE%\.docker\config.json no Windows, e um valor exportado no terminal atual substitui o salvo apenas para aquela sessão. Se o endpoint for um nome de host em vez de um socket local, a resolução de nomes e as rotas decidem onde ele aterrissa, e é exatamente isso que um cliente VPN reescreve. Um túnel que adiciona uma rota ou troca o resolvedor pode enviar um nome que antes significava este notebook para um endereço dentro da rede corporativa.
Compare estado em vez de configuração. Execute docker ps -a e docker images com o túnel desligado, e depois novamente ligado. Uma lista de contêineres diferente é prova mais forte do que qualquer arquivo de configuração, porque mostra que outro daemon respondeu à sua requisição.
Pergunte ao daemon se o caminho existe lá
Quando você souber a qual daemon chegou, faça-o olhar o diretório. --mount forma falha com uma mensagem mais clara do que a opção curta, porque valida a origem antes de iniciar o contêiner:
docker run --rm --mount type=bind,source=/srv/app,target=/app alpine ls -la /app
Se ela informar que a origem está ausente, crie-a na máquina que respondeu, ou aponte o serviço para um volume nomeado e copie os dados para lá. O que você não deve fazer é recriar volumes e reinstalar o motor antes desta verificação. Essas ações mudam o ambiente o suficiente para esconder a discrepância enquanto a deixam no lugar.
Há mais um caminho que roda em outro lugar completamente: baixar uma imagem é trabalho do daemon, não do cliente. Um download bem-sucedido só prova que o daemon alcança um registry, e um bind mount funcionando só prova que um caminho foi resolvido. Teste cada caminho onde ele realmente é executado, ou você passará uma tarde no errado.
Nomes de intranet falham por outro motivo
A segunda metade do mesmo relatório costuma ser que o contêiner não consegue alcançar intranet.corp.example. Esse é um mecanismo separado, não um segundo problema de volume. Um contêiner recebe seu /etc/resolv.conf na inicialização, gerado a partir das configurações do resolvedor do daemon, então um servidor DNS que a VPN adicionou depois que o contêiner foi criado não aparecerá ali até que o contêiner seja recriado.
O resolvedor embutido do Docker em uma rede definida pelo usuário encaminha consultas externas aos servidores configurados no host, o que ajuda quando a VPN reescreve o resolvedor do host e atrapalha quando o DNS dividido envia uma zona interna para um servidor que o daemon não vê mais. Um mapeamento estreito e explícito contorna a discussão para nomes que nunca mudam:
services:
web:
extra_hosts:
- "intranet.corp.example:10.20.30.40"
Isso grava uma entrada no arquivo hosts do contêiner, então o contêiner nunca aprende nada sobre o túnel, e também congela o endereço. Quando o host interno se move, a entrada continua errada até alguém editar o arquivo Compose. Use para um dispositivo fixo ou um banco de dados fixado, e deixe o DNS cuidar dos nomes que se movem.
O que uma reinstalação realmente muda
Reinstalar o Docker Desktop recria a VM, o socket e a lista de contextos, e a montagem volta a funcionar. Esse resultado é real e não ensina nada, e é por isso que a mesma falha retorna após a próxima atualização da VPN. A reinstalação substituiu o endpoint que o cliente usava; ela não reconciliou o caminho que seu projeto nomeia com o sistema de arquivos que o daemon realmente lê.
Minha ordem de trabalho é curta. Confirme qual endpoint o cliente vai usar. Confirme qual daemon respondeu comparando listas de contêineres. Peça a esse daemon para listar o caminho de origem. Só então edite a montagem, adicione uma entrada hosts ou mude um tipo de volume. Se você mantém o arquivo Compose para uma equipe que trabalha por VPN, prefira volumes nomeados para dados que o daemon deve possuir, para que um caminho do host nunca faça parte do contrato.