Контейнеризация и принципы 12-факторных приложений
Мы подошли к этапу, когда код должен покинуть локальную машину и отправиться в продакшен. Для React-разработчика процесс сборки привычен, но в Go он дает суперсилу: благодаря статической линковке вы получаете один компактный файл. Ему не нужны node_modules, интерпретаторы или тяжелые среды выполнения.
Принципы 12-Factor App в 2026 году
Методология 12-Factor App — это стандарт разработки облачных приложений (Cloud Native). Эти правила гарантируют, что сервис будет работать предсказуемо и в Docker на ноутбуке, и в кластере Kubernetes.
Сегодня мы внедрим четыре ключевых фактора:
- III. Конфигурация. Храним настройки в переменных окружения (Environment Variables), а не в коде.
- VI. Процессы. Приложение должно быть Stateless (без сохранения состояния). Все данные отправляем во внешнее хранилище, например в PostgreSQL.
- IX. Утилизируемость. Быстрый запуск и корректное завершение. Мы подготовим базу, а детали разберем в теме про Graceful Shutdown.
- XI. Логи. Рассматриваем логи как поток событий. Просто пишем в
stdout, а инфраструктура сама их соберет.
От гигабайтов к мегабайтам: Multi-stage build
В экосистеме Node.js образы весят сотни мегабайт. В Go мы используем Multi-stage build. Мы берем тяжелый образ с инструментами разработки только для компиляции, а в финальный контейнер забираем лишь готовый бинарный файл.
Как показано на Схеме 1, мы разделяем процесс на «сборочный цех» и «чистую комнату».
Ниже — Dockerfile, отвечающий стандартам безопасности 2026 года. Мы не используем root и выбираем максимально легкий фундамент.
# Этап 1: Сборка (Build stage)
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Кэшируем зависимости (вспомните тему про Go Modules)
COPY go.mod go.sum ./
RUN go mod download
# Собираем статический бинарник
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /main ./cmd/server
# Этап 2: Финальный образ (Run stage)
# scratch — это абсолютно пустой образ
FROM scratch
# Переносим только исполняемый файл
COPY --from=builder /main /main
# Port binding (фактор VII)
EXPOSE 8080
ENTRYPOINT ["/main"]
🛡️ Безопасность: Образ
scratchне содержит даже командной оболочкиshилиbash. Это лишает злоумышленника инструментов внутри контейнера, если он найдет уязвимость в коде.
Конфигурация через окружение
Согласно фактору III, приложение не должно знать адрес базы данных до момента запуска. Используем пакет os для чтения переменных.
Плохо (жесткая привязка):
// При смене пароля придется пересобирать весь проект
dbURL := "postgres://user:pass@localhost:5432/mydb"
Хорошо (12-factor style):
import "os"
func main() {
// Docker пробросит эту переменную при старте
dbURL := os.Getenv("DATABASE_URL")
if dbURL == "" {
// Пишем в stdout (фактор XI)
fmt.Println("Критическая ошибка: DATABASE_URL не задана")
os.Exit(1)
}
// Инициализируем pgx...
}
Оркестровка через Docker Compose
Чтобы запустить микросервис вместе с базой данных одной командой, используем Docker Compose. Обратите внимание, как мы связываем компоненты через переменные окружения.
services:
app:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://user:password@db:5432/app_db
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=app_db
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app_db"]
interval: 5s
timeout: 5s
retries: 5
Практическое упражнение
- Создайте в корне проекта
Dockerfile, используя пример с Multi-stage build. - Переведите инициализацию HTTP-сервера и БД на использование
os.Getenv. - Соберите образ:
docker build -t my-go-app .. - Проверьте результат: размер образа должен быть в пределах 15–20 МБ. 🚀
Контейнеризация делает сервис Stateless (эфемерным) и готовым к масштабированию. Теперь это автономный кирпичик системы. Однако в реальности важно не только быстро запускаться, но и «вежливо» завершать работу.
В следующей теме мы научим наш контейнер правильно обрабатывать сигналы завершения с помощью Graceful Shutdown.