Installing from Source
Most of the time, apt install puts software on your system in seconds. But sometimes the version in your distribution’s repositories is old, the package isn’t packaged for your distro at all, or you need to compile it with a specific feature enabled. Installing from source means downloading a program’s actual source code and using a compiler toolchain to build a runnable binary yourself, rather than downloading something already built. It gives you the newest code and full control over how it’s built, at the cost of manually handling dependencies, updates, and cleanup.
Overview: What “Building from Source” Actually Means
When apt installs a package, it downloads a pre-compiled binary plus metadata, and dpkg records every file it placed on disk so it can be cleanly removed later. Building from source skips all of that: you get plain text source files (.c, .cpp, etc.), and it’s up to you to turn them into an executable using a compiler like gcc.
A compiler translates source code into object files — machine code for your CPU’s instruction set, one object file per source file. A linker then combines those object files with any required libraries into a single executable. For any project with more than a handful of source files, doing this by hand would mean typing dozens of gcc commands in the right order, so nearly every project ships a build system that automates it.
The most common one you’ll meet on Linux is the GNU Autotools family (autoconf and automake). Projects built with it ship a configure script that probes your system — does a C compiler exist, is a required header like ncurses.h present, which libraries are installed — and then generates a Makefile tailored to your machine. Running make reads that Makefile, which lists build targets, the files each target depends on, and the shell commands needed to produce it, then compiles only what’s changed since the last build. make install (almost always run with sudo, since it writes outside your home directory) copies the finished binary, libraries, and man pages into system directories.
Source Install vs. Package Manager Install
Package manager (apt) |
Building from source |
|---|---|
| Pre-built binary, installs in seconds | Compiled locally; can take minutes |
| Version fixed by the distro’s repos | Any version, including unreleased branches |
Tracked by dpkg, clean apt remove |
Not tracked; manual or make uninstall |
Security patches via apt upgrade |
You are responsible for rebuilding on updates |
Why /usr/local Instead of /usr
On Debian and Ubuntu, apt and dpkg consider /usr their territory — every file under it is supposed to be tracked in the package database. The Filesystem Hierarchy Standard reserves /usr/local for software installed outside the package manager precisely so the two never collide. Nearly every source build defaults its install prefix to /usr/local, which is why a source-installed binary usually ends up at /usr/local/bin rather than /usr/bin. Installing straight into /usr risks silently overwriting a file apt thinks it owns, with no record of what you changed.
When to Build from Source (and When Not To)
Build from source when you need a version newer than what your distro ships, the software isn’t packaged for Debian/Ubuntu or RHEL/Fedora at all, you need to compile in an optional feature that the distro’s build disabled, or you’re debugging the software itself. Stick with apt (Debian/Ubuntu) or dnf/yum (RHEL/Fedora) for everything else — you get automatic security updates, dependency resolution, and clean removal for free.
Syntax: The Standard Build Workflow
Autotools-based projects follow the same three-command pattern. It isn’t a single command with flags — it’s a sequence, shown here as a generic pattern:
./configure [--prefix=<install-path>] [--enable-<feature>] [--disable-<feature>]
make
sudo make install
| Option | Meaning |
|---|---|
--prefix=DIR |
Install root; binaries go to DIR/bin, libs to DIR/lib, etc. Defaults to /usr/local. |
--enable-FEATURE |
Turn on an optional feature the project supports building with. |
--disable-FEATURE |
Turn off a feature that would otherwise build by default. |
--with-PACKAGE |
Build using an optional external library or tool if present. |
--without-PACKAGE |
Explicitly skip an optional dependency even if it’s installed. |
--help |
List every flag a specific project’s configure script actually supports. |
Not every project uses configure at all. Some, like Redis, ship a hand-written Makefile with no configure step. Others use CMake (cmake .. && make) or a language-specific build tool. Always check the project’s README or INSTALL file first instead of assuming the autotools pattern applies.
Examples
Example 1: Building htop from a Release Tarball
htop is a common target for a source build when a distro’s repo version lags behind upstream. Install the build toolchain and htop’s specific dependencies first, then download, verify, and build:
sudo apt update
sudo apt install -y build-essential autoconf automake pkg-config libncursesw5-dev wget
cd /tmp
wget https://github.com/htop-dev/htop/releases/download/3.3.0/htop-3.3.0.tar.xz
sha256sum htop-3.3.0.tar.xz
tar -xf htop-3.3.0.tar.xz
cd htop-3.3.0
./configure
make -j"$(nproc)"
sudo make install
Output:
checking for gcc... gcc
checking whether the C compiler works... yes
...
config.status: creating Makefile
CC htop.o
CC CommandLine.o
CC linux/LinuxProcess.o
...
CCLD htop
/usr/bin/install -c htop '/usr/local/bin/htop'
/usr/bin/install -c htop.1 '/usr/local/share/man/man1/htop.1'
On Debian/Ubuntu, libncursesw5-dev provides the ncurses headers htop’s configure looks for; on Fedora/RHEL the equivalent is sudo dnf install ncurses-devel after sudo dnf groupinstall "Development Tools". The sha256sum output should be compared against the checksum published on htop’s release page before you trust the tarball. Confirm the new binary is what you expect:
which htop
htop --version
Output:
/usr/local/bin/htop
htop 3.3.0
Example 2: Redis — a Project with No configure Step
Not every source build uses autotools. Redis ships a plain Makefile, so the workflow skips ./configure entirely:
cd /tmp
wget https://download.redis.io/redis-stable.tar.gz
tar -xzvf redis-stable.tar.gz
cd redis-stable
make -j"$(nproc)"
sudo make install
redis-server --version
Output:
CC Makefile.dep
CC adlist.o
CC quicklist.o
...
LINK redis-server
Hint: It's a good idea to run 'make test' ;)
redis-server v=7.4.0 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64
Redis’s Makefile installs straight to /usr/local/bin by default. Because there’s no configure probing your system, dependency problems here would only surface as compile errors from make itself — which is why reading a project’s README before building matters more than memorizing one universal command sequence.
Example 3: A Reusable Build Script
Wrapping the workflow in a script pays off if you rebuild the same tool often, and it’s a good demonstration of the quoting and error-handling habits this course’s Bash lessons cover:
#!/usr/bin/env bash
set -euo pipefail
readonly PKG_NAME="jq"
readonly PKG_VERSION="1.7.1"
readonly TARBALL="jq-${PKG_VERSION}.tar.gz"
readonly URL="https://github.com/jqlang/jq/releases/download/jq-${PKG_VERSION}/${TARBALL}"
readonly BUILD_DIR="/tmp/${PKG_NAME}-build"
echo "Downloading ${PKG_NAME} ${PKG_VERSION}..."
mkdir -p "$BUILD_DIR"
wget -O "${BUILD_DIR}/${TARBALL}" "$URL"
cd "$BUILD_DIR"
tar -xzf "$TARBALL"
cd "${PKG_NAME}-${PKG_VERSION}"
echo "Configuring and building..."
./configure --prefix=/usr/local
make -j"$(nproc)"
echo "Installing (requires sudo)..."
sudo make install
echo "Done. Installed version:"
"$PKG_NAME" --version
Output:
Downloading jq 1.7.1...
Configuring and building...
Installing (requires sudo)...
Done. Installed version:
jq-1.7.1
Every variable expansion is quoted ("$BUILD_DIR", "$TARBALL"), and set -euo pipefail makes the script stop immediately if wget fails or a variable is unset, instead of plowing ahead and trying to compile a tarball that was never downloaded.
How It Works Step by Step
Walking through what the htop build actually did:
- 1.
./configureis itself a generated shell script. It runs a series of small test compiles to answer questions like “does a C compiler exist,” “isncurses.havailable,” and “does this system support the syscalls htop needs.” Based on the answers, it writes aMakefilefrom aMakefile.intemplate, substituting in your compiler path, install prefix, and enabled features. - 2.
makereads that Makefile, which lists targets, the files each depends on, and the exact shell commands to build them. For every.cfile it invokesgcc -cto produce a.oobject file — but only if the source file or a header it includes is newer than the existing object file, which is why editing one file and rerunningmakerecompiles almost nothing. - 3. The linker (invoked via
gcc) combines all the object files plus required libraries, such as-lncursesw, into the finalhtopexecutable. - 4.
sudo make installruns the Makefile’sinstalltarget, which copies the built binary into<prefix>/binand man pages into<prefix>/share/man. If the project also builds a shared library, this step (or a manualsudo ldconfig) refreshes the dynamic linker’s cache sold.socan find the new.sofile at runtime. - 5. None of this touched
dpkg‘s database. As far asaptis concerned, htop was never installed — which is the core trade-off of building from source.
Common Mistakes
Mistake 1: Running the Whole Build as Root
It’s tempting to prefix every command with sudo to avoid permission errors:
sudo ./configure && sudo make && sudo make install
Only the final install step needs elevated privileges, since it’s the only one writing outside your home or temp directory. Running configure and make as root leaves root-owned files scattered in your source directory, which can cause confusing permission errors the next time you try to build there as your normal user.
./configure && make -j"$(nproc)" && sudo make install
Mistake 2: Skipping the Build Toolchain
Running configure on a fresh system without a compiler installed fails immediately:
checking for gcc... no
checking for cc... no
configure: error: no acceptable C compiler found in $PATH
Install the toolchain first — build-essential pulls in gcc, g++, and make on Debian/Ubuntu (the Fedora/RHEL equivalent is sudo dnf groupinstall "Development Tools"):
sudo apt install -y build-essential
Mistake 3: Trusting a Download Without Verifying It
Downloading a tarball over HTTPS doesn’t guarantee the file itself wasn’t tampered with on the server or a mirror. Skipping checksum verification means you’re compiling and running code you never actually confirmed matches what the project published — always compare sha256sum output (or a GPG signature, if offered) against the value on the project’s official release page before extracting the tarball.
Mistake 4: Forgetting ldconfig After Installing a Shared Library
If a source build installs a new shared library, the program that needs it can fail to start even though the file is right there on disk:
redis-server: error while loading shared libraries: libssl.so.3: cannot open shared object file: No such file or directory
The dynamic linker caches known library locations; a newly installed .so file isn’t found until that cache is refreshed:
sudo ldconfig
Best Practices
- Check
apt/dnf(and backports or a trusted PPA) before building from source — it’s less work and gives you automatic updates. - Always read the project’s
READMEorINSTALLfile first; not every project follows the configure/make/install pattern. - Install the matching
-dev(Debian/Ubuntu) or-devel(Fedora/RHEL) header packages for any libraryconfigurecomplains about. - Verify the tarball’s checksum or GPG signature before extracting and building it.
- Leave the install prefix at its default,
/usr/local, so source installs never collide with filesaptmanages under/usr. - Use
checkinstallinstead of a baremake installwhen you want the result trackable and removable withdpkg -r:sudo apt install -y checkinstall sudo checkinstall --pkgname=jq-src --pkgversion=1.7.1 - Keep the extracted source directory (or record the exact version) so you can run
sudo make uninstalllater if the project’s Makefile supports it:cd /tmp/htop-3.3.0 sudo make uninstall - Use
make -j"$(nproc)"to compile using all available CPU cores instead of one file at a time. - For experimental or short-lived builds, install to a dedicated prefix like
/opt/myappso removal is a singlerm -rf.
Practice Exercises
- Pick a small CLI tool that’s outdated or missing in your distro’s repos and build it from source using the
./configure && make && sudo make installflow. Confirm the result withwhich <tool>and<tool> --version, and check it landed under/usr/local. - On a fresh system or container without
build-essentialinstalled, run a project’s./configureon purpose, read the exact error it prints, then install the missing toolchain and rerun it successfully. - Build the same tool a second time using
checkinstallinstead of a plainmake install, then remove it cleanly withsudo dpkg -r <package-name>and confirm withwhichthat it’s gone.
Summary
- Installing from source means compiling a program’s source code yourself instead of downloading a pre-built package.
- The classic autotools workflow is
./configure,make, thensudo make install— but not every project usesconfigure, so always check the README first. - Install
build-essential(or Fedora/RHEL’s “Development Tools” group) and any project-specific-dev/-develheaders before building. - Verify a downloaded tarball’s checksum before building it, and leave the install prefix at
/usr/localto avoid colliding with filesaptmanages. - A source install isn’t tracked by
dpkg, so usecheckinstallor keep the source directory around if you’ll need a clean uninstall later. - Because source installs bypass the package manager, security updates for that software become your responsibility.
