A reliable and widely used pattern when building software from source on Unix-like systems is:
Build as a normal user → Install as root (only if required).
The core idea is simple:
This avoids permission problems, toolchain path issues, and accidental pollution of your system with root-owned build artifacts.
Build tools (compilers, package managers, language toolchains) assume a normal user environment. Running builds as root can introduce several issues:
Common problems when building as root:
PATH (e.g., Cargo, Go, npm, pip)/root$HOME, $PATH, config files)Because of this, the long-standing Unix convention is:
Root installs software. Users build software.
Most classic Unix build systems follow this pattern:
./configure
make
sudo make install
Expected responsibilities:
| Step | Purpose |
|---|---|
configure | Detect system features and generate build configuration |
make | Compile everything |
make install | Copy finished artifacts into system locations |
If the build system follows this convention properly, make install should not compile anything.
Modern projects sometimes break this convention and trigger builds during make install. For example:
cargoWhen this happens, running sudo make install can fail because root does not have the same environment or toolchain paths.
In these cases, a better pattern is needed.
The staged install approach avoids running install steps as root.
Instead of installing directly to /usr, /etc, etc., files are installed into a temporary directory first.
Example:
make install DESTDIR="$PWD/pkgroot"
This produces a directory tree like:
pkgroot/
usr/local/bin/nginx
usr/local/nginx/modules/...
etc/nginx/...
At this stage:
Once staging is complete, the files are copied into the real filesystem:
sudo rsync -a pkgroot/ /
Optional safety check:
sudo rsync -a --dry-run pkgroot/ /
This shows what will be installed before actually copying anything.
rsync is Preferred Over cpWhile cp -a pkgroot/ / technically works, it is generally avoided because it is less transparent and harder to audit.
rsync provides:
--dry-run safety previewExample:
sudo rsync -a --info=NAME pkgroot/ /
A typical safe build process looks like this:
./configure
make -j$(nproc)
make install DESTDIR="$PWD/pkgroot"
sudo rsync -a --dry-run pkgroot/ /
sudo rsync -a pkgroot/ /
This ensures:
sudo make installIf make install only copies files and does not trigger any builds, the traditional approach remains perfectly fine:
./configure
make
sudo make install
Many mature projects (especially older Unix software) still follow this clean separation.
Think of the process like this:
developer user → builds software
root → places software into system directories
Root acts only as the authority that can write into protected locations such as:
/usr/usr/local/etc/optAll compilation and toolchains belong in user space.
Best practice when building software from source:
DESTDIR) if install steps rebuild thingsThis pattern prevents permission issues, toolchain path problems, and unstable build environments.
Following this model leads to cleaner builds and fewer surprises when rebuilding or upgrading software later.