Ситуация знакомая: на машине стоит свежий дистрибутив, а запустить нужно сборку, которой несколько лет. Системные библиотеки уже другие, ABI поменялся, а бинарник требует конкретных версий. Docker с официальным образом — очевидный первый кандидат, но он не всегда подходит: нужен доступ к железу, интеграция с системными сервисами, отладка в реальном окружении или просто нет возможности тащить контейнерный рантайм на прод-сервер. Тогда остаётся собрать окружение вручную — аккуратно и с пониманием, что именно ломается.
Главная ошибка новичка — пытаться подсунуть старые библиотеки через LD_LIBRARY_PATH глобально или, хуже того, заменить системные пакеты. Это почти гарантированно приведёт к поломке других приложений и пакетного менеджера. Правильный путь — изоляция: собственный тулчейн, собственные зависимости и явное указание, где их искать. Ниже — рабочий каркас такого подхода.
Зачем свой компилятор
Старый код часто не собирается новым GCC: ужесточились проверки, изменились заголовочные файлы, появились новые предупреждения, которые превращаются в ошибки. Обратная ситуация тоже бывает — новый код требует свежих стандартов C++ или C. Если вы работаете со старым софтом, разумно поставить отдельный GCC вроде 9 или 10 в собственный префикс, например /opt/toolchain, и не трогать системный.
- Скачайте исходники нужной версии GCC и соберите с --prefix=/opt/toolchain и включёнными языками, которые реально нужны (C, C++).
- Убедитесь, что в PATH для вашей сессии первым идёт /opt/toolchain/bin, но только в рамках скрипта сборки, а не глобально.
- Проверьте, что gcc --version и ldd --version указывают на нужные бинарники, прежде чем собирать зависимости.
Сборка GCC — процесс небыстрый, зато предсказуемый. Если версия нужна только для одного проекта, альтернатива — готовые тулчейны от вендоров или пакеты из репозиториев старых дистрибутивов, распакованные в отдельный префикс. Главное — не смешивать их с системными.
Своя криптобиблиотека и другие зависимости
OpenSSL — классический источник боли: старые приложения ждут версию 1.1, а в свежих дистрибутивах уже 3.x с другим API и SONAME. Собирать OpenSSL нужно тем же тулчейном и в тот же префикс, чтобы не возникло конфликтов. Дальше по списку — zlib, libcurl, ICU, libxml2: всё, что требует конкретной версии, ставится в изолированный каталог.
- Собирайте зависимости с --prefix=/opt/myapp и фиксируйте версии в скрипте сборки, чтобы результат воспроизводился.
- Не ставьте их в /usr/local без нужды: это усложнит жизнь другим проектам и системе.
- Проверяйте, какие библиотеки реально тянет бинарник: ldd и readelf -d покажут зависимости и текущие пути поиска.
Отдельно стоит продумать, как приложение будет находить свои библиотеки. Именно здесь начинаются проблемы, которые проявляются не при сборке, а при запуске — часто уже на другом сервере.
RPATH вместо сюрпризов
RPATH (или более современный RUNPATH) — это запись в бинарнике, которая говорит загрузчику, где искать библиотеки. Это честнее, чем переменные окружения: путь зашит в артефакт и работает одинаково у всех, кто его запускает. При линковке используйте -Wl,-rpath,/opt/myapp/lib и, при необходимости, -Wl,--enable-new-dtags для RUNPATH.
- Указывайте абсолютные пути к каталогу с вашими библиотеками, а не к конкретным файлам.
- Проверьте результат: readelf -d ./binary | grep -i path должен показать ожидаемую запись.
- Убедитесь, что в RPATH нет путей к системным каталогам — иначе вы рискуете подхватить не те версии.
- Не полагайтесь на LD_LIBRARY_PATH как на основной механизм: он легко перекрывается и создаёт разное поведение на разных машинах.
Типичная ошибка — прописать RPATH в момент сборки, а потом перенести бинарник в другой каталог. Путь останется прежним, и приложение не найдёт библиотеки. Если перенос неизбежен, используйте относительный RPATH через $ORIGIN, например -Wl,-rpath,'$ORIGIN/../lib', но тогда следите за структурой каталогов при деплое.
Что проверить перед запуском
Прежде чем отдавать сборку коллегам или на сервер, пройдите короткий чек-лист. Он экономит часы отладки и снижает риск неожиданных падений в проде.
- Запустите бинарник в чистом окружении: env -i с минимальным набором переменных.
- Проверьте, что ldd не показывает not found и не тянет системные версии вместо ваших.
- Убедитесь, что используемые библиотеки собраны тем же тулчейном и совместимы по ABI.
- Зафиксируйте версии всех компонентов в README или скрипте — через полгода это спасёт.
Отдельно продумайте обновления: если приложение живёт долго, изолированный префикс упростит переход на новые версии зависимостей без риска для системы. Держите сборку воспроизводимой, а пути — предсказуемыми.
Итог простой: старый софт на свежем Linux — это не про героические правки системных пакетов, а про дисциплину изоляции. Свой компилятор, свои зависимости, честный RPATH и проверка в чистом окружении дают предсказуемый результат. Docker остаётся хорошим вариантом, когда он уместен, но ручная сборка даёт контроль, который в ряде сценариев важнее удобства.