O .dockerignore: o ficheiro que quase ninguém escreve
Naquele docker build . que toda a gente escreve, o ponto final não quer dizer «o Dockerfile está aqui». Quer dizer: empacota esta pasta inteira e envia-a. Acontece antes de o Docker ler uma única linha do Dockerfile.
Contei os ficheiros deste projeto para ver o que isso significa:
node_modules 54 077 ficheiros 895 MB
.next 3 004 ficheiros 340 MB
.git 1 262 ficheiros 4 MB
src, public, prisma 118 ficheiros
a pasta inteira 1,3 GBSem um .dockerignore, são 1,3 GB e cerca de 58 mil ficheiros a viajar em cada construção, para uma imagem que precisa de 118 deles.
1. O que isso custa, além do tempo
O tempo é o menor dos problemas — aquela linha do «a enviar o contexto» que ninguém lê. O que dói é a cache das camadas.
Um COPY . . produz uma camada com uma impressão digital do que foi copiado. Se qualquer coisa no contexto mudar, essa camada muda, e tudo o que vem a seguir é reconstruído. Com o .next dentro do contexto, basta ter tido o servidor de desenvolvimento a correr para que cada build recomece do zero, sem razão nenhuma.
2. O problema a sério é outro
O .env. Se ele estiver no contexto e houver um COPY . ., vai para dentro da imagem — e aqui está a parte que apanha muita gente:
COPY . .
RUN rm .env # não serve de nadaAs camadas são cumulativas. O rm cria uma camada nova onde o ficheiro não aparece, mas a camada anterior continua lá dentro, inteira, dentro da imagem que se publicou. Quem tiver a imagem consegue extraí-la:
docker save a-minha-imagem -o imagem.tar
tar -xf imagem.tar
# e ler as camadas uma a umaUm segredo que entrou numa camada está publicado. A única forma de o tirar é construir a imagem outra vez sem ele — e rodar o segredo, porque já lá esteve.
3. O node_modules é o que engana
Parece só peso a mais, e não é. Um COPY . . leva o node_modules da máquina de quem constrói para dentro da imagem, por cima do que o npm ci lá instalou.
Enquanto forem módulos em JavaScript puro, ninguém dá por nada. Assim que um deles trouxer binários compilados — e há sempre um — ficam binários de Windows ou de macOS dentro de uma imagem Alpine, e o erro aparece no arranque, longe da causa.
4. Como se escreve
A sintaxe é a do .gitignore, com ! para voltar a incluir. Este é o deste site, quase por inteiro:
.git
.vscode
node_modules
# Compilado localmente; a imagem constrói o seu.
.next
*.tsbuildinfo
# Segredos. Em produção as variáveis vêm do ambiente.
.env
.env.*
# Documentação e ferramentas de migração: não pertencem à imagem.
*.md
!README.md
deploy
fixtures
scriptsRepare no par *.md seguido de !README.md: fica tudo de fora menos um. É a construção que mais falta faz e que menos gente conhece.
5. Não é o .gitignore
São listas com propósitos diferentes, e copiar uma para a outra dá sempre problemas nos dois sentidos:
Coisas que o Git ignora e a imagem precisa — um ficheiro gerado no momento da construção, uma pasta de recursos produzida por outra ferramenta.
Coisas que o Git segue e a imagem não quer — documentação, capturas de ecrã, fixtures de teste, os scripts de migração que já correram. Tudo isso está no repositório com toda a razão, e não tem nada que fazer dentro de um contentor de produção.
6. Ver o que entrou mesmo
Não é preciso adivinhar. Constrói-se uma imagem descartável que copia o contexto e conta:
docker build -f- . <<'FIM'
FROM busybox
COPY . /ctx
RUN find /ctx -type f | wc -l
FIMO -f- lê o Dockerfile da entrada padrão e o . continua a ser o contexto. Se aquele número vier na casa das dezenas de milhar, já se sabe o que falta.
O resumo
O .dockerignore escreve-se uma vez e resolve três coisas ao mesmo tempo: builds que deixam de reconstruir tudo sem motivo, imagens que não levam lá dentro o que a máquina de quem as construiu tinha, e segredos que não ficam presos numa camada para sempre. Para um ficheiro de vinte linhas, é bom negócio.