Компилировать и собирать приложение из исходного кода лучше непосредственно в процессе сборки образа – так мы минимизируем влияние своей собственной операционной системы, однако у этого есть и обратная сторона. Размеры образов построенных таким образом порой получаются достаточно большими.
Причина этого в следующем. Из-за того что мы собираем свое приложение на этапе построения образа, мы также тащим за собой все инструменты и библиотеки, которые были необходимы только для сборки и компиляции, но не для работы самого приложения. Так, для запуска приложения на java нам достаточно иметь JRE в контейнере где будет запущено приложение, а тащить за собой весь JDK и зависимости – излишество. Аналогично и для приложения на go – инструменты языка go нам нужны только на этапе компиляции приложения, сам бинарный файл же не требует ничего дополнительно для работы.
Решить эту проблему можно с помощью многоступенчатой сборки.
Dockerfile в этом случае состоит из нескольких ступеней или стадий, каждая из которых по структуре выглядит как Dockerfile состоящий из одной ступени. У разных ступеней Dockerfile могут быть разные базовые образы, и каждая последующая ступень может пользоваться результатами предыдущих ступеней для построения своего образа.
Пример Dockerfile состоящего из одной ступени для приложения на go
FROM golang:1.21-alpine
WORKDIR /app
COPY main.go .
RUN go build -o hello-go main.go
CMD ["./hello-go"]
А ниже пример многоступенчатой сборки того же приложения
# первая ступень
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY main.go .
RUN go build -o hello-go main.go
# вторая ступень
FROM alpine:3.15
WORKDIR /app
COPY --from=builder /app/hello-go .
CMD ["./hello-go"]
Здесь для построения приложения использовался базовый образ golang:1.21-alpine
, а для запуска – alpine:3.15
. В результате итоговый образ получается сильно меньшего размера.
Примеры проектов на Java и Go
Подбробнее в видео на Youtube