Terminal & DevOps: Linux Command Line in Depth
The terminal is where DevOps work actually happens—SSH into a node, tail logs during an incident, run kubectl and terraform, debug a pipeline. Linux powers most servers, containers, and cloud VMs; this handbook covers how the system is organized, which commands you need day to day, and how to debug permissions, disk, processes, networking, and services without memorizing a thousand flags.
In short
Treat Linux as files + processes + permissions. Master the shell and core CLI tools, learn systemd and journalctl on servers, then wire the same terminal habits into Git, containers, Kubernetes, and IaC. When something breaks: logs → resources → process → network → recent change.
What Linux is (and why it matters to you)
Linux is an open-source kernel—the core that talks to hardware, schedules processes, manages memory, and enforces permissions. What you install on a laptop or server is usually a distribution (Ubuntu, Debian, Fedora, RHEL, Alpine): the kernel plus userspace tools, package manager, and defaults.
Most cloud VMs, containers, and Kubernetes nodes run Linux. Even when you write application code on macOS or Windows, production debugging often means SSH into a Linux box, reading logs, and fixing permissions. For the wider stack—CPU, memory, OS theory, networking—see Computing in Depth. Treat this guide as a handbook: concepts first, then commands you will use weekly as a developer, operator, or platform engineer.
The mental model: everything is a file
Linux exposes much of the system through the filesystem:
- Regular files and directories (obvious).
- Devices under
/dev(disks, terminals). - Process information under
/proc(read/proc/1234/statusfor PID 1234). - Kernel tunables under
/sys. - Sockets and pipes—also file descriptors from the shell’s point of view.
Commands are programs in PATH (often /usr/bin). Configuration lives in /etc; your home directory ~ holds personal settings. Once this clicks, man pages and errors make more sense: “Permission denied” is often a file mode problem; “No space left on device” may be inodes or a full disk.
Filesystem hierarchy (FHS) — where things live
| Path | Purpose |
|---|---|
/ | Root of the tree; all paths start here |
/home | User home directories (/home/alice) |
/root | Home for the root user |
/etc | System and service configuration |
/var | Variable data: logs (/var/log), caches, databases |
/tmp | Temporary files (often cleared on reboot) |
/usr | User programs and libraries (most commands) |
/bin, /sbin | Essential binaries (init, shell, mount) |
/opt | Optional third-party software |
/proc, /sys | Virtual views of processes and kernel state |
Containers and VMs still use the same ideas; paths may differ slightly in minimal images (Alpine, distroless). See Docker’s hidden side for how images relate to the host kernel.
Opening a terminal and getting help
Common shells: bash (default on many distros), zsh, sh (minimal). The prompt shows user@host and current directory.
# Built-in help
help cd # shell builtins
man ls # manual page (q to quit)
ls --help # short flag summary
info coreutils # GNU info (optional)
# Which program runs?
type ls
which python3
command -v kubectl
Navigation and listing files
pwd # print working directory
cd /var/log # absolute path
cd .. # parent directory
cd ~ # home
cd - # previous directory
ls # names only
ls -l # long format (permissions, owner, size, date)
ls -la # include hidden dotfiles
ls -lh # human-readable sizes
tree -L 2 /etc # tree view (install tree if missing)
Paths: / absolute from root; ./file current dir; ../dir parent; ~/docs under home. Tab completes names; ls app<TAB> saves typing. For what happens under the hood when you run ls, see What happens when you type ls and press Enter.
Creating, copying, moving, and deleting
touch notes.txt # empty file or update timestamp
mkdir -p projects/app/src # create nested dirs
cp file.txt backup.txt
cp -r dir/ dir-copy/ # recursive for directories
mv old.txt new.txt # rename or move
rm file.txt
rm -r dir/ # recursive delete — no trash bin
rm -i file.txt # prompt before remove
# Links
ln -s /etc/nginx/nginx.conf ~/nginx.conf # symbolic (shortcut)
ln file hardlink # hard link (same inode)
Use rm -r carefully. For safety in scripts, prefer explicit paths and avoid rm -rf / folklore—modern systems often block it, but rm -rf $EMPTY_VAR/* has destroyed real data.
Reading and editing file contents
cat file.txt # print whole file (small files)
less file.log # scroll (Space, q to quit)
head -n 20 file.log
tail -n 50 file.log
tail -f /var/log/syslog # follow new lines (live logs)
wc -l file.txt # line count
file binary # guess file type
stat file.txt # inode, size, timestamps
# Editors (pick one and learn it)
nano file.txt # beginner-friendly
vim file.txt # powerful; :wq save quit, :q! force quit
Permissions and ownership
ls -l shows something like -rw-r--r-- 1 alice dev 4096 May 20 10:00 app.conf:
- First character:
-file,ddirectory,lsymlink. - Next nine: rwx for user, group, others.
- Owner and group names; then size and time.
chmod 644 file.txt # rw- r-- r--
chmod u+x script.sh # add execute for owner
chmod -R g-w secrets/ # recursive
chown bob:ops file.txt
chgrp dev file.txt
umask # default mask for new files (often 0022)
Numeric modes: read=4, write=2, execute=1; add per triplet (e.g. 7 = rwx). Directories need x to enter (cd). Scripts need x to run as programs.
Users, groups, and sudo
whoami
id
id alice
# Admin tasks (Ubuntu/Debian)
sudo apt update
sudo -i # root shell (use sparingly)
# User management (root or sudo)
sudo useradd -m -s /bin/bash devuser
sudo passwd devuser
groups devuser
sudo usermod -aG docker devuser # add to supplementary group
sudo runs one command as root using your password (or NOPASSWD rules). Principle of least privilege: daily work as normal user; elevate only when needed. In production, prefer SSH keys, sudo logging, and break-glass accounts over shared root passwords.
Processes: see what is running and stop it
ps aux # all processes (BSD style)
ps -ef # POSIX style
pgrep nginx
pidof nginx
top # interactive (q to quit)
htop # friendlier if installed
kill 1234 # SIGTERM (polite shutdown)
kill -9 1234 # SIGKILL (last resort)
killall nginx
# Foreground / background in shell
./long-job.sh &
jobs
fg %1
bg %1
nice -n 10 ./cpu-task.sh # lower priority
PID 1 is systemd (or historically init) on most servers. Zombie processes are dead children not yet reaped—usually harmless in small numbers; investigate the parent. In containers, PID 1 behavior matters—see the Docker post linked above.
Disk, memory, and system health
df -h # filesystem usage
df -i # inode usage (can block writes when full)
du -sh /var/log/* # directory sizes
du -h --max-depth=1 .
free -h # RAM and swap
uptime # load averages
uname -a # kernel version
hostnamectl # host name and OS (systemd hosts)
lscpu
lsblk # block devices
Load average (three numbers) is a rough queue of runnable tasks—not CPU percent. On a 4-core box, sustained load > 4 suggests saturation. Combine with top, iostat, and application metrics—not load alone.
Networking essentials
# Modern iproute2 (prefer over legacy ifconfig/route)
ip a
ip route
ss -tlnp # listening TCP ports and processes
ss -unap
ping -c 3 example.com
curl -I https://example.com
curl -o out.bin https://example.com/file
wget https://example.com/file
dig example.com A
nslookup example.com
traceroute example.com # or tracepath
# Firewall (examples — names differ by distro)
sudo ufw status
sudo firewall-cmd --list-all # RHEL/Fedora firewalld
“Connection refused” usually means nothing listens on that port. “Timeout” often means firewall, routing, or wrong IP. “Name or service not known” is DNS. ss is faster than older netstat for “what is listening?”
Packages: installing and updating software
Distros ship a package manager that resolves dependencies from repositories.
# Debian / Ubuntu
sudo apt update
sudo apt install -y curl jq
apt search nginx
apt show nginx
sudo apt remove nginx
sudo apt autoremove
# RHEL / Fedora / Amazon Linux 2023
sudo dnf install -y vim
sudo dnf update
rpm -qa | grep curl
# Alpine (common in containers)
apk add --no-cache curl
Prefer distro packages for servers when you can; use language-specific tools (pip, npm) in virtual environments or containers to avoid polluting system Python.
Services with systemd
Most Linux servers use systemd as init: it starts services, mounts filesystems, and manages targets (runlevels).
systemctl status nginx
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl enable nginx # start on boot
sudo systemctl disable nginx
journalctl -u nginx -f # follow service logs
journalctl -xe # recent errors, explained
systemctl list-units --type=service --state=running
Unit files live under /etc/systemd/system/ and /lib/systemd/system/. After editing: sudo systemctl daemon-reload then restart the unit.
The shell: pipes, redirection, and variables
The shell connects programs: stdout of one becomes stdin of another.
# Redirection
echo hello > out.txt # overwrite
echo world >> out.txt # append
cmd 2> errors.log # stderr
cmd &> all.log # stdout + stderr
cmd < input.txt
# Pipes
cat access.log | grep " 500 " | wc -l
curl -s https://api.example.com/health | jq .
# Variables
export APP_ENV=production
echo $PATH
env | sort
Quote variables: "$HOME" handles spaces. Single quotes '$HOME' prevent expansion. Scripts start with #!/bin/bash and chmod +x.
Finding and filtering text
grep pattern file.txt
grep -r pattern /etc/nginx/ # recursive
grep -i error app.log # case insensitive
grep -n error app.log # line numbers
grep -E 'error|warn' app.log # extended regex
find /var/log -name "*.log" -mtime -7
find . -type f -size +100M
find /tmp -name core -delete # careful with -delete
sort names.txt | uniq -c | sort -nr # count duplicates
cut -d, -f1,3 data.csv
awk -F, '{print $1, $3}' data.csv
sed 's/foo/bar/g' file.txt # stream edit (test first)
sed -n '10,20p' file.txt # print lines 10-20
find is powerful and easy to misuse—always test without -delete first. For large trees, narrow path and use -name early.
Archives and compression
tar -cvf archive.tar dir/
tar -xvf archive.tar
tar -czvf archive.tar.gz dir/ # gzip
tar -xzvf archive.tar.gz
tar -cjvf archive.tar.bz2 dir/ # bzip2
zip -r out.zip folder/
unzip out.zip
Flags: c create, x extract, v verbose, f file, z/j compression. This is how you move app releases and backup configs—same skills apply inside CI artifacts.
SSH, SCP, and rsync (remote administration)
ssh [email protected]
ssh -i ~/.ssh/id_ed25519 user@host
ssh -p 2222 user@host
scp file.txt user@host:/tmp/
scp -r ./dist user@host:/var/www/
rsync -avz ./local/ user@host:/remote/path/
rsync -avz --delete ./build/ user@host:/app/ # mirror; destructive on target
Use SSH keys, disable password login on servers, and restrict sudo. For automation, consider Ansible (see Terraform & IaC and your config management stack) over ad-hoc scp loops.
Logs: where evidence lives
- journalctl — systemd journal (structured, per-unit).
- /var/log — syslog, auth.log, nginx/, application logs.
- Application stdout — in Kubernetes,
kubectl logs; on VMs, often captured by the service manager or a log agent.
sudo journalctl -f
sudo journalctl -u myapp --since "1 hour ago"
sudo tail -f /var/log/nginx/access.log
dmesg | tail # kernel ring buffer (boot/hardware)
Scheduling: cron and timers
crontab -e # edit your crontab
# minute hour day month weekday command
# 0 2 * * * /usr/local/bin/backup.sh
crontab -l
sudo systemctl list-timers # systemd timers (preferred on modern distros)
Cron jobs run in a minimal environment—use full paths and log output. Prefer systemd timers when you already manage the service with systemd.
Environment, PATH, and shell configuration
Your shell loads settings from files such as ~/.bashrc (interactive bash) or ~/.profile. The PATH variable is a colon-separated list of directories searched for commands.
echo $SHELL
echo $PATH
export PATH="$HOME/bin:$PATH"
# Persist in ~/.bashrc
echo 'export EDITOR=vim' >> ~/.bashrc
source ~/.bashrc
# Run command once without editing profile
EDITOR=nano crontab -e
Aliases save typing: alias ll='ls -lah'. For repeatable ops, put logic in scripts under ~/bin or /usr/local/bin with a proper shebang and chmod +x.
Mounts, disks, and fstab (when df shows full)
lsblk -f # filesystems and UUIDs
sudo mount /dev/xvdf1 /mnt/data
mount | column -t
# /etc/fstab — mounts at boot (edit carefully)
# UUID=... /mnt/data ext4 defaults,nofail 0 2
sudo blkid
sudo umount /mnt/data
Cloud volumes attach as block devices; format once (mkfs.ext4), mount, then add an fstab entry using UUID so the mount survives reboot. Always test with sudo mount -a after editing fstab—a bad line can prevent boot.
Vim essentials (minimum survival kit)
Many servers only ship vim. You do not need every plugin—learn insert mode vs normal mode:
vim file.txt
# Normal mode (default after open):
# i — insert before cursor
# Esc — back to normal mode
# :w — save
# :q — quit
# :wq — save and quit
# :q! — quit without saving
# /word — search forward
# dd — delete line
# u — undo
Security habits (non-negotiable on real servers)
- Keep packages patched:
apt upgrade/dnf updateon a schedule. - SSH keys, no root login, firewall only required ports.
- Least privilege: separate users and groups; avoid chmod 777.
- Secrets not in Git—use vaults or secret managers; same discipline as GitOps.
- Audit
sudoand auth logs in/var/log/auth.logor journal. - SELinux (RHEL) or AppArmor (Ubuntu) may block access—learn to read denials instead of disabling blindly.
Troubleshooting playbook
- Reproduce — exact command, user, host, time.
- Logs — journalctl, app log, nginx error log.
- Resources —
df -h,free -h,dmesg. - Process — is it running?
systemctl status,ss -tlnp. - Permissions —
ls -l, SELinux/AppArmor context if enabled. - Network — DNS, firewall, curl from the same host.
- Change — what deployed last? Git diff, package update, config edit.
Under pressure, write timestamps and commands you ran—future you (and teammates) will thank you. Pair operational calm with incident response practice.
Command cheat sheet (quick reference)
| Task | Command |
|---|---|
| Where am I? | pwd |
| List files | ls -lah |
| Copy / move | cp -r, mv |
| Permissions | chmod, chown |
| Find text | grep -r |
| Find files | find |
| Disk space | df -h, du -sh |
| Memory | free -h |
| Processes | ps aux, top, kill |
| Services | systemctl status |
| Logs | journalctl -u, tail -f |
| Network | ip a, ss -tlnp, curl |
| Remote | ssh, rsync -avz |
| Archives | tar -czvf, tar -xzvf |
The DevOps toolchain from your terminal
Most DevOps tools are CLI-first: they compose with pipes, run in CI agents, and script the same way on your laptop and in production. You do not need every flag memorized—learn the verbs that match your mental model, then use --help and tab completion.
Git and GitHub (git, gh)
Version control is the spine of delivery. Daily flow: branch, commit, push, open a pull request, merge. For depth on the object model, recovery, and GitHub Actions, see Git & GitHub in depth.
git status
git switch -c feature/login
git add -p # stage hunks interactively
git commit -m "feat: add login form"
git push -u origin HEAD
gh pr create --fill
gh pr checks
gh run list --workflow deploy.yml
Containers (docker / podman)
Build images, run locally, inspect layers. Production clusters use containerd/CRI; the CLI still teaches image and process semantics. See Docker’s hidden side for namespaces and cgroups.
docker build -t myapp:local .
docker run --rm -p 8080:8080 myapp:local
docker ps -a
docker logs -f <container_id>
docker exec -it <container_id> sh
docker inspect myapp:local | jq '.[0].Config'
Kubernetes (kubectl)
kubectl talks to the API server—same resources as YAML in Git. Context and namespace matter; always know where you are pointing.
kubectl config current-context
kubectl get pods -A
kubectl -n prod get deploy,svc
kubectl describe pod/myapp-7d9f8b-abcde -n prod
kubectl logs -f deploy/myapp -n prod --tail=100
kubectl exec -it deploy/myapp -n prod -- sh
kubectl apply -f manifests/
kubectl diff -f manifests/ # requires plugin or server dry-run
kubectl port-forward svc/myapp 8080:80
For a guided lab sequence, start with Kubernetes hands-on part 1.
Infrastructure as Code (terraform)
Terraform plans changes before apply—treat plan output like a code review. State is sensitive; never commit secrets. Full beginner walkthrough: Terraform & IaC for everyone.
terraform init
terraform fmt -recursive
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
terraform output -json | jq .
terraform state list
Cloud CLIs (aws, gcloud, az)
Hyperscaler CLIs mirror APIs: list resources, call services, pipe JSON to jq. Prefer named profiles and short-lived credentials over long-lived keys in shell history.
aws sts get-caller-identity
aws s3 ls
aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId'
gcloud config list
gcloud compute instances list
az account show
az group list -o table
JSON on the command line (jq)
APIs and cloud tools return JSON. jq filters and shapes it without leaving the terminal.
curl -s https://api.github.com/repos/owner/repo | jq '.full_name, .stargazers_count'
aws iam list-users --output json | jq '.Users[].UserName'
kubectl get pods -o json | jq '.items[] | {name: .metadata.name, phase: .status.phase}'
Long sessions: tmux and SSH
On-call and deploy windows run longer than one SSH connection. tmux keeps panes alive if your laptop sleeps; pair with SSH config for jump hosts and key-based auth (see SSH section above).
tmux new -s incident
# Ctrl+b then c — new window; % split pane; d detach
tmux attach -t incident
tmux list-sessions
Shell scripts in CI and GitOps
Pipeline steps are shell commands in a clean environment. Keep scripts set -euo pipefail, quote variables, log with timestamps, and fail fast with clear messages. GitOps controllers reconcile declared YAML from Git—your terminal applies the same manifests locally for dry runs; principles in GitOps principles.
#!/usr/bin/env bash
set -euo pipefail
trap 'echo "Failed at line $LINENO"' ERR
log() { echo "[$(date -Iseconds)] $*"; }
log "Running smoke test"
curl -fsS https://app.example.com/health | jq -e '.status == "ok"'
From handbook to platform work
Linux and terminal fluency stack directly into the rest of a platform career:
- Containers — namespaces and cgroups on a shared kernel (Docker hidden side).
- Kubernetes — nodes are Linux; debugging often means
crictl, node logs, and cgroup limits (Kubernetes architecture). - CI/CD and GitOps — pipelines run shell; manifests apply over APIs (GitOps principles).
- Data stores — Redis, SQL, and queues all run on Linux hosts you must monitor (Redis and redis-cli, SQL course).
You do not need to memorize every flag. Learn the mental model, keep this cheat sheet handy, and practice on a small VM or WSL until commands become muscle memory.
Further reading
- Linux kernel documentation
- systemd man pages
- William Shotts — The Linux Command Line (free online book)
- Michael Kerrisk — The Linux Programming Interface (deep reference)
Blog index · Docker hidden side · Kubernetes hands-on lab · Terraform & IaC