Это простой вопрос со сложным ответом.
Да, никс пакеты - это не контейнеры, а другой вид абстракции. Но в конечном счете результат похожий.
Задаётся необходимая версия пакета и в результате создаётся нечто, что называется derivation. В свою очередь derivation - это штука, в которую тем или иным способом засовывается/линкуется всё, что необходимо для работы программы. Таким образом, можно одновременно использовать программу A, которой нужно libc версии 2.31 и программу B, которой нужно libc версии 2.10. Для libc обоих версий, в свою очередь, будет созданы свои независимые друг от друга derivation. Или же можно использовать несколько версий одной и той же программы.
Более того, в описании пакета, которое чем-то напоминает ebuild в gentoo, можно задавать кроме версии еще и флаги сборки. То есть одновременно может сосуществовать несколько вариантов одной и той же программы с разными флагами. Если в бинарном кэше оверлеев нету такого уже собранного варианта, программа будет собрана из исходников.
Деривации идентифицируются уникальными хэшами, поэтому если хоть что-то отличается при сборке одной и той же программы, хэш будет разным, а значит это будет две совершенно разные деривации.
Под капотом там довольно сложная система с хэш таблицами, симлинками, изменением линковки итд. И всё это работает на чистом функциональном языке, который очень похож на хаскель. Ещё есть специальные утилиты для патча динамической линковки в уже собранном софте (полезно для проприетарных помоев), исправления шебэнгов итд.