Почему я перевёл свои Docker контейнеры на Alpine Linux
С чего всё началось
У меня на сервере крутится около 20 Docker контейнеров. Каждый на Ubuntu или Debian. И каждый весит 200-400 MB. Я посчитал суммарный размер образов на диске — почти 8 гигабайт! Для террабайтного NVMe это копейки, но меня бесила неэффективность.
Начал смотреть, какие образы можно уменьшить. Нашёл Alpine Linux — дистрибутив на musl и busybox, который весит около 5 MB. Да, 5 мегабайт против 200 у Ubuntu.
Решил перевести все свои Dockerfile на Alpine.
С чем столкнулся
Первая проблема — musl вместо glibc. Многие программы собираются под glibc и на Alpine падают с ошибками. Например, npm-пакет sharp не собирался на Alpine, потому что требует glibc. Пришлось ставить libc6-compat через apk.
Вторая — пакетный менеджер apk. Я привык к apt, а apk работает иначе: команда не apt install python3, а apk add python3. Пришлось переписывать Dockerfile, привыкать. Но apk быстрее apt — это факт.
Третья — Node.js на Alpine официально поддерживается, но есть нюанс: версии в apk часто устаревшие. Лучше ставить Node через официальный образ node:alpine, а не через apk.
Python тоже норм. Стандартные библиотеки работают, но некоторые модули (например, mysqlclient) требуют компиляции, и на Alpine нужно ставить musl-dev и gcc.
Что я получил
Размер образов уменьшился в 5-10 раз. Мой nginx на Alpine весит 12 MB вместо 150 на Ubuntu. Python-сервисы — 50 MB вместо 300. PHP-FPM — 30 MB вместо 200.
Билды стали быстрее. apk качает пакеты быстрее apt. Сборка контейнера с нуля на Alpine занимает 30 секунд, на Ubuntu — 2-3 минуты.
Поверхность атаки меньше. Alpine минимален — в нём меньше потенциальных уязвимостей в стандартной поставке.
Мой первый Alpine контейнер
Помню, как собрал первый образ на Alpine для Python-скрипта, который парсит RSS и шлёт в Telegram. Раньше он жил в контейнере на Ubuntu — 280 MB. После переезда на Alpine стало 35 MB. Я обалдел. Перезапустил контейнер — стартанул за секунду. Раньше на Ubuntu грузился секунды 3-4. Разница вроде небольшая, но когда 20 контейнеров перезагружаются после обновления ядра — экономия времени ощутимая.
Что меня бесило в Alpine
Первое — отладка. В Alpine нет bash, только sh (busybox). Если привык к стрелочкам вверх и автодополнению — будешь страдать. Я ставлю bash через apk в dev-контейнерах, но в прод я его не тащу.
Второе — DNS. Alpine использует musl, у которого свои тараканы с резолвом. Была ситуация: контейнер на Alpine не мог достучаться до другого контейнера по hostname, пока я не прописал dns-сервер явно в docker-compose. На Ubuntu эта проблема не всплывала.
Третье — логи. По умолчанию в Alpine нет rsyslog или syslog-ng. Пришлось настраивать логгирование в stdout через Docker, чтобы логи попадали в docker logs. Мелочь, но кто первый раз ставит — теряет время.
Главный аргумент для перехода
Знаете, что меня окончательно убедило? Я обновил все образы на сервере, запустил docker system df и увидел: 8.2 GB стало 1.3 GB. Семь гигабайт свободного места на диске. Для домашнего сервера с NVMe на 256 GB — это серьёзно.
Плюс билды в CI/CD стали проходить за 40 секунд вместо 3 минут. Когда ты пушишь фикс и ждёшь деплой, каждая секунда на счету.
Я не фанатик. Если Alpine глючит с какой-то библиотекой — я не мучаюсь, беру Ubuntu. Но для 80% моих контейнеров Alpine — идеал. Маленький, быстрый, безопасный.
Кстати, я даже nginx собираю из сорцов на Alpine, а не беру готовый образ от nginx. Потому что официальный nginx:alpine — 11 MB, а nginx:latest — 187 MB. Разница в 17 раз. Задумайтесь.
Где я оставил Ubuntu
Не все контейнеры перевёл. Тяжёлые сервисы с кучей зависимостей (например, базы данных) оставил на Debian — там стабильнее поддержка драйверов. MySQL и PostgreSQL официально рекомендуют Debian. Для n8n оставил Ubuntu, потому что на нём проще отлаживать.
Но для простых сервисов типа nginx, Python-скриптов, PHP-FPM — только Alpine.
Совет
Если ваш контейнер делает что-то простое — проксирует запросы, выполняет Python-скрипты, отдаёт статику — переводите на Alpine. Сэкономите место, время сборки и нервы.
Если используете сложные библиотеки с нативными расширениями — сначала проверьте совместимость в тестовом контейнере. MySQL, Redis, PostgreSQL лучше не трогать.