The Package Manager Proliferation Is Completely Out of Hand, and Somehow All of Them Are Correct
The situation in 2026: you have a fresh Linux machine. You want to install a development tool. Your options include apt install, brew install, nix-env -iA, flatpak install, snap install, pipx install, compiling from source, and approximately six language-specific managers (npm, pip, cargo, go install) that technically count as package managers.
This is either a rich ecosystem with mature solutions for different problems, or a fragmented nightmare with no standards. Both are true simultaneously.
The good news: once you understand what each category of tool is optimized for, the decision at any given moment becomes fairly clear. The bad news: you’ll probably end up using three of them.
apt and dpkg: The Workhorse You Take for Granted
apt (Advanced Package Tool) manages Debian and Ubuntu packages. It’s the default, the thing that’s always there, and the thing you use for anything that belongs to the operating system itself.
apt update # Refresh package indexapt install nginx # Install a packageapt upgrade # Upgrade all packagesapt search keyword # Search available packagesapt show package-name # Package detailsapt autoremove # Remove orphaned dependenciesThe underlying tool is dpkg — apt is the friendlier frontend that handles dependency resolution and repository management. dpkg -l lists installed packages; dpkg --listfiles package shows what files a package owns.
What apt is good at:
- System software (web servers, databases, system tools)
- Anything that needs deep OS integration
- Software that needs to be available to all users
- Stable, predictable versions (often older, but tested)
Where apt falls over:
- You need a newer version than what’s in the repos
- You need multiple versions of the same package
- You need a package not in the repos at all
- You’re a developer who doesn’t want to pollute system packages with dev tools
The Debian/Ubuntu repos prioritize stability over currency. If you’re on Ubuntu LTS and want a recent version of Node.js, you’re getting Node 18 from the default repos when Node 22 has been out for years. This is a feature for servers (stability), a frustration for developers (old).
dnf and rpm: The Other Half
Red Hat / Fedora / CentOS / Rocky use dnf (formerly yum) as the package manager on top of rpm. Same concept as apt/dpkg, different format. Commands are parallel:
dnf install nginxdnf updatednf search keywordrpm -qa | grep nginx # List installed RPM packagesFedora ships newer packages faster than Debian stable, making it more developer-friendly at the cost of some stability. RHEL (and its clones) lean even more conservative.
The Dependency Hell Problem
Here’s the fundamental issue that spawned all the alternatives.
System package managers manage one version of each library. Package A needs libssl 1.1. Package B needs libssl 3.0. You cannot install both A and B without the package manager doing something creative, and often “something creative” means “one of them doesn’t work.”
Real scenario: you’re running a Python application that requires Python 3.9, and a new tool you want requires Python 3.12. The system has Python 3.9. You upgrade. Your application breaks because it needed a deprecated API that was removed.
The classic solution was virtual environments (Python venv, Node nvm, Ruby rbenv, etc.). Create isolated environments per project, install there, don’t touch system packages. This works and is widely used. It doesn’t solve the case where you want a system-wide tool in a newer version than the distro provides.
Homebrew on Linux: User-Space Package Management
Homebrew was designed for macOS but runs perfectly well on Linux. Its key property: it installs entirely in user space (default: /home/linuxbrew/.linuxbrew) without root access. No sudo. No touching system files. No interfering with apt.
# Install Homebrew/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Thenbrew install node # Latest Node.jsbrew install python@3.12 # Specific Python versionbrew install gh # GitHub CLIbrew install ripgrep # Modern grep replacementHomebrew’s formula repository (Homebrew Core) tends to have recent software versions. It’s particularly good for developer CLI tools that aren’t available in system repos or are significantly outdated there.
Best use cases:
- Developer tools (language runtimes, CLI utilities)
- Software you want recent versions of without adding PPAs/external repos
- Tools that don’t need root or system integration
- macOS/Linux consistency (same commands on both platforms)
Limitations:
- Another package store to keep updated (
brew update && brew upgrade) - Some packages require dependencies that can conflict with system packages in subtle ways
- Not designed for GUI applications
Flatpak: Sandboxed Applications
Flatpak installs GUI applications in sandboxed containers with their own dependency trees. The application brings its own libraries. No dependency conflicts. The same Flatpak package works on any Linux distribution.
# Install Flatpak (if not present)apt install flatpak
# Add Flathub repositoryflatpak remote-add --if-not-exists flathub \ https://flathub.org/repo/flathub.flatpakrepo
# Install applicationsflatpak install flathub org.videolan.VLCflatpak install flathub com.spotify.Clientflatpak install flathub com.bitwarden.desktop
# Runflatpak run org.videolan.VLC
# Update allflatpak updateFlathub has become the de facto Linux app store for GUI applications. Coverage has improved dramatically — most major applications are there.
Best use cases:
- GUI applications
- Proprietary software (Spotify, Discord, etc.) where sandboxing is valuable
- Applications that need different library versions than your system provides
- Distribution-agnostic application deployment
Limitations:
- Larger disk usage (bundled dependencies)
- Slightly more startup overhead
- Sandbox restrictions can be awkward for apps that need deep system access
The Snap Situation
Canonical’s Snap is a direct competitor to Flatpak. It’s also sandboxed, also distribution-independent, also bundles dependencies. The community reception has been… mixed. Snap installs are slower than Flatpak, the snap store is proprietary and controlled by Canonical, and the forced daily updates have frustrated users.
Ubuntu installs Firefox as a Snap by default, which was controversial enough to spawn guides on installing Firefox from apt instead.
Flatpak has won the community preference battle for most users who aren’t specifically on Ubuntu or committed to the Snap ecosystem.
Nix: Reproducible Everything
Nix is the package manager that makes everything before it look like toy tools. It’s also the one that requires the most explanation, has the steepest learning curve, and will consume significant time if you decide to go deep.
What Makes Nix Different
Nix is a purely functional package manager. Packages are defined as pure functions: same inputs always produce the same output. Every package version is stored in /nix/store/ with a hash in the path:
/nix/store/abc123xyz-nodejs-20.11.0//nix/store/def456uvw-nodejs-18.19.0/Both versions coexist. A project that needs Node 18 uses its hash path. A different project uses Node 20. No conflicts. No virtual environments needed.
Installing Nix (on any Linux distro)
curl --proto '=https' --tlsv1.2 -sSf -L \ https://install.determinate.systems/nix | sh -s -- installThe Determinate Systems installer is the current recommended approach (better than the official installer for most use cases).
nix-shell: Per-Project Environments
The immediate practical benefit. Drop a shell.nix in a project directory:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell { buildInputs = [ pkgs.nodejs_20 pkgs.python311 pkgs.postgresql_16 pkgs.redis ];
shellHook = '' echo "Dev environment loaded" export DATABASE_URL="postgresql://localhost/myapp_dev" '';}Then:
nix-shell # Drops you into a shell with exactly these toolsOr with newer flakes syntax:
nix develop # Uses flake.nix in current directoryThe environment is isolated, reproducible, and doesn’t touch your system packages. Your colleague runs nix develop on their machine and gets the exact same versions. Works the same in CI.
home-manager: Managing Dotfiles and User Packages
home-manager extends Nix to manage your user environment — packages, dotfiles, shell configuration, all declared in Nix:
{ pkgs, ... }:
{ home.packages = [ pkgs.ripgrep pkgs.fd pkgs.bat pkgs.jq pkgs.gh ];
programs.git = { enable = true; userName = "Your Name"; userEmail = "you@example.com"; extraConfig.init.defaultBranch = "main"; };
programs.zsh = { enable = true; autosuggestions.enable = true; syntaxHighlighting.enable = true; };}home-manager switch applies the configuration. This is your dotfiles, package list, and tool configuration all in one declarative file, version-controlled, reproducible on any machine with Nix installed.
NixOS: The Full Rabbit Hole
NixOS takes the Nix principle to the operating system level. The entire OS is configured in /etc/nixos/configuration.nix. Services, packages, users, system settings — all declarative:
# /etc/nixos/configuration.nix (excerpt)services.nginx.enable = true;services.postgresql.enable = true;services.postgresql.package = pkgs.postgresql_16;
users.users.alice = { isNormalUser = true; extraGroups = [ "wheel" "docker" ];};
environment.systemPackages = with pkgs; [ vim git curl wget];nixos-rebuild switch applies changes. nixos-rebuild switch --rollback goes back. The OS is a pure function of this configuration file.
The appeal: your entire system is a git repo. Reproducible across machines. No “works on my machine” — your machine IS the configuration.
The caveat: the learning curve is real. NixOS doesn’t work like other Linux distributions. Standard Linux tutorials often don’t apply directly. The Nix language is unusual. The documentation is improving but can still be sparse in places.
Practical Developer Workflow: Using All of Them
The realistic answer for a developer in 2026:
apt → system software, server daemons, OS toolsbrew → developer CLI tools (gh, jq, ripgrep, bat, etc.)Flatpak → GUI applications (VS Code from apt OR Flatpak, Spotify, Discord)nix-shell → per-project environments (exact versions, reproducible)nix/home-mgr → if you decide to go deep on Nix (dotfiles, full env management)Not every project needs nix-shell, but when you work on multiple projects with different language version requirements, it pays for itself quickly.
Comparison Table
| Tool | Scope | Root needed | Reproducible | GUI apps | System integration |
|---|---|---|---|---|---|
| apt/dnf | System | Yes | Mostly | Some | Deep |
| Homebrew | User-space | No | Moderate | CLI only | Light |
| Flatpak | Applications | No | Yes | Yes | Sandboxed |
| Snap | Applications | Partial | Partial | Yes | Moderate |
| Nix | Everything | No (user mode) | Yes | Via nixpkgs | Configurable |
| NixOS | OS-level | N/A (declarative) | Yes | Yes | Complete |
The Honest Recommendation
Start with apt for system stuff. Add Homebrew when you want developer tools without sudo or when you need newer versions. Use Flatpak for GUI apps, especially proprietary ones.
If you find yourself fighting dependency conflicts on a development machine, look at nix-shell for project environments. The shell.nix file approach integrates with existing workflows without committing to the full Nix stack.
If you enjoy configuration as code, want reproducibility as a first-class feature, and don’t mind an investment in learning something that’s genuinely different — explore home-manager or NixOS.
The friends we made along the way were honestly pretty useful. Even the weird functional one that speaks a language nobody else does.