Коротко: Go компилируется в один статический бинарник, поэтому образ собирают multi-stage — итог в единицы мегабайт вместо сотен.
Коротко: Go компилируется в один статический бинарник, поэтому образ для него собирают в два этапа (multi-stage) — на первом компилируют в «толстом» образе с тулчейном, на втором копируют только готовый бинарник в минимальный образ. Итог — образ в единицы мегабайт вместо сотен. Ниже рабочий Dockerfile и нюансы.
Зачем multi-stage
Если просто собрать в golang:1.22 и оставить как есть — в образ попадёт весь Go-тулчейн, исходники и кэш (~800+ МБ). Это лишнее в проде: для запуска нужен только скомпилированный бинарник. Multi-stage решает это: компиляция в одном слое, рантайм — в другом.
Базовый Dockerfile
FROM golang:1.22 AS build
WORKDIR /src
# сначала зависимости — кэш слоёв при неизменных go.mod/go.sum
COPY go.mod go.sum ./
RUN go mod download
# затем код
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app ./cmd/server
# --- минимальный финальный образ ---
FROM alpine:3.20
RUN apk add --no-cache ca-certificates
COPY --from=build /app /app
ENTRYPOINT ["/app"]
Разбор ключевых моментов
CGO_ENABLED=0— отключает cgo, получаем полностью статический бинарник без зависимостей от системных библиотек. Без этого alpine может ругаться на отсутствие glibc.- Сначала
go.mod/go.sum, потом код. Слой с зависимостями кэшируется и не пересобирается, пока не изменились зависимости — ускоряет повторные сборки. ca-certificates— нужны, если бинарник ходит по HTTPS. Иначе будут ошибки TLS.COPY --from=build— берём из стадии сборки только бинарник.
Ещё меньше: scratch и distroless
Если бинарник полностью статический, финальный образ можно сделать почти нулевым:
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app /app
ENTRYPOINT ["/app"]
scratch — пустой образ (0 байт базы). Минус: нет shell и утилит для отладки. Компромисс — gcr.io/distroless/static: чуть больше, но безопаснее и с сертификатами «из коробки».
Уменьшаем размер бинарника
Флаги линковки убирают отладочную информацию:
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/server
-s -w выкидывают таблицу символов и DWARF — бинарник становится заметно легче (ценой отсутствия отладочных символов в проде, что обычно и нужно).
.dockerignore обязателен
Чтобы в контекст сборки не уезжало лишнее (ускоряет билд и уменьшает шум):
.git
*.md
/tmp
/bin
Dockerfile
.dockerignore
Прокидывание версии в бинарник
Версию/коммит удобно зашить при сборке через ldflags:
ARG VERSION=dev
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$VERSION" -o /app ./cmd/server
В коде это var version string в пакете main — подставится значение при компиляции.
Healthcheck и непривилегированный пользователь
Для прода стоит запускать не от root и добавить healthcheck:
FROM alpine:3.20
RUN apk add --no-cache ca-certificates && adduser -D -u 10001 app
USER app
COPY --from=build /app /app
HEALTHCHECK --interval=30s CMD ["/app", "healthcheck"]
ENTRYPOINT ["/app"]
Multi-stage + статический бинарник — одна из причин, почему Go так приятно эксплуатировать: лёгкие образы, быстрый деплой, мало поверхности для атак. Если выстраиваете CI/CD и контейнеризацию сервисов, могу помочь на консультации или в рамках разработки.


Комментарии