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 index
apt install nginx # Install a package
apt upgrade # Upgrade all packages
apt search keyword # Search available packages
apt show package-name # Package details
apt autoremove # Remove orphaned dependencies
The 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 nginx
dnf update
dnf search keyword
rpm -qa | grep nginx # List installed RPM packages
Fedora 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)"
# Then
brew install node # Latest Node.js
brew install python@3.12 # Specific Python version
brew install gh # GitHub CLI
brew install ripgrep # Modern grep replacement
Homebrew’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 repository
flatpak remote-add --if-not-exists flathub \
https://flathub.org/repo/flathub.flatpakrepo
# Install applications
flatpak install flathub org.videolan.VLC
flatpak install flathub com.spotify.Client
flatpak install flathub com.bitwarden.desktop
# Run
flatpak run org.videolan.VLC
# Update all
flatpak update
Flathub 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 -- install
The 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 tools
Or with newer flakes syntax:
nix develop # Uses flake.nix in current directory
The 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:
# home.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 tools
brew → 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.