Not so long ago, we would install code to the global runtime path, where it would be available machine-wide. CPAN was perhaps the earliest to do this, but PEAR, rubygems, and probably others followed. After all, CPAN was seen as a great strength of Perl.
Users of such a library would say something like use Image::EXIF;
or include_once "Smarty/Smarty.class.php";
Notice that these are a mechanism to follow some include path to resolve relative filenames.
But as servers got larger faster than applications, co-installing those applications became attractive. The problem was that they can have different dependency requirements, which complicates the management of a shared include path.
This is about where Bundler happened for Ruby, and its ideas were brought to other ecosystems: Perl got carton, and PHP got composer. These systems build a directory within the project to hold the dependencies, and basically ignore the global include path entirely.
At the cost of disk space, this bypasses the problems of sharing dependencies between projects. It bypasses all the problems of working with multiple versions of dependencies that may be installed in different hosts’ include paths. It also makes the concept of the language’s include path obsolete, at least in PHP’s case: the Composer autoloader is a more-capable “include path” written in code. Finally, it allows piecemeal upgrades—a small service can make a risky upgrade and gain experience, before a larger or more important service on the machine needs to upgrade.
Piecemeal upgrades also enable independent teams to make more decisions on their own. Since their dependencies are not shared with other users, changes to them do not have to be coordinated with other users.
Containers are the next step in the evolution of this process. Pulling even more dependencies into a container brings all the advantages of local management of packages into a broader scope. It allows piecemeal upgrading of entire PHP versions. And it makes an application cost an order of magnitude more space, once again.
In our non-containerized environment, I can switch the PHP version on a host pretty easily, using co-installable versions and either update-alternatives (for the CLI) or a2disconf/a2enconf (for FPM), but this means the services do not really have a choice about their PHP version. It has been made before the application's entry point is reached.