Полтора года мы собирали образы на том же сервере, где они и запускались. Работало до момента, когда сборка упёрлась в память: контейнер с mvn package выел 1,5 ГБ, ядро OOM-killer выбрало самый жирный процесс и убило заодно базу. Деплой, который роняет прод, — это не деплой.
Что плохого в сборке на сервере
- Память и процессор делятся с рабочими сервисами. Сборка — самый ресурсоёмкий процесс в жизни сервера.
- Тег
latestпересобирается непредсказуемо. «На сервере сейчас какой образ?» — вопрос без ответа. - Нет повторяемости. Зависимости кэшируются локально, и два запуска на двух машинах дают разные результаты.
Как делаю теперь
Сборка в CI, на сервер приходит только готовый артефакт по неизменяемому тегу (хэш коммита), и запускается он через docker compose с явным тегом.
services:
app:
image: registry.example.com/app:${TAG:?TAG is required}
restart: unless-stopped
mem_limit: 768m
environment:
- JAVA_TOOL_OPTIONS=-XX:MaxRAMPercentage=70
Три детали:
${TAG:?TAG is required}— compose откажется стартовать без явного тега. Никаких «взял что было».mem_limit— контейнер не имеет права вытеснить базу. Правильный лимит защищает от того, чтобы одна утечка превратилась в отказ всего сервера.MaxRAMPercentage=70— JVM в контейнере читает лимит и сам держится в нём, вместо того чтобы гадать по памяти хоста.
Про кэш сборки
Если сборка в CI каждый раз с нуля — это минус десять минут жизни. Кэш слоёв (cache-from/cache-to) решается за одну строку в CI-конфиге, но работает только при правильном порядке инструкций в Dockerfile: сначала зависимости, потом исходники. Копирование всего проекта первой строкой обнуляет кэш при любом изменении.
Когда собирать локально на сервере всё же нормально
Единственный случай — одноразовая отладка: когда нужно проверить, что именно ломается на целевой архитектуре. Но результат такой сборки не должен попадать в прод: это инструмент, а не процесс.