Chapter 09Lesson 04~60 minutes

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.

Source buildsAlternativesOperational safety

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.

Prefer packages

Integrated lifecycle

Distribution or trusted internal packages provide versioned ownership, upgrades, removal, signatures, and fleet consistency.

Use source deliberately

Document the exception

Record upstream source, commit or release, checksums, build flags, dependencies, destination prefix, owner, and review date.

Package when repeated

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

Controlled source-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.

LocationTypical ownerOperational rule
/usrDistribution package managerAvoid unmanaged writes
/usr/localLocal administratorTrack manifests and upgrades
/opt/nameSelf-contained applicationVersion directories and atomic symlink promotion work well
~/.localIndividual userKeep PATH and support scope explicit

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
Alternatives are not arbitrary version managers

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.

Next lesson

Repository Trust, Updates, Pinning, and Patch Workflows

The final lesson connects repository authentication and package policy to governed patch execution, rollback planning, and fleet evidence.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.