Week 1: Linux Fundamentals & the Command Line

Every tool in this course — Docker, Terraform, Kubernetes, the CI runner your pipeline executes on — ultimately runs on Linux and expects you to be comfortable at a shell prompt. This week builds that fluency from the ground up: the filesystem and permission model, processes, package managers, enough Bash to automate a repetitive task, and connecting to a remote machine over SSH.

Module 1 of 12 Week 1 of 15 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Navigate the Linux filesystem and read/change file permissions and ownership
  • Inspect and manage running processes, and install software with a package manager
  • Write a Bash script with variables, conditionals and loops, and SSH into a remote server

1. The Linux Filesystem & Permissions

Linux organizes everything — files, devices, even running processes — as a single tree rooted at /. Unlike Windows, there's no C:\; every drive and partition gets mounted somewhere inside that one tree. A handful of top-level directories show up constantly:

terminal
/bin, /usr/bin   # executables (ls, bash, docker, ...)
/etc             # system-wide configuration files
/home/<user>     # your personal files and dotfiles
/var             # logs, caches, and other data that changes at runtime
/tmp             # scratch space, cleared on reboot

You'll live in a handful of navigation and inspection commands:

terminal
pwd                 # print working directory
ls -la              # list all files, including hidden dotfiles, in long format
cd /var/log         # change directory
cat app.log         # print a file's contents
less app.log         # page through a large file (q to quit)
find . -name "*.log" # search recursively for files matching a pattern

Every file has an owner, a group, and permission bits for three audiences — owner, group, and everyone else — each with read (r), write (w) and execute (x) flags. ls -la prints them as a ten-character string:

terminal
$ ls -la deploy.sh
-rwxr-xr-- 1 ada devops 812 Jan 12 09:14 deploy.sh
# -   rwx      r-x      r--
# type owner    group    others
# owner (ada): read, write, execute
# group (devops): read, execute
# others: read only
terminal
chmod 750 deploy.sh       # owner: rwx, group: r-x, others: ---
chmod +x deploy.sh        # add execute for everyone
chown ada:devops deploy.sh # change owner and group
sudo chmod 600 id_rsa     # only the owner can read/write -- required for SSH keys

The numeric form (750) is three digits, one per audience, where each digit sums r=4, w=2, x=1. 750 means owner 7 (4+2+1 = rwx), group 5 (4+1 = r-x), others 0 (none). You'll type this pattern constantly once SSH keys and deployment scripts show up.

Why this matters for DevOps

Almost every "permission denied" error you'll hit setting up a CI runner, an SSH key, or a Docker volume traces back to this exact owner/group/others model — being able to read a permission string on sight will save you real debugging time later in this course.

2. Processes & Package Managers

A process is a running program. Every process has a numeric ID (PID), a parent process, and a resource footprint you can inspect directly:

terminal
ps aux              # list every running process
top                 # live, updating view of CPU/memory usage
kill 4821           # ask process 4821 to terminate gracefully (SIGTERM)
kill -9 4821        # force-terminate it (SIGKILL) -- last resort

You'll rarely install software by downloading a binary by hand. Each major Linux distribution ships a package manager that resolves dependencies, verifies checksums, and tracks what's installed so it can be cleanly removed later:

terminal — Debian/Ubuntu (apt)
sudo apt update              # refresh the package index
sudo apt install git curl jq # install packages
apt list --installed | grep git

This course uses Ubuntu-based examples throughout, but the pattern is the same everywhere: Red Hat/Fedora use dnf, Arch uses pacman, and macOS's unofficial equivalent is brew. Your first real task is getting the tools this course leans on installed and confirmed working:

terminal
sudo apt install -y git curl jq
git --version
curl --version
jq --version
On Windows

Install WSL2 (wsl --install from an admin PowerShell prompt) and use its Ubuntu shell for every command in this course. It's a real Linux kernel, not an emulator, so everything here — permissions, package managers, Docker — behaves exactly like it would on a Linux server.

3. Bash Scripting Basics

A shell script is just a sequence of the same commands you'd type interactively, saved to a file so you never have to type them again. Variables, conditionals and loops turn that into real automation — the same shape you'll later see in CI pipeline steps and Dockerfile RUN commands.

check-disk.sh
#!/usr/bin/env bash
set -euo pipefail   # exit on error, on unset variables, and on pipe failures

THRESHOLD=80
USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')

if [ "$USAGE" -ge "$THRESHOLD" ]; then
    echo "WARNING: disk usage at ${USAGE}%, threshold is ${THRESHOLD}%"
    exit 1
else
    echo "OK: disk usage at ${USAGE}%"
fi

set -euo pipefail belongs at the top of nearly every script you'll write in this course: -e stops the script the moment any command fails, -u turns a typo'd variable name into an immediate error instead of a silent empty string, and pipefail makes a failure anywhere in a cmd1 | cmd2 pipeline fail the whole line, not just the last command.

backup-logs.sh — loops and arguments
#!/usr/bin/env bash
set -euo pipefail

LOG_DIR="${1:-/var/log/myapp}"   # first argument, defaulting to /var/log/myapp
DEST="/tmp/backups/$(date +%F)"

mkdir -p "$DEST"

for file in "$LOG_DIR"/*.log; do
    name=$(basename "$file")
    cp "$file" "$DEST/$name"
    echo "Backed up: $name"
done

echo "Done. $(ls "$DEST" | wc -l) files copied to $DEST"

Run it with chmod +x backup-logs.sh && ./backup-logs.sh /var/log/nginx. Quoting variables ("$file", not $file) matters: unquoted, a filename with a space in it splits into two arguments and silently breaks the script — one of the most common Bash bugs you'll run into.

4. SSH & Remote Servers

SSH (Secure Shell) is how you'll administer every remote machine in this course — a cloud VM in Week 8, a CI runner, a Kubernetes node. It authenticates with a public/private key pair instead of a password by default:

terminal
# Generate a key pair once (press Enter to accept defaults)
ssh-keygen -t ed25519 -C "you@example.com"

# Copy your public key to a remote server you already have password access to
ssh-copy-id ada@203.0.113.10

# Connect -- no password needed after the key is installed
ssh ada@203.0.113.10

# Copy a file to/from a remote host
scp ./deploy.sh ada@203.0.113.10:/home/ada/

The private key (~/.ssh/id_ed25519) never leaves your machine and must stay chmod 600 — SSH will refuse to use a key that other users can read. The public key (id_ed25519.pub) is what you hand out and append to a server's ~/.ssh/authorized_keys.

Looking ahead

This same key pair is what you'll register with GitHub for Git access, and the same authentication model (a keypair a service trusts) reappears as AWS access keys in Week 8 and Kubernetes service account tokens in Week 11 — the underlying idea doesn't change even as the tool does.

5. Hands-on Exercise

Hands-on

Write a system health-check script

Combine this week's filesystem, process and Bash skills into one small but genuinely useful tool.

Requirements:

  1. Set up WSL2 (Windows) or confirm your Linux/macOS terminal, then install git, curl and jq with your package manager.
  2. Generate an SSH key pair with ssh-keygen -t ed25519 and confirm the private key is chmod 600.
  3. Write healthcheck.sh with set -euo pipefail that reports: disk usage percentage on /, the top 3 processes by memory usage (ps aux --sort=-%mem | head -4), and the number of files in /var/log.
  4. Make the script accept an optional disk-usage threshold as its first argument (default 80), and exit 1 with a clear message if usage is at or above it.
  5. Make it executable with chmod +x healthcheck.sh and run it as ./healthcheck.sh 90.
Hint

Reuse the disk-usage line from the check-disk.sh example above rather than reinventing it — real ops scripts are built by composing small, previously-proven pieces, not by writing everything from scratch each time.

6. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What does chmod 750 deploy.sh actually set?

Owner permissions to 7 (read+write+execute, 4+2+1), group permissions to 5 (read+execute, 4+1), and permissions for everyone else to 0 (none). Each digit is an independent sum of read(4)/write(2)/execute(1) for one of the three audiences: owner, group, others.

Q2

Why does set -euo pipefail matter at the top of a Bash script?

-e stops execution the moment any command exits with a failure, instead of silently continuing; -u turns a reference to an unset variable into an error rather than an empty string; and pipefail makes a pipeline like cmd1 | cmd2 fail if either command fails, not just the last one. Without these, scripts can silently limp forward after a real failure and cause damage several lines later.

Q3

Why does SSH refuse to use a private key that isn't chmod 600?

A private key proves your identity to every server that trusts its matching public key. If group or other users on the machine could read it (permissions looser than owner-only read/write), anyone with local access could impersonate you on any server you can reach — so SSH enforces owner-only access as a safety check rather than trusting you to remember it.

Q4

Why quote a variable as "$file" instead of writing $file in a script?

Unquoted, Bash performs word-splitting on the variable's value — a filename containing a space (like "march report.log") becomes two separate arguments instead of one, which silently breaks commands like cp or a for loop. Quoting preserves the value as a single argument regardless of spaces or other special characters inside it.

← Back to Full Syllabus Up next Week 2: Networking Basics & Git for Ops — coming soon