Programs are rarely self-contained. A web server requires SSL libraries for HTTPS, compression libraries for gzip, and regular expression libraries for URL rewriting. These dependencies must be installed before the program can run. Package management resolves dependencies automatically—it installs missing libraries, checks version compatibility, and prevents conflicts.
An nginx package declares dependencies such as libc6, libssl3, and libpcre2-8-0. The package manager checks: Are these packages installed? Do the versions match? If not, the dependencies are installed automatically. The user enters install nginx, and the system installs nginx together with the 15 required libraries.
Libraries and Dynamic Linking
Libraries are reusable code. The C standard library (libc) provides fundamental functions—string handling, file access, and memory management. OpenSSL provides cryptography. libpng handles PNG images. Programs use these libraries instead of implementing everything themselves.
Dynamic linking loads libraries at runtime. An nginx binary contains only the nginx code, not an SSL implementation. At startup, the system loads /usr/lib/libssl.so.3—the OpenSSL library. Multiple programs can use the same library simultaneously. libssl exists only once in memory, and nginx and curl share the same instance.
Static linking copies library code into the binary. The resulting program is larger but requires no external dependencies. A statically linked nginx includes the SSL code and runs without a libssl installation. Distributions generally prefer dynamic linking—smaller binaries and shared updates.
Library versions are encoded in file names. libssl.so.3 is OpenSSL version 3.x. libssl.so.1.1 was version 1.1.x. Programs link against specific versions—a version of nginx compiled for libssl.so.3 will not consider libssl.so.1.1 compatible. The package manager guarantees that if nginx requires libssl.so.3, the libssl3 package is installed.
Dependency Graphs and Transitive Closure
Dependencies form directed graphs. Package A depends on B, while B depends on C and D. The system calculates the transitive closure: installing A also requires installing B, C, and D. The installation order matters—first C and D, then B, and finally A.
A complex GNOME desktop has thousands of dependencies. The gnome-shell package requires graphical libraries, those require drivers, and those require kernel modules. The dependency graph has multiple levels of depth. The package manager calculates the complete installation order automatically.
Circular dependencies are problematic. Package A requires B, while B requires A—which one is installed first? Modern package managers resolve certain cycles through split packages or pre-depends mechanisms. Debian forbids true dependency cycles and permits them only for recommendations.
The calculations become complex on large systems. A desktop with 2,000 packages has tens of thousands of dependency relationships. The package manager caches previously resolved graphs. Only the affected portions are recalculated during updates. Updating nginx triggers recalculation for nginx and its direct dependencies, not for the entire system.
Version Constraints and Compatibility
Dependencies include version requirements. nginx requires libssl3 (>= 3.0.0)—version 3.0.0 or newer. libc6 (>= 2.34) means “glibc version 2.34 or later.” These constraints guarantee API compatibility—nginx uses functions that exist only starting with libssl 3.0.
Upper version limits are less common. libfoo (<< 2.0) means “libfoo older than 2.0.” A package compiled against libfoo 1.x is incompatible with 2.x. The library has broken its ABI (Application Binary Interface)—function signatures changed and structures were reorganized.
Semantic Versioning helps. Major version increments (1.x → 2.x) break compatibility, while minor updates (2.1 → 2.2) remain compatible. Libraries ideally follow this principle. Reality is more complicated—some projects break their ABI in minor releases, while others guarantee compatibility across major versions.
The package manager checks all constraints. Installing package A with libfoo (>= 2.0) fails if only libfoo 1.9 is installed. The system offers to upgrade libfoo to version 2.0 and then installs A. Alternatively, it reports a conflict: “libfoo 2.0 would break package B, which requires libfoo (« 2.0).”
Conflicts and Alternatives
Conflicts declare incompatibility. A package defines Conflicts: apache2—it cannot coexist with apache2. Both web servers want to bind to port 80, and only one can succeed. The package manager prevents both from being installed simultaneously.
Replacement allows substitution. A package declares Provides: mail-transport-agent—it fulfills the role of an MTA. Other packages can declare Depends: mail-transport-agent. Postfix, Exim, and Sendmail all provide mail-transport-agent. The user selects one, and the dependencies are satisfied.
Alternatives systems manage symbolic links. Multiple programs can provide /usr/bin/editor—vim, nano, or emacs. The alternatives system maintains priorities, and the administrator selects which one to use. Packages depend on /usr/bin/editor, regardless of which editor is actually installed.
Virtual packages are purely a Provides mechanism. libc-dev is a virtual package—libc6-dev and libc-dev-bin provide it. Dependencies on libc-dev are satisfied by any provider. This abstracts away the concrete implementation.
Update Strategies: Rolling vs. Stable
Rolling release updates continuously. New software versions appear daily, and updates flow directly to users. Arch delivers 50–200 package updates every day. Dependencies change—a libssl update from 3.0 to 3.1 triggers rebuilds of all dependent packages.
Dependency resolution is dynamic. A single update can affect hundreds of packages—a new version of libfoo requires a new libbar, which in turn requires an updated libbaz. The package manager calculates the upgrade path: “These 247 packages will be upgraded.” The user confirms, and the system performs the update.
Stable releases freeze dependencies. Debian Bookworm ships with libssl 3.0.11, and that version remains throughout the entire Bookworm lifecycle (2–3 years). Only security patches are backported—3.0.11 → 3.0.12, never 3.0 → 3.1. Dependency graphs remain stable, and there are no surprises.
This prevents dependency hell. Older software continues to run, and dependencies do not break. The trade-off is older versions and missing features. A developer may require new library features that are unavailable in Stable. Backports or compiling from source are common workarounds.
Partial Upgrades and Mixed Sources
Mixed repositories (Stable + Testing) are risky. Debian Stable with individual Testing packages can cause dependency chaos. A Testing package requires a newer libfoo, which pulls in additional Testing dependencies, and suddenly half the system is running Testing.
APT pinning (Debian) or package masks (Gentoo) control this behavior. The administrator defines: “nginx from Testing, everything else from Stable.” The package manager respects these preferences and resolves dependencies correctly. PostgreSQL 16 from Testing automatically installs the newer libpq from Testing, while the base system remains Stable.
Backports are a safer alternative. Debian Backports compiles Testing software against Stable libraries. An nginx backport runs with the Stable version of libssl and introduces no Testing dependencies. The risk is lower, but the software is also less current—some features are unavailable without the newest libraries.
Flatpak and Snap avoid system dependencies entirely. Each application ships with its own library versions, isolated in a container. This eliminates dependency hell at the cost of additional disk space and memory—every application includes its own copy of libssl. System integration is weaker—there are no shared updates, and duplicate libraries occupy memory.
Dependency Hell and Resolution Strategies
Dependency hell occurs when requirements are incompatible. Package A requires libfoo >= 2.0, while package B requires libfoo <= 1.9. Installing both simultaneously is impossible. The package manager reports the conflict and refuses the installation.
The solutions vary:
Parallel library installation: libfoo1 and libfoo2 coexist. Package A links against libfoo2.so, while B links against libfoo1.so. Both versions are installed, and there are no conflicts. The additional disk space is acceptable because libraries are small.
ABI bumps in package names: libssl1.1 and libssl3 are separate packages. During a system upgrade, both versions remain installed for a transition period. Older software continues using libssl1.1, while newer software uses libssl3. After all software has migrated, libssl1.1 is removed.
Rebuilding all dependent packages: The Gentoo approach. Updating libfoo triggers rebuilds of every package that depends on libfoo. After the rebuild wave finishes, the system is consistent—everything links against the new version of libfoo. Time-consuming, but clean.
Containers and isolation: The Docker approach. Each container has its own library versions and no system dependencies. Service A runs with libfoo 1.9 in one container, while Service B runs with libfoo 2.0 in another. The problem is solved, but resource consumption is higher.
Automatic vs. Manual Resolution
The package manager calculates dependencies automatically. The user says “install A,” and the system calculates the dependencies, installing package A together with 20 required packages. No manual dependency management is necessary. This works in 99% of cases.
Rare conflicts require manual intervention. The system cannot find an automatic solution because multiple packages require incompatible versions. The administrator decides which package is more important. Can one package be removed? Is there an alternative provider?
Solver failure messages are often cryptic. “The following packages have unmet dependencies: A depends on B (>= 2.0) but 1.9 is to be installed.” The real cause lies deeper—why is version 1.9 being installed? Because package C requires package B in version <= 1.9. The administrator must understand the complete dependency graph.
Dry-run modes help: “What would happen if package A were installed?” The package manager performs a simulation and displays the effects. “These 47 packages would be upgraded, these 5 removed.” The administrator can see the consequences before making any changes and either proceed or look for alternatives.
Recommended and Optional Dependencies
Hard dependencies are mandatory. Package A cannot function without package B, so installation without B is impossible. The package manager installs B automatically without prompting.
Recommended dependencies are optional but recommended. Package A works without B but with reduced functionality. nginx runs without the GeoIP library but has no geolocation support. Debian installs recommended packages by default while allowing this behavior to be disabled.
Suggested dependencies are purely informational. “nginx suggests monitoring-tool”—nginx functions completely without it, but the monitoring tool may be useful. The package manager ignores suggestions during installation and only displays them as information.
Build dependencies are temporary. Compiling package A requires packages B, C, and D (compiler, header files, and related tools). After the build completes, these packages can be removed because the resulting binary runs without the build tools. Source-based systems explicitly distinguish between build-time and runtime dependencies.
Orphaned Packages and Autoremove
Orphaned packages were installed as dependencies and are no longer needed. nginx has been removed, and libssl is now orphaned—no other package requires it. The package manager marks such packages and offers to remove them.
Autoremove mechanisms clean up automatically. After removing nginx, the package manager displays: “The following packages are no longer required and can be removed: libssl3, libpcre2-8-0.” A single command removes all orphaned packages and cleans up the system.
Some packages are installed explicitly, while others are installed automatically as dependencies. Running apt install libssl3 marks libssl3 as explicitly requested, so Autoremove will not remove it. Automatic installation as an nginx dependency marks it as a candidate for Autoremove after nginx has been removed.
This reduces disk space usage and complexity. Old dependencies disappear automatically, and the system remains clean. The disadvantage is the accidental removal of libraries that are still required by manually compiled software or external programs.
Update Order and Failure Points
System updates have a critical order. Base libraries are updated first, followed by the packages that depend on them. A libc update takes place before all other packages because libc is the most fundamental dependency. After libc is updated, core utilities are upgraded, followed by userland software.
An interrupted update can break the system. libc has been upgraded, but not all dependent programs. Those programs still link against the old libc version, while the ABI has changed, causing them to crash. Package managers use transactions—either everything succeeds, or nothing does.
Post-installation scripts configure services. After nginx is installed, postinst runs—it registers the systemd service and starts nginx. These scripts can fail because of configuration errors or because the port is already in use. The package manager logs the error, but the package remains “installed but not configured.”
Rollback is difficult. A failed update leaves the system partially upgraded. Downgrading individual packages is possible but risky because old and new versions may have mismatched dependencies. Filesystem snapshots or virtual machine backups provide a more reliable rollback mechanism.
Summary
Dependency resolution automates library management. Programs declare their dependencies, package managers calculate the installation order, and compatibility is guaranteed. Version constraints, conflicts, and alternatives make complex scenarios possible.
Rolling release distributions update dynamically with constantly changing dependencies. Stable releases freeze dependency graphs to maximize stability. Both approaches are valid—they pursue different goals and make different trade-offs.
The concepts of package management apply across distributions. APT, pacman, and pkg_add all resolve dependencies using similar algorithms. The next articles cover distribution-specific implementations for APT, pacman, and OpenBSD packages.
Software and Versions Used
This discussion applies to:
- Dependency concepts: Universal across all Unix systems
- Examples: Debian, Arch Linux, Gentoo, OpenBSD
- Context: Cross-distribution fundamentals
- Version: October 2025
Next steps:
- Repository Systems →
- Binary Packages vs. Source Code →
- Debian Fundamentals (planned)