Installing from Source and Managing Alternatives
Build and install software from source without corrupting distribution state, stage files safely, record provenance, uninstall predictably, and manage competing command implementations.
Learning objectives
By the end of this lesson
- Explain when a source installation is justified and why distribution packages are preferable by default.
- Use an isolated build directory, explicit prefix, non-root compilation, and staged installation workflow.
- Inspect build instructions and generated install manifests before writing into system paths.
- Design an uninstall and upgrade strategy for software outside the package database.
- Use an alternatives system to select among compatible implementations without replacing files manually.
1. Source installation is an exception with ownership cost
Building from source can be necessary for a missing feature, urgent upstream fix, development snapshot, unsupported architecture, or internal component. It also bypasses part of the distribution's integration work: dependency policy, security advisories, file ownership, upgrade ordering, reproducible builds, service conventions, and removal tracking.
Integrated lifecycle
Distribution or trusted internal packages provide versioned ownership, upgrades, removal, signatures, and fleet consistency.
Document the exception
Record upstream source, commit or release, checksums, build flags, dependencies, destination prefix, owner, and review date.
Convert craft into supply chain
If multiple hosts need the build, create a proper package or container image instead of repeating manual installation.
The goal is not merely to make compilation succeed. The goal is to produce a reversible, auditable artifact that coexists with package-managed software.
2. Use an out-of-tree, least-privilege build workflow
flowchart TD S["Pinned source and checksum"] --> D["Install build dependencies"] D --> B["Unprivileged out-of-tree build"] B --> T["Tests and static checks"] T --> G["Stage into DESTDIR"] G --> I["Inspect manifest and paths"] I --> P["Package or install to explicit prefix"] P --> R["Record provenance and rollback"]
Compile as an ordinary user. Use a separate build directory so
generated files do not contaminate the source tree. Select an
explicit prefix such as /usr/local or an
application-specific directory under /opt. Stage
installation into a temporary root with DESTDIR when
the build system supports it, then inspect the exact file set before
privileged installation or packaging.
project="$HOME/src/example-tool"
build="$HOME/build/example-tool"
stage="$HOME/stage/example-tool"
rm -rf "$build" "$stage"
mkdir -p "$build" "$stage"
# Autotools-style example; read the project's instructions first.
cd "$build"
"$project/configure" --prefix=/usr/local
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')"
make check
make DESTDIR="$stage" install
# Inspect staged paths before any system write.
find "$stage" -printf '%M %u:%g %p\n' | sort | less
find "$stage" -type f -exec sha256sum {} + > "$stage.sha256"
For CMake, Meson, Go, Rust, Python, and other ecosystems, equivalent concepts apply: isolated build state, pinned inputs, tests, staged outputs, explicit destination, and a manifest.
3. Separate build dependencies from runtime dependencies
Headers, compilers, linkers, generators, test frameworks, and static-analysis tools may be required only to build. Runtime libraries and data files remain necessary after installation. Record both groups so builders can be reproduced and production hosts remain minimal.
# Detect common build systems and read their declared files.
for file in configure.ac CMakeLists.txt meson.build Makefile Cargo.toml go.mod pyproject.toml; do
[ -f "$project/$file" ] && printf '%s\n' "$file"
done
# Inspect dynamic dependencies of one staged ELF executable.
exe=$(find "$stage" -type f -perm -111 -print -quit)
if [ -n "$exe" ] && command -v readelf >/dev/null 2>&1; then
readelf -d "$exe" | grep NEEDED || true
fi
# Ask the package system which installed packages own required build tools.
for cmd in cc make pkg-config; do
path=$(command -v "$cmd" 2>/dev/null || true)
[ -n "$path" ] || continue
if command -v dpkg-query >/dev/null 2>&1; then
dpkg-query -S "$path" 2>/dev/null || true
elif command -v rpm >/dev/null 2>&1; then
rpm -qf "$path" 2>/dev/null || true
fi
done
A successful build on one workstation does not prove that dependencies are documented. Rebuild in a clean environment and make undeclared dependencies fail early.
4. Keep package-managed and local namespaces distinct
Distribution packages generally own /usr. Locally
administered software conventionally belongs under
/usr/local; self-contained vendor or internal
applications may use /opt/name. User-scoped tools may
live under $HOME/.local. Do not overwrite a packaged
binary in /usr/bin with a manual copy.
For fleet deployment, package the staged tree as a
.deb or RPM, publish it through a controlled
repository, or build it into an image. This restores ownership and
repeatability.
5. Alternatives choose compatible defaults
An alternatives framework manages groups of symbolic links so
several compatible implementations can coexist. Debian-family
systems commonly provide update-alternatives;
RPM-family distributions may provide an
alternatives command. The system can choose the
highest-priority automatic candidate or a manually selected
implementation.
if command -v update-alternatives >/dev/null 2>&1; then
update-alternatives --get-selections | head -30
update-alternatives --display editor 2>/dev/null || true
# Interactive selection when an administrator intentionally changes it:
# sudo update-alternatives --config editor
elif command -v alternatives >/dev/null 2>&1; then
alternatives --display editor 2>/dev/null || true
# sudo alternatives --config editor
fi
# Inspect the current default without changing it.
command -v editor 2>/dev/null || true
readlink -f "$(command -v editor 2>/dev/null)" 2>/dev/null || true
Only register implementations that are interface-compatible for the managed link group. Language runtimes often need dedicated version-management or packaging strategies because libraries, modules, and ABI expectations extend beyond one executable link.
6. Hands-on lab: stage a tiny source installation
This lab compiles a small C program into a staged
/usr/local tree without writing to system directories.
It records source, compiler, checksum, and manifest evidence.
lab="$HOME/devops-academy/linux/chapter09/lesson04"
src="$lab/src"
stage="$lab/stage"
rm -rf "$lab"
mkdir -p "$src" "$stage/usr/local/bin"
cat > "$src/academy-version.c" <<'EOF'
#include <stdio.h>
int main(void) {
puts("academy-version 1.0.0");
return 0;
}
EOF
cc -Wall -Wextra -Werror -O2 \
"$src/academy-version.c" \
-o "$stage/usr/local/bin/academy-version"
"$stage/usr/local/bin/academy-version"
{
printf 'built_at=%s\n' "$(date --iso-8601=seconds)"
printf 'compiler=%s\n' "$(cc --version | head -1)"
printf 'source_sha256='; sha256sum "$src/academy-version.c" | awk '{print $1}'
printf 'binary_sha256='; sha256sum "$stage/usr/local/bin/academy-version" | awk '{print $1}'
printf '\n=== manifest ===\n'
find "$stage" -printf '%M %u:%g %s %p\n' | sort
printf '\n=== dynamic dependencies ===\n'
ldd "$stage/usr/local/bin/academy-version" 2>/dev/null || true
} > "$lab/build-record.txt"
less "$lab/build-record.txt"
Verification checklist
7. Common mistakes
Running the entire build with sudo
This creates root-owned build artifacts and gives untrusted build logic unnecessary privileges.
Installing without a manifest
Removal and upgrade become guesswork, leaving stale binaries and libraries.
Using make uninstall as the only rollback plan
The source tree may disappear or the uninstall target may be incomplete. Preserve staged manifests or create a package.
Overwriting packaged files
Future package upgrades can replace the manual copy, while verification and ownership become misleading.
8. Knowledge check
Question 1. Why stage with DESTDIR?
Question 2. Why compile as an unprivileged user?
Question 3. What problem do alternatives solve?
9. Summary
Source installations must be deliberate exceptions. Pin and verify inputs, build without privilege, isolate generated state, run tests, stage outputs, inspect manifests, use explicit local prefixes, and create a package when deployment repeats. Alternatives can select among compatible implementations, but they do not replace full runtime or dependency management.
10. Further reading
- GNU Make and Autoconf installation documentation.
-
Filesystem Hierarchy Standard guidance for
/usr,/usr/local, and/opt. -
update-alternatives(1)or the installed distribution's alternatives manual.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.