Как организовать установку зависимостей и сборку кода в Docker?

«Как организовать установку зависимостей и сборку кода в Docker?» — вопрос из категории Docker, который задают на 24% собеседований Devops Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Организация установки зависимостей и сборки в Docker — это критически важный аспект для создания эффективных, безопасных и воспроизводимых образов. Вот мой подход:

1. Многоступенчатая сборка (Multi-stage builds) Это основной паттерн, который я использую для разделения этапов сборки и выполнения.

Пример для Go приложения:

# Этап 1: Сборка зависимостей
FROM golang:1.21 AS deps
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

# Этап 2: Тестирование
FROM deps AS tester
COPY . .
RUN go test ./... -v

# Этап 3: Сборка бинарника
FROM deps AS builder
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/main ./cmd/app

# Этап 4: Финальный образ
FROM gcr.io/distroless/static-debian11
COPY --from=builder /app/main /app/main
USER nonroot:nonroot
ENTRYPOINT ["/app/main"]

2. Оптимизация кэширования зависимостей Копирую файлы зависимостей отдельно от исходного кода, чтобы максимально использовать кэш Docker.

Для Node.js:

FROM node:18-alpine AS deps
WORKDIR /app

# Копируем только package файлы
COPY package.json package-lock.json* ./

# Устанавливаем зависимости
RUN npm ci --only=production && 
    cp -R node_modules production_node_modules

# Устанавливаем dev зависимости отдельно
RUN npm ci

# Копируем исходный код и собираем
COPY . .
RUN npm run build

# Финальный образ
FROM node:18-alpine
WORKDIR /app
COPY --from=deps /app/production_node_modules ./node_modules
COPY --from=deps /app/dist ./dist
COPY --from=deps /app/package.json ./package.json
USER node
CMD ["node", "dist/index.js"]

3. Использование специфичных команд установки

  • npm: npm ci вместо npm install для предсказуемости и скорости
  • pip: pip install --no-cache-dir -r requirements.txt
  • apt: Объединение update, install и clean в одной RUN-инструкции

4. Безопасность и сканирование зависимостей

# Этап сканирования уязвимостей
FROM aquasec/trivy:latest AS scanner
COPY --from=builder /app /app
RUN trivy filesystem --exit-code 1 --severity HIGH,CRITICAL /app

# Этап сканирования лицензий
FROM fossa/fossa-cli AS license-check
COPY --from=builder /app /app
WORKDIR /app
RUN fossa analyze --output

5. Пример для Python приложения:

# Этап сборки
FROM python:3.11-slim AS builder
WORKDIR /app

# Установка системных зависимостей
RUN apt-get update && 
    apt-get install -y --no-install-recommends gcc build-essential && 
    rm -rf /var/lib/apt/lists/*

# Копируем и устанавливаем Python зависимости
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# Копируем исходный код
COPY . .

# Запускаем тесты
RUN python -m pytest tests/

# Финальный образ
FROM python:3.11-slim
WORKDIR /app

# Копируем зависимости из builder
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app .

# Настраиваем PATH для Python пакетов
ENV PATH=/root/.local/bin:$PATH

# Создаем непривилегированного пользователя
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# Запускаем приложение
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "wsgi:app"]

6. Использование BuildKit для оптимизации

# syntax=docker/dockerfile:1.4

# Используем BuildKit секреты для безопасной передачи ключей
RUN --mount=type=secret,id=npm_token 
    npm config set //registry.npmjs.org/:_authToken $(cat /run/secrets/npm_token) && 
    npm ci

# Используем кэш для npm
RUN --mount=type=cache,target=/root/.npm 
    npm ci

# Используем кэш для apt
RUN --mount=type=cache,target=/var/cache/apt 
    apt-get update && apt-get install -y package

7. Интеграция с CI/CD В CI/CD пайплайне я дополнительно:

  • Кэширую слои Docker между сборками
  • Использую docker buildx для multi-arch сборок
  • Подписываю образы с помощью Cosign
  • Сохраняю SBOM (Software Bill of Materials) с помощью Syft

Ключевые принципы, которых я придерживаюсь:

  1. Минимализм: Только необходимые зависимости в финальном образе
  2. Воспроизводимость: Фиксированные версии всех зависимостей
  3. Безопасность: Регулярное сканирование на уязвимости
  4. Эффективность: Максимальное использование кэширования
  5. Стандартизация: Единый подход для всех проектов в организации