Table of Contents
Why Linux Matters for Developers
Linux is the dominant environment for servers, containers, DevOps tooling, and cloud platforms. As a developer, that means:
- Most production workloads you deploy will run on Linux.
- Many tools you use locally (Git, compilers, containers, CI systems) are Linux-first.
- Understanding the Linux userland (shell, processes, files, permissions) lets you debug real issues that GUI tools often hide.
This chapter focuses on what you specifically need as a developer on Linux: how to build, debug, version, and package code in a Linux environment.
You should already be comfortable with basic command-line usage, package managers, and filesystem layout from earlier chapters.
Development Environment Basics on Linux
Choosing a Shell for Development
You’ll use the shell constantly for building, testing, and scripting.
Common shells:
bash— default on many distros; widely supported in scripts and CI.zsh— popular with developers, especially with frameworks like Oh My Zsh.fish— user-friendly with smart completions, but less used in system scripts.
For development work:
- Prefer
bashfor scripts you intend to share, ship, or run in CI. - You can still use
zshorfishinteractively; just ensure your scripts use#!/usr/bin/env bash.
Set your login shell (example for bash):
chsh -s /bin/bashOrganizing Project Directories
A common pattern:
mkdir -p ~/dev/{personal,work,opensource}
cd ~/dev/personalWithin a project, typical structure:
myproject/
src/ # source code
include/ # headers (for C/C++)
tests/ # tests
build/ # build artifacts (often added to .gitignore)
docs/
scripts/
Keeping build/ separate is especially important for C/C++ and similar compiled languages.
Useful Developer Tools to Install
Typical essentials (Debian/Ubuntu-style names; adjust per distro):
- Editors/IDEs:
vim,neovim,emacs,code(VS Code),kate. - Compilation:
build-essential(or equivalent),cmake,ninja-build,pkg-config. - Debugging:
gdb,valgrind,strace,ltrace. - VCS and tooling:
git,tig,diffstat. - Scripting:
python3,python3-pip,nodejs,npm.
Example (Debian/Ubuntu):
sudo apt install build-essential cmake ninja-build pkg-config \
git vim gdb valgrind straceWorking Effectively with the Linux Toolchain
This section focuses on the traditional Unix toolchain you’ll encounter in build systems and CI: compilers, linkers, and supporting utilities.
Key Build Concepts
At a high level, most compiled-language builds follow:
- Compile source files to object files:
- Example:
main.c→main.o - Link object files and libraries into an executable or library:
- Example:
main.o+libfoo.a→myapp - Install binaries, headers, config files into standard locations (often under
/usr/local/for local builds).
Compile + link is often combined into one command by tools like gcc, but the steps are conceptually separate.
The Compiler–Linker Pipeline in Practice
Example with C:
- Compile only (no linking):
gcc -c main.c -o main.o- Link one or more object files into an executable:
gcc main.o util.o -o myapp- Include paths and library paths:
-I/path/includeadds a header search path.-L/path/libadds a library search path.-lmyliblinks againstlibmylib.soorlibmylib.a.
Example:
gcc main.c -I/usr/local/include -L/usr/local/lib -lmylib -o myapp
On Linux, shared libraries typically end with .so (e.g. libm.so for the math library, which you link via -lm).
Environment Variables for Builds
Build tools rely heavily on environment variables:
CC— C compiler (e.g.gcc,clang).CXX— C++ compiler (e.g.g++,clang++).CFLAGS,CXXFLAGS— flags for C/C++ compilation (-O2,-g,-Wall, etc.).LDFLAGS— flags for the linker (e.g.-Lpaths,-Wl,options).PKG_CONFIG_PATH— wherepkg-configshould look for.pcfiles.
Example (temporary for a single command):
CC=clang CFLAGS="-O2 -g -Wall" makeThis is core to cross-compiling, customizing builds, and CI pipelines.
Using `make` and Simple Build Automation
The make tool automates builds based on file dependencies. Full build-system coverage happens elsewhere; here we focus on what’s uniquely useful in day-to-day Linux dev work.
Basic `Makefile` Ideas for Developers
A minimal Makefile:
CC := gcc
CFLAGS := -Wall -Wextra -O2
LDFLAGS :=
SRC := main.c util.c
OBJ := $(SRC:.c=.o)
BIN := myapp
all: $(BIN)
$(BIN): $(OBJ)
$(CC) $(OBJ) $(LDFLAGS) -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJ) $(BIN)Key points:
- Targets (like
all,clean,myapp) describe operations. - Dependencies (e.g.
$(BIN): $(OBJ)) tellmakewhat a target needs. - Recipes are the indented commands (must start with a TAB).
$@is the target name;$<is the first dependency.
Usage:
make # builds the default target (here: all)
make clean # cleans build artifacts
In CI, make is often the single entrypoint for building the project.
Package Management for Developers
You’ll often need libraries and headers for development, not just runtime packages.
Developer vs Runtime Packages
On Debian-like systems, development headers are usually in *-dev packages:
libssl1.1— runtime OpenSSL library.libssl-dev— headers and static libraries needed to build against OpenSSL.
On RPM-based systems, they typically end with -devel:
openssl-libsopenssl-devel
Example (Debian/Ubuntu):
sudo apt install libcurl4-openssl-dev libssl-devIf a build fails with a missing header, search your distro’s packages for that header name or library:
apt-cache search libcurl | grep dev # Debian/Ubuntu
dnf search libcurl-devel # Fedora/RHELUsing `pkg-config`
pkg-config helps discover compiler and linker flags:
pkg-config --cflags --libs libcurlTypical output:
-I/usr/include/x86_64-linux-gnu -lcurlYou can use this in Makefiles:
CFLAGS += $(shell pkg-config --cflags libcurl)
LDFLAGS += $(shell pkg-config --libs libcurl)
If pkg-config can’t find a library, you may need to install its -dev/-devel package or adjust PKG_CONFIG_PATH.
Git Fundamentals on Linux
Git is central to Linux-based development; this section focuses on practical daily usage rather than in-depth theory.
Initial Git Configuration
Per-user config:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global core.editor "vim"Check configuration:
git config --listCreating and Cloning Repositories
Create a new local repo:
mkdir myproject && cd myproject
git initClone an existing repo:
git clone https://github.com/user/repo.git
cd repoDaily Workflow: Status, Stage, Commit
- Check status:
git status- View changes:
git diff # unstaged changes
git diff --cached # staged changes- Stage files:
git add file1.c src/module.c- Commit:
git commit -m "Implement feature X"
Use .gitignore to exclude build artifacts (e.g. build/, *.o, .venv/).
Branching and Merging (Developer-Focused)
Create and switch branches:
git branch feature-x
git checkout feature-x
# or:
git checkout -b feature-xMerge into the main branch:
git checkout main
git merge feature-xRebasing onto the latest main:
git checkout feature-x
git fetch origin
git rebase origin/mainFor CI and code review, you’ll often:
- Branch from
main. - Commit logically grouped changes.
- Push to remote and open a pull/merge request.
Working with Remotes
Add a remote:
git remote add origin git@github.com:user/repo.gitPush a branch:
git push -u origin feature-xPull latest changes:
git pull # fetch + merge/rebase depending on config
git fetch # only update remote tracking branchesGit integration with SSH keys and CI-specific flows is covered elsewhere; here the focus is on your day-to-day commands.
Debugging Tools: `gdb`, `strace`, `lsof`
Linux provides powerful CLI debugging tools that integrate well with development and CI.
Building with Debug Symbols
For C/C++ debugging, compile with -g (debug info) and without heavy optimization (or with modest -O):
gcc -g -O0 main.c -o myapp
or in a CMakeLists.txt / Makefile via build-type flags.
Using `gdb` for Native Debugging
Basic session:
gdb ./myapp
(gdb) run arg1 arg2
(gdb) bt # backtrace on crash
(gdb) info locals
(gdb) quitAttach to a running process:
ps aux | grep myapp # find PID
gdb -p <PID>Useful breakpoints:
(gdb) break main
(gdb) break file.c:42
(gdb) break some_functionThis is invaluable for debugging segmentation faults, memory corruption, and complex logic errors in native code.
Tracing System Calls with `strace`
strace shows system calls made by a process:
strace ./myappCommon use cases:
- See which files are being opened or failing:
strace -e openat,stat ./myapp- Attach to a running process:
sudo strace -p <PID>
As a developer, strace is particularly useful when something behaves differently in production vs your local machine (e.g. missing config files, permission issues).
Inspecting Open Files and Ports with `lsof`
lsof lists open files (including network sockets):
- All open files from a process:
lsof -p <PID>- Which process is listening on a port:
sudo lsof -i :8080This helps with debugging port conflicts and resource leaks.
Working with Interpreted Languages on Linux
Many developers primarily use Python, Node.js, or similar. Linux is often the reference platform for these runtimes.
Python Environment Basics
Typical pattern for a project:
- Create a virtual environment:
python3 -m venv .venv- Activate it:
source .venv/bin/activate- Install dependencies:
pip install -r requirements.txt
Put .venv/ into .gitignore and keep requirements.txt under version control.
On Linux, you may also encounter distribution vs pip package conflicts; prefer virtual environments to avoid polluting system Python.
Node.js and npm
Common pattern:
npm init -y
npm install express --save
npm install --save-dev jestUseful Linux-specific points:
- Many build tools (like front-end bundlers) are memory-intensive; use system monitors to check if you’re constrained.
- Node.js versions are often managed via
nvmor distro packages; usenvmif you need multiple versions.
Logs, Environment, and Configuration for Developers
Understanding how your app interacts with the Linux environment is key for debugging and operability.
Environment Variables at Runtime
Applications often rely on environment variables for configuration:
export APP_ENV=production
export DB_URL="postgres://user:pass@localhost/db"
./myappFrom code (examples):
- C:
const char* env = getenv("APP_ENV");- Python:
import os
env = os.environ.get("APP_ENV")- Node.js:
const env = process.env.APP_ENV;In unit tests and CI, you typically set env vars in the test runner or CI configuration.
Working with Logs
Linux applications usually log to:
- Stdout/stderr (especially in containerized environments).
- Log files under
/var/log/(for system-level services). - Custom log directories under
/var/log/<appname>/or app-specific paths.
For developer-run services, you’ll often:
./myservice 2>&1 | tee myservice.logUse tools like:
tail -f logfile— live view.journalctl -u myservice— for systemd-managed services.
Add structured logs and log levels in your code to ease debugging in Linux environments.
Working Comfortably in Containers and CI
Modern development often targets containerized and CI environments that are Linux-based even if your desktop isn’t.
Building and Running in Docker (Developer View)
Example Dockerfile for building a simple C app:
FROM debian:stable-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
RUN make
CMD ["./myapp"]Build and run:
docker build -t myapp:latest .
docker run --rm myapp:latestAs a developer, you should:
- Ensure your build works non-interactively.
- Avoid relying on tools not available in the container (e.g. GUI-only tools).
CI-Friendliness
To run on Linux CI systems:
- Provide a single entrypoint command:
make test,npm test,pytest, etc. - Avoid hardcoding absolute paths; use relative paths and environment variables.
- Ensure your tests don’t require an interactive TTY.
CI configuration specifics are covered in the DevOps chapters; your role as a developer is to structure code and commands in a CI-friendly way.
Performance and Profiling Basics for Developers
Performance tuning in depth is covered elsewhere; here is what you’re likely to touch as a developer.
Simple Timing
Use the time command:
time ./myappThis reports real, user, and sys time.
CPU Profiling (Overview)
Common tools:
gprof— for applications compiled with-pg.perf— powerful Linux profiler (often needs root).- Language-specific profilers: e.g.
cProfilefor Python,--inspect/Chrome DevTools for Node.js.
Example with perf:
perf record ./myapp
perf reportHigher-level performance tools and advanced tuning live in later chapters; as a developer, the key is to know these exist and to ensure your code can be run under them without extra interaction.
Summary: Linux Skills Every Developer Should Aim For
As a developer on Linux, you should be comfortable with:
- Navigating and organizing source trees and build directories.
- Using
gcc/clang,make, or higher-level build tools like CMake. - Installing development libraries and using
pkg-config. - Daily Git workflows for branching, merging, and collaborating.
- Basic debugging with
gdb,strace, andlsof. - Managing language runtimes and virtual environments (Python, Node.js, etc.).
- Handling environment variables and logs for configuration and diagnosis.
- Ensuring your project builds and tests cleanly in Linux containers and CI.
These skills form the practical foundation for the more advanced DevOps and cloud topics that follow.