Como diminuir o tamanho do container usando multi-stage builds.
De vez em quando esbarro com alguma imagem docker enorme em que 99% do tamanho é lixo deixado quando a imagem estava sendo construída. Isso é especialmente ruim quando estamos de executáveis Go que são autocontidos e geralmente não precisam de nenhum recurso externo, nem mesmo a libc.
multi-stage builds
A solução é usar multi-stage builds, dessa forma você pode ter duas etapas no seu Dockerfile, uma de compilação que baixa o que for necessário, compila o código, etc. e outra que apenas copia os executáveis e os recursos para os locais esperados e nem precisa ser baseada em alguma distribuição, pode ser uma imagem scretch que reduz ainda mais o tamanho final.
Exemplo de Dockerfile
Este é um pequeno exemplo de Dockerfile que usa multi-stage builds para primeiro criar um executável e em seguida criar uma imagem que contém apenas o próprio executável e mais nada.
FROM golang:latest as build
WORKDIR /build
ADD . .
RUN CGO_ENABLED=0 GOOS=linux \
go build -ldflags '-extldflags "-static"' -o app
FROM scratch
COPY --from=build /build/app /app
ENTRYPOINT ["/app"]
Pontos positivos
- Menor superfície de ataque, não tem nenhum software extra parado esperando ser expoitado é só o seu executável e mais nada.
- Mais fácil de escalar, simplesmente pela velocidade que um cluster vai conseguir baixar e subir novas instâncias.
Pontos negativos
- Se você está acostumado a acessar seus containers abrindo um shell, você não vai poder fazer isso com imagens scratch para isso precisa subir uma distribuição nem que seja uma bem leve.
- Debug pode ser um pouco mais complicado se dependendo de como seu sistema está projetado, se você cometer um erro e seu executável retornar um panic você precisa garantir que está observando a saída de erro.
Boa prática
Usar multi-stage build é sem dúvida uma boa prática não importando a linguagem, é simples de implementar e os pontos negativos são facilmente contornados.