Почему «просто работающий» Docker — не продакшн

Многие команды переходят на Docker и думают, что достаточно написать Dockerfile и запустить docker run. Между «работает локально» и «работает в продакшне» — пропасть. Разберём конкретные практики, которые отличают production-деплой от тестового стенда.

Multi-stage сборка: меньше размер, меньше атак

Multi-stage сборки позволяют разделить этап компиляции и финальный образ. Компилятор, сборочные инструменты и dev-зависимости остаются только во временном слое и не попадают в production-образ.

Типичная ошибка: включать компиляторы и dev-зависимости в финальный образ. Он раздувается до 1–2 ГБ вместо 50–100 МБ.

dockerfile# Этап 1: сборка
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .

# Этап 2: минимальный финальный образ (~8 МБ)
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]

Healthcheck и политики перезапуска

Docker не знает, работает ли приложение корректно — только то, что процесс запущен. Добавьте HEALTHCHECK и настройте зависимости через depends_on с условием service_healthy.

yamlservices:
  app:
    build: .
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
      interval: 10s
      retries: 5

Управление секретами

Никогда не храните секреты в Dockerfile или переменных окружения, зашитых в образ. Используйте BuildKit secrets для значений, нужных только при сборке, и Docker Secrets / Vault для runtime-секретов.

dockerfile# Секрет используется при сборке и не попадает в слои образа
# docker build --secret id=npmrc,src=$HOME/.npmrc .
RUN --mount=type=secret,id=npmrc \
    cp /run/secrets/npmrc .npmrc && \
    npm ci --production && \
    rm .npmrc

Docker Compose: разделение по окружениям

Используйте override-файлы: docker-compose.yml — базовая конфигурация, docker-compose.prod.yml — продакшн настройки с лимитами ресурсов и политикой логирования.

yaml# docker-compose.prod.yml
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"

Запуск: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d. Файл docker-compose.override.yml добавляйте в .gitignore.

Чеклист перед деплоем

  • Multi-stage сборка с минимальным финальным образом
  • Непривилегированный пользователь (USER nonroot)
  • HEALTHCHECK в Dockerfile и depends_on с проверкой
  • Политика restart: unless-stopped
  • Лимиты CPU и памяти
  • Секреты вне образа и переменных окружения
  • Настроенная ротация логов
  • Файл .dockerignore
102 оценки
Комментарии 6
ДФ
dfedorovСисадмин42 мин назад

Насчёт образа scratch — будьте осторожны с отладкой. Когда нет шелла, docker exec бесполезен. Держите отдельный debug-тег с busybox или distroless debug.

Ответить
КВ
k_volkovBackend1 ч назад

Спасибо! Вопрос: как вы делаете zero-downtime обновление через docker compose? Старый контейнер иногда не успевает завершить текущие запросы.

Ответить
НС
n_sidorovaDevOps2 ч назад

Раздел про лимиты ресурсов — золото. У нас один сервис съел всю память и положил соседей. После limits таких проблем нет.

Ответить
ПН
p_nikitinЧитатель3 ч назад

Добавлю к чеклисту: не забывайте про .dockerignore! Без него node_modules и .git попадают в контекст сборки и всё замедляют.

Ответить
ОЗ
o_zaytsevTech Lead5 ч назад

Отличный материал. Добавлю ещё одну практику: сканируйте образы на уязвимости через docker scout cves или trivy в CI/CD пайплайне — поймаете проблемы до деплоя.

Ответить
АМ
Алексей Морозов
DevOps-инженер · Москва

7 лет строю infrastructure as code. Последние 4 года — Kubernetes и Docker в production финтех-компаний. Люблю автоматизировать всё, что можно автоматизировать.