# ============================================================================== # ЭТАП СБОРКИ (BUILDER) # ============================================================================== # Используем официальный образ Go на базе легковесного дистрибутива Alpine Linux. # Именуем этот этап как "builder", чтобы позже забрать из него скомпилированный файл. FROM golang:1.27-alpine AS builder # Устанавливаем Git без сохранения кэша пакетов (уменьшает размер слоя). # Он необходим Go для скачивания внешних зависимостей (например, golang.org/x/sys). RUN apk add --no-cache git # Объявляем переменные окружения для создания изолированного системного пользователя. ENV USER=stunuser ENV UID=10001 # Создаем безопасного пользователя внутри сборочного контейнера: # --disabled-password: без пароля (вход по паролю заблокирован) # --gecos "": отключает заполнение анкетных данных (имя, телефон и т.д.) # --home "/nonexistent": без домашней папки # --shell "/sbin/nologin": запрещает пользователю выполнять bash/sh команды в системе # --no-create-home: принудительно запрещает создавать домашний каталог RUN adduser --disabled-password --gecos "" --home "/nonexistent" --shell "/sbin/nologin" --no-create-home --uid "${UID}" "${USER}" # Устанавливаем рабочую директорию, где будет происходить сборка кода WORKDIR /app # Шаг оптимизации кэша Docker: сначала копируем только файлы зависимостей go.mod и go.sum. # Звездочка * защищает от ошибки сборки, если файла go.sum еще физически не существует. COPY go.mod* go.sum* ./ # Если файл go.mod существует, скачиваем все внешние библиотеки и проверяем их хэш-суммы. # Благодаря этому шагу зависимости будут кэшироваться и не будут скачиваться заново при изменении кода. RUN if [ -f go.mod ]; then go mod download && go mod verify; fi # Копируем весь оставшийся исходный код проекта (main.go и другие файлы) в рабочую директорию COPY . . # Компилируем высокопроизводительный бинарник: # CGO_ENABLED=0: отключаем зависимости от C-библиотек (делает бинарник полностью автономным) # GOOS=linux: жестко указываем целевую операционную систему Linux # -ldflags="-s -w": отключает генерацию отладочной информации (символов) и таблиц DWARF. # Это уменьшает размер итогового файла почти в 2 раза без потери скорости. RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/stun_server . # ============================================================================== # ФИНАЛЬНЫЙ ЭТАП (PRODUCTION) # ============================================================================== # FROM scratch — это пустой слой. В нем нет Linux, нет bash, нет пакетных менеджеров. # Это гарантирует минимальный размер контейнера и максимальную безопасность (его невозможно взломать). FROM scratch # Переносим файлы пользователей и групп, созданные на этапе сборки. # Без этого контейнер scratch не узнает, кто такой "stunuser". COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /etc/group /etc/group # Копируем единственный скомпилированный исполняемый файл STUN-сервера из этапа builder COPY --from=builder /app/stun_server /stun_server # Переключаем контекст выполнения. Сервер будет работать от имени stunuser, а не root. # Если в коде сервера найдут уязвимость, злоумышленник все равно не получит права root на хосте. USER stunuser:stunuser # Документируем, что контейнер слушает порт 3478 по протоколу UDP EXPOSE 3478/udp # Точка входа: при старте контейнера автоматически запускается наш STUN-сервер. # Использование квадратных скобок [строгий JSON] запускает процесс напрямую (PID 1) без лишней оболочки sh. ENTRYPOINT ["/stun_server"]