


Multi-stage build разделяет Dockerfile на несколько этапов через инструкции FROM. В одном этапе можно скомпилировать приложение, а в финальный перенести только нужный результат через COPY --from. Так компилятор и промежуточные файлы не обязаны попадать в образ запуска.
В учебном примере Mentoring.digital соберём консольную Go-программу без внешних модулей. Она только печатает строку. Для настоящего сервиса понадобятся дополнительные требования к сертификатам, конфигурации, сигналам и доступу к файлам.
В отдельной папке сохраните main.go:
package main
import "fmt"
func main() {
fmt.Println("Ready")
}
Рядом создайте Dockerfile:
# syntax=docker/dockerfile:1
FROM golang:1.26 AS build
WORKDIR /src
COPY main.go ./
RUN CGO_ENABLED=0 go build -o /out/demo ./main.go
FROM scratch AS runtime
COPY --from=build /out/demo /demo
USER 65532:65532
ENTRYPOINT ["/demo"]
Этап build содержит Go и исходник. Команда компилирует один файл, поэтому для этого минимального примера не требуется go.mod. В runtime попадает только /demo. Имя build используется вместо номера этапа, чтобы назначение копирования оставалось понятным при добавлении других стадий.
docker build --target runtime -t mentoring-demo:local .
docker run --rm mentoring-demo:local
docker image inspect mentoring-demo:local --format "{{.Config.User}}"
При успешной сборке запуск программы должен вывести Ready, а поле пользователя в метаданных образа должно быть 65532:65532. Эти признаки проверяют запуск примера и конфигурацию пользователя. Они не доказывают безопасность произвольного приложения и не заменяют проверку содержимого образа.
Для исследования стадии сборки можно отдельно выполнить docker build --target build -t mentoring-demo-build:local .. Это создаёт другой локальный образ с инструментами сборки. Его не нужно путать с минимальным runtime. Теги в примере относятся к вашему компьютеру, публикация в реестр здесь не выполняется.
Scratch является пустой основой. В ней нет обычной оболочки, пакетного менеджера и набора системных файлов. Попытка запустить sh внутри такого образа не будет обычным способом отладки.
В нашем примере CGO_ENABLED=0 отключает cgo для простой Go-программы. Если приложение требует динамических библиотек, внешних программ или системных ресурсов, перенос одного бинарного файла может оказаться недостаточным. HTTPS-клиенту могут понадобиться корневые сертификаты, а операции со временем могут зависеть от данных часовых поясов.
Поэтому сначала составьте список runtime-зависимостей. Для приложения с такими потребностями разумнее выбрать подходящий минимальный базовый образ и явно добавить необходимые файлы. Многоэтапная сборка не требует использовать именно scratch.
Финальная стадия может стать меньше, но конкретный размер нужно измерить. Кэш сборки при этом остаётся отдельным потребителем диска: небольшой runtime-образ не означает, что сборщик перестал хранить промежуточные данные.
На курсе Docker с наставником можно разобрать собственную цепочку сборки и запуска. Наставнику по Docker покажите Dockerfile, перечень runtime-зависимостей и воспроизводимую ошибку, чтобы выбрать этапы по задаче приложения.