Ситуация знакомая: на машине стоит свежий дистрибутив, а запустить нужно сборку, которой несколько лет. Системные библиотеки уже другие, 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 остаётся хорошим вариантом, когда он уместен, но ручная сборка даёт контроль, который в ряде сценариев важнее удобства.