Depois de muitos anos de pedidos, finalmente a Docker resolveu implementar um watch mode no comando compose! Com ele a gente vai poder iniciar uma espécie de atualização automática dos nossos containers sempre que nossos arquivos forem modificados no disco!

O problema

Um dos maiores problemas para quem usava (ou até usa) o Docker compose, é que, sempre que temos nosso cluster de containers ativo, e estamos desenvolvendo localmente com um desses containers, quando fazemos uma modificação em algum dos arquivos do projeto, precisamos reiniciar todo o compose para criar a nova imagem.

Geralmente, quando estamos fazendo uma API externa, por exemplo, não temos tanto esse problema porque podemos rodar a API em si fora do compose, enquanto apontamos para dentro dos containers de dependências, por exemplo, de alguma API externa ou algo do tipo. Mas, as vezes, isso não é possível, geralmente quando precisamos que seja o que for que a gente está fazendo fique na mesma rede do Docker, ou então precise estar rodando dentro de um container. Nesse caso, a única saída é rodar tudo dentro do Docker compose.

Porém, sempre que a gente atualiza um dos arquivos do projeto, temos que rodar aquele famoso docker compose down seguido de um docker compose up --rebuild para poder reconstruir o nosso serviço e continuar desenvolvendo.

A solução

Por muitos anos, a galera sugeriu que o docker compose tivesse um modo watch, ou seja, um modo que pudesse ouvir o que está acontecendo com um determinado arquivo ou conjunto de arquivos do projeto e rodar um comando específico quando eles forem alterados. Depois de alguns anos, temos o compose file watch mode. E o mais legal é que você não precisa habilitar ele para todos os seus serviços.

Isso permite que a gente agora comece a desenvolver diretamente dentro dos containers do compose, tornando o ambiente muito mais fácil de ser migrado de um lado para o outro, independente se o seu projeto precisa ou não de uma integração direta com o Docker.

Para ativar o modo watch basta que você coloque a chave watch em qualquer serviço dentro do seu docker-compose.yml. Essa chave leva um array de objetos com duas propriedades, uma é o path e o outra é a action, basicamente o serviço vai tomar uma action quando tiver uma mudança em um determinado path.

Vamos a um exemplo:

services:
	web:
    	build: .
        command: npm run dev
        x-develop:
        	watch:
            	- action: sync
                  path: ./web
                  target: /src/web
                  ignore:
                    - node_modules
No momento, o compose watch mode está em estado experimental, por isso temos que usar a chave x-develop para dizer que estamos ativando um comando beta.

Actions

Existem dois tipos de actions pré definidas que você pode tomar: sync e rebuild:

Sync

Esse tipo de ação vai garantir que qualquer mudança feita no seu arquivo local, fora do container, seja adicionada ao container e substitua o arquivo que está dentro dele.

É muito parecido com o conceito de bind mounts, ou até mesmo de volumes, que continuam existindo dentro do Docker, mas, ao contrário deles, você tem uma granularidade maior com o Sync do que com os volumes. Um exemplo disso é justamente poder ignorar arquivos ou diretórios inteiros, coisa que não podemos fazer a não ser que tivéssemos múltiplos volumes.

Um caso de uso muito interessante disso é justamente no nosso amado JavaScript, especialmente com Node.js, o ideal é que a gente ignore completamente a pasta node_modules porque, por mais que a gente tenha muito código JS lá dentro, algumas dependências usam Native Modules, que não vão funcionar em ambientes diferentes do que eles foram criados e compilados para executar.

Rebuild

Quando usamos o Rebuild, vamos ter o equivalente de rodar docker compose up --build <serviço> sempre que salvarmos o path.

Enquanto o sync é mais recomendado para aplicações que podem fazer um hot reload, isto é, recarregar a aplicação já é suficiente para poder verificar as modificações, como um front-end ou algo do tipo, o rebuild é mais indicado para quando temos uma modificação em algum arquivo chave (tipo o package.json) que precisa de uma recompilação da imagem para reinstalar ou remover dependências que podem ter sido modificadas.

Outro caso de uso é para linguagens compiladas, que precisam passar pelo processo completo de build para serem refeitas, embora existam casos onde você pode somente trocar o binário de uma aplicação que será suficiente.

Path e Target

Ambas as ações vão acompanhadas de duas outras propriedades, o path e o target.

Em resumo, o target é o caminho do arquivo observado dentro do container, por exemplo, se meu arquivo package.json está na raiz da minha pasta local, mas dentro da pasta /app do meu container, então meu target será /app/package.json.

Lembrando que, por regra, todos os caminhos são baseados no diretório de build da sua aplicação, ou seja, o local onde o docker encontrou o Dockerfile

Executando

Para executar o docker compose watch, primeiramente precisamos criar a build do nosso serviço com docker compose up -d --build --wait, isso fará com que o docker inicie o compose e construa todas as imagens.

Depois, podemos rodar docker compose alpha watch para iniciar o watch mode que vai ler as configurações que queremos do nosso arquivo.

Conclusão

O Docker watch é um comando que será muito útil na maioria dos workflows de programação, especialmente quando temos que desenvolver aplicações de grande porte que permitem um Hot Reload.

Com esse comando podemos abrir as portas para o desenvolvimento dentro de containers cada vez mais, ou seja, não vamos ter diferenças substanciais entre as máquinas e chega do "na minha máquina funciona" de uma vez por todas!

Importante: Lembre-se que o comando ainda está no estado alpha, então é possível que a API dele mude nos próximos meses, se isso acontecer, vou tentar atualizar o artigo para refletir as mudanças!