Sempre que possível, sempre mesclo comandos que criam arquivos com comandos que excluem esses mesmos arquivos em uma única RUN
linha. Isso ocorre porque cada RUN
linha adiciona uma camada à imagem, a saída é literalmente as alterações no sistema de arquivos que você pode visualizar docker diff
no contêiner temporário criado. Se você excluir um arquivo criado em uma camada diferente, tudo o que o sistema de arquivos da união fará é registrar a alteração do sistema de arquivos em uma nova camada, o arquivo ainda existe na camada anterior e é enviado pela rede e armazenado no disco. Portanto, se você baixar o código-fonte, extraí-lo, compilá-lo em um binário e excluir os arquivos tgz e de origem no final, você realmente deseja que tudo seja feito em uma única camada para reduzir o tamanho da imagem.
Em seguida, eu pessoalmente divido as camadas com base em seu potencial de reutilização em outras imagens e no uso esperado de armazenamento em cache. Se eu tiver 4 imagens, todas com a mesma imagem base (por exemplo, debian), posso puxar uma coleção de utilitários comuns para a maioria dessas imagens no primeiro comando de execução, para que as outras imagens se beneficiem do cache.
A ordem no Dockerfile é importante ao analisar a reutilização do cache de imagem. Examino todos os componentes que serão atualizados muito raramente, possivelmente apenas quando a imagem base é atualizada e os coloca no alto do Dockerfile. No final do Dockerfile, incluo todos os comandos que serão executados rapidamente e podem mudar com frequência, por exemplo, adicionando um usuário com um UID específico do host ou criando pastas e alterando permissões. Se o contêiner incluir código interpretado (por exemplo, JavaScript) que está sendo desenvolvido ativamente, isso será adicionado o mais tarde possível, para que uma reconstrução execute apenas essa única alteração.
Em cada um desses grupos de alterações, consolido o melhor possível para minimizar as camadas. Portanto, se houver quatro pastas de código-fonte diferentes, elas serão colocadas em uma única pasta para que possam ser adicionadas com um único comando. Qualquer instalação de pacote de algo como o apt-get é mesclada em um único RUN, quando possível, para minimizar a quantidade de sobrecarga do gerenciador de pacotes (atualização e limpeza).
Atualização para compilações de vários estágios:
Preocupo-me muito menos em reduzir o tamanho da imagem nos estágios não finais de uma compilação de vários estágios. Quando esses estágios não são marcados e enviados para outros nós, você pode maximizar a probabilidade de uma reutilização de cache dividindo cada comando em uma RUN
linha separada .
No entanto, essa não é uma solução perfeita para esmagar camadas, pois tudo o que você copia entre os estágios são os arquivos e não o restante dos metadados da imagem, como configurações de variáveis de ambiente, ponto de entrada e comando. E quando você instala pacotes em uma distribuição linux, as bibliotecas e outras dependências podem estar espalhadas pelo sistema de arquivos, dificultando a cópia de todas as dependências.
Por esse motivo, utilizo compilações de vários estágios como substituto para a construção de binários em um servidor de CI / CD, para que meu servidor de CI / CD precise apenas ter as ferramentas para executar docker build
e não ter um jdk, nodejs, go e quaisquer outras ferramentas de compilação instaladas.