Linux & Shell — The DevOps Cockpit
What you'll learn
- Why DevOps lives in the Linux terminal
- Shell scripting — automating with text commands
- Pipes and redirection, the Unix superpower
- You'll write shell-style automation logic
Automate everything ⚙️
DevOps is the culture and practice of getting code from a developer's laptop into production fast, reliably, and automatically. Its home base is the Linux terminal — servers run Linux, and DevOps is about automating what you'd otherwise do by hand on them.
The key upgrade beyond typing commands one by one: a shell script — a text file of commands that run in sequence. Instead of manually deploying in 10 steps every time, you write them once and run one script.
#!/bin/bash
echo "Starting deployment..."
git pull # get latest code
npm install # install dependencies
npm run build # build the app
systemctl restart myapp # restart the service
echo "Deployment complete!"
Run that once and a whole deployment happens. This is the heart of DevOps: replace repetitive manual work with a script that's fast, consistent, and never forgets a step.
The Unix superpower: pipes (|). A pipe takes the output of one command and feeds it as input to the next, letting you chain tiny tools into powerful one-liners:
cat access.log | grep "ERROR" | wc -l
# read file → keep error lines → count them
That one line counts errors in a log — three simple tools combined. Redirection (>) sends output to a file instead of the screen: echo "done" > status.txt.
The Unix philosophy — small tools that each do one thing well, combined via pipes — is exactly how DevOps engineers build automation. You already saw grep, ls, and cat in the security track; here they become building blocks for scripts that deploy, monitor, and maintain systems automatically.
Simulate a pipe: grep "ERROR" | wc -l. Write countErrors(lines) where lines is an array of log strings. Return how many contain "ERROR" (case-sensitive).
Example: ["INFO ok", "ERROR bad", "ERROR worse"] → 2
Environment variables and process control 🎛️
Scripts often need to behave differently depending on where they run — a staging server vs production, a testing pipeline vs your laptop. Hardcoding those differences into the script itself is fragile. The Linux answer is environment variables: named values that live in the shell's environment, readable by any program that runs inside it.
export DATABASE_URL="postgres://prod-db:5432"
export LOG_LEVEL="info"
echo $DATABASE_URL # read a variable with $
node app.js # app.js can read process.env.DATABASE_URL
This is exactly how DevOps pipelines configure the same script or container differently per environment — staging gets DATABASE_URL pointing at a staging database, production gets the production one, with zero changes to the code itself. It's also the standard way to inject secrets at runtime (as you saw with Docker) instead of hardcoding them.
Process control matters just as much: a deploy script needs to know if the previous command actually succeeded before running the next one. Every command exits with a status code — 0 means success, anything else means failure:
npm run build && systemctl restart myapp
# ^^ only restarts if build's exit code was 0
&& chains commands so the next only runs if the previous succeeded — the shell-level version of the CI/CD principle "if a stage fails, stop the pipeline."
Simulate chained command execution with &&. Write runChain(commands) where commands is an array of { name, exitCode }. Run them in order; if any exitCode !== 0, stop immediately and return "STOPPED at " + name. If all succeed, return "ALL SUCCEEDED".
Real-world case study: GitLab.com's 2017 database incident 🔍
In early 2017, GitLab.com suffered a severe outage after an engineer, trying to clear a replication backlog, ran a directory-removal command against what he believed was a lagging secondary database — but was actually connected to the primary production database. Roughly 300GB of live data was deleted in seconds.
What made the incident survivable rather than catastrophic was largely the deep bench of backup mechanisms — several of which, GitLab later admitted publicly in a widely-read post-incident report, had silently failed for weeks or months without anyone noticing, leaving only one out of five backup/replication methods actually usable when it mattered. GitLab recovered from a snapshot that was several hours old, permanently losing some data in between.
The lesson connects directly to this lesson's themes: a single manually-typed shell command, run against the wrong target, caused the damage — exactly the kind of human error that scripting, guardrails, and double-checking your target before running a destructive command are meant to prevent. And a backup you've never test-restored isn't a real backup at all — GitLab's own transparent write-up became one of the most cited case studies in the industry for exactly that reason.
Lab exercise 1
BashGraded in Code LabSummarise a log in Bash. Each input line starts with a level (INFO, WARN or ERROR) followed by a message. Read standard input and count the lines per level.
Print exactly these five lines, in this order, even when a count is zero:
INFO <n>
WARN <n>
ERROR <n>
OTHER <n>
TOTAL <n>
Lines that start with any other word (and blank lines are ignored) count as OTHER. TOTAL is the number of non-blank lines.
- Counts each level
- Unknown levels are OTHER, blank lines ignored
- Empty input prints zeros
- A message that contains other level words
Example run
- Input
INFO service started WARN disk at 80% ERROR connection refused INFO request ok ERROR timeout ERROR timeout
- Expected output
INFO 2 WARN 1 ERROR 3 OTHER 0 TOTAL 6
#!/bin/bash info=0; warn=0; error=0; other=0 while read -r level rest || [ -n "$level" ]; do # TODO 1: skip blank lines (when $level is empty) # TODO 2: use `case "$level" in INFO) ... ;; WARN) ... ;; ERROR) ... ;; *) ... ;; esac` # to increase the right counter, e.g. info=$((info + 1)) : done echo "INFO $info" echo "WARN $warn"
More lines in Code Lab: 3
Hints and instant feedback are in Code Lab. Optional practice: it does not block finishing the lesson.
JavaScript and Python also run in your browser without an account; other languages use the secure server runner and need a free account.
Question 1 of 5
What is DevOps fundamentally about?
Question 2 of 5
What does a pipe (|) do in the shell?
Question 3 of 5
Why write a shell script instead of typing commands manually?
Question 4 of 5
Why do pipelines use environment variables instead of hardcoding values like database URLs?
Question 5 of 5
What does `npm run build && systemctl restart myapp` guarantee?
Summary
- DevOps = automating code → production, based in the Linux terminal
- Shell scripts turn repetitive manual steps into one reliable command
- Pipes (|) chain small tools; redirection (>) saves output to files
- Environment variables configure the same script per environment; exit codes and
&&chain commands safely - You simulated a grep|wc pipe and a chained command runner
- GitLab's 2017 database incident shows why destructive commands need guardrails and tested backups
Next: Git & Version Control — the DevOps branching workflows that make team automation possible.
Practice
Reading is not learning until you run something. Pick one.
Graded lab exercises
- Lab exercise 1BashNot passed yet Try in Code Lab