Ответ
Зависимости следует устанавливать на раннем этапе сборки (build stage), но после копирования файла менеджера зависимостей (например, requirements.txt, package.json). Это критически важно для эффективного использования кэша слоев Docker.
Оптимальная последовательность в Dockerfile:
- Установить базовый образ.
- Скопировать файл со списком зависимостей.
- Установить зависимости.
- Скопировать остальной код приложения.
Пример для Python-приложения:
FROM python:3.11-slim as builder
# Устанавливаем системные зависимости, если нужны для компиляции Python-пакетов
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 1. Копируем ТОЛЬКО файл зависимостей
COPY requirements.txt .
# 2. Устанавливаем зависимости. Этот слой будет закэширован.
RUN pip install --user --no-cache-dir -r requirements.txt
# Финальный, минимальный образ
FROM python:3.11-slim
WORKDIR /app
# Копируем установленные зависимости из стадии builder
COPY --from=builder /root/.local /root/.local
# 3. Копируем весь код приложения (этот слой меняется чаще всего)
COPY . .
# Добавляем локальные pip-пакеты в PATH
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "main.py"]
Почему этот порядок важен?
- Кэширование: Слой
RUN pip install ...кэшируется Docker. Еслиrequirements.txtне изменился, при следующей сборке Docker повторно использует кэшированный слой с уже установленными зависимостями, что сильно ускоряет процесс. - Если бы мы скопировали весь код (
COPY . .) до установки зависимостей, то любое изменение в любом файле кода приводило бы к инвалидации кэша, и зависимости переустанавливались бы каждый раз. - Multi-stage build (использование
as builder) позволяет оставить в итоговом образе только зависимости времени выполнения, без компиляторов и промежуточных артефактов, что уменьшает размер финального образа.
Ответ 18+ 🔞
А, ну ты держись, сейчас я тебе такую простыню накатаю про зависимости в Docker, что ты офигеешь. Это ж основа основ, а народ постоянно наступает на одни и те же грабли, ёпта.
Смотри, чтобы не было как в том анекдоте про мартышку с гранатой, надо зависимости ставить прям в начале сборки (build stage), но только после того, как скопируешь файлик с их списком. Это, блядь, архиважно, если хочешь, чтобы кэш Docker работал, а не просто так ресурсы машины жрал.
Идеальный расклад в Dockerfile должен быть такой, чтобы даже кот соседский понял:
- Берём голый образ, на котором будем всё строить.
- Тащим в контейнер ОДИН файл — тот самый список зависимостей (
requirements.txt,package.json— ну ты понял). - Устанавливаем по этому списку всё, что нужно. Вот этот шаг и закэшируется на ура.
- И только ПОТОМ, когда всё установлено, закидываем туда весь наш остальной код, который меняется каждые пять минут.
Вот смотри, как для питонячего приложения выглядит нормальный Dockerfile, а не пиздопроебина какая-то:
FROM python:3.11-slim as builder
# Иногда для сборки каких-нибудь нативных библиотек нужен компилятор. Ставим его тут.
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 1. Копируем ТОЛЬКО файл с зависимостями, больше нихуя!
COPY requirements.txt .
# 2. Ставим сами зависимости. Вот этот слой — золотой, он закэшируется.
RUN pip install --user --no-cache-dir -r requirements.txt
# А теперь делаем финальный, чистенький и лёгкий образ
FROM python:3.11-slim
WORKDIR /app
# Перетаскиваем уже установленные зависимости из первой стадии (builder)
COPY --from=builder /root/.local /root/.local
# 3. А вот теперь, в последний момент, копируем весь наш код. Его-то мы и меняем постоянно.
COPY . .
# Чтобы система видела наши локально установленные питоньи пакеты
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "main.py"]
А теперь, блядь, слушай сюда, почему это так работает, а иначе — пиздец:
- Волшебный кэш: Этот шаг
RUN pip install ...— он как отпечаток пальца. Если твойrequirements.txtне менялся, Docker такой: «О, да я это уже делал!» — и берёт готовый слой из кэша. Сборка из трёх минут превращается в три секунды. Удивление пиздец, да? - А если бы ты скопировал весь код (
COPY . .) в самом начале, то любое изменение в любом файлике — хоть в README запятую поправил — приводило бы к полной пересборке ВСЕГО, включая установку зависимостей. Это же терпения ноль ебать, каждый раз ждать. - Multi-stage build (это когда
as builderпишем) — это вообще гениальная штука. Мы в первой стадии наворотили всего, что нужно для сборки (типа того компилятораgcc), а в финальный образ забрали только результат — сами библиотеки. Весь строительный мусор остался за бортом. Итоговый образ легче, чище, безопаснее. Красота, я тебе в рот чих-пых!