Most DevOps material is organised the way the documentation is: by feature, in the order the API happens to list them. That works for reference and badly for learning, because it means you meet “use a volume” as a rule before you know what a writable layer is — and a rule without its cause does not survive contact with a real problem.
So there are two ways in here. If you want a sequence, the paths below are ordered by dependency: each step exists because the previous one raises the question it answers. If you already have a specific question, go straight to the kind of explanation that matches it.
If you want a sequence
Docker, in the order that makes it stick
You have run docker run and it worked, and you are not confident you could explain why.
The usual Docker curriculum teaches features in the order the documentation lists them. This orders them by dependency instead — each step exists because the previous one raises a question it answers.
Before you start: You can install Docker and start a container. No Linux internals are assumed; the first step supplies the ones you need.
By the end you can
Explain what a container shares with its host, and what that changes compared with a virtual machine.
Say where a file written inside a container goes, and why it is gone when the container is removed.
Tell EXPOSE from a published port, and fix a service that is not reachable from the host.
Write an ENTRYPOINT and CMD that let your process receive the stop signal.
The writable layer is the second load-bearing idea. It has to come before anything about persistence, or “use a volume” is a rule instead of a consequence.
Right after ENTRYPOINT because this is the kernel refusing to start exactly the program those settings name. It also brings back step one: the container shares the host's kernel, so it needs a binary built for the host's CPU.
Ends here because it tests exactly this sequence, and the last question in it is the one you can only answer if the earlier steps landed.
Check yourself
Run an image of your own, write a file inside the container, remove the container and start a fresh one: say why the file is gone and what you would change to keep it. Then time docker stop on it. About ten seconds means your process never received SIGTERM and was killed when the default timeout ran out.
Kubernetes troubleshooting, from symptom to cause
You can deploy to Kubernetes and you lose time when something breaks.
Ordered the way an incident actually unfolds rather than the way the API is organised. Each step is either a failure you will meet or the concept you need to have already understood when you meet it.
Before you start: You can apply a manifest and use kubectl get, describe and logs. The Docker path, or equivalent experience, helps with the exit codes.
By the end you can
Tell a Pod that cannot be scheduled because of its requests from one killed because of its limits.
Tell a restart caused by a failing probe from an application that exits on its own.
Explain the errors that appear for a moment during every rolling update.
Say why a successful rollout does not prove the application works.
First because it explains why failures are asynchronous and show up somewhere other than the command you ran. Without it, kubectl apply succeeding while nothing happens is baffling.
Two of the four common failures are here, and they are opposite: Pending is about requests; OOMKilled is about limits, except when the whole node runs out of memory and the kernel picks a victim by QoS class. Separating them early saves a lot of wrong turns.
Needed before CrashLoopBackOff, because failing liveness or startup probes are one of the documented causes of a restart loop, separate from the application exiting on its own.
The failure the previous comparison predicts: the pod keeps running but gets no traffic, because readiness never restarts anything. The message after the colon says which of a handful of causes it is.
Now the cause list reads as a set of distinct things rather than one confusing state, because you can already tell a probe-induced restart from an application exit.
A check on whether the sequence transferred. Most of its questions are steps above, asked without warning; the rest build on them.
Check yourself
In a test namespace, create one Pod whose memory request is larger than any node can offer, and one whose memory limit is smaller than it uses. Name the state each ends in, and the line in kubectl describe pod that told you: an event for the first, the last state of the container for the second.
Securing a pipeline you did not design
You inherited a CI/CD setup and you are not sure what to worry about first.
Ordered by likelihood and irreversibility rather than by risk number. The first two are where one mistake does the most lasting damage; the rest matter more once those are handled.
Before you start: You can read your pipeline's configuration, and you have admin rights on the repository, or someone who has them is willing to help.
By the end you can
Say what to do in the first hour after a secret is committed, and why deleting it is not on the list.
Require review on the branch that deploys, with administrators included in the rule.
Install dependencies strictly from the lockfile with install scripts switched off.
Explain what a green pipeline does and does not prove about the artifact it built.
Installing strictly from the lockfile with install scripts switched off is cheap and shuts two direct routes in — a silently updated malicious version, and code running at install time — which makes this the best ratio of protection to work on the whole list.
Last because the final question asks you to prioritise these — which you can now do from reasoning rather than from the order they were listed in.
Check yourself
For one repository: list every place the pipeline can read a secret from — variables, build arguments, image layers, logs. Check whether an administrator can push straight to the branch that deploys. And check whether the install step uses the lockfile with scripts off (for npm, npm ci --ignore-scripts).
Linux and networking for the server you look after
You can SSH into a server and run commands, and you are the one people ask when it misbehaves.
Ordered from getting onto the machine, to reading what it is doing, to the traffic going in and out. Trust bookends it — the SSH host key near the start and the TLS certificate chain at the end — because they are the same idea, and meeting it twice is what makes it stick.
Before you start: A Linux shell you are comfortable in: moving around, reading files, running a command with sudo. Nothing about networking is assumed.
By the end you can
Find out from ssh -v and the server's log why a key was refused, and check a changed host key against a fingerprint you trust instead of deleting the line.
Read free without alarm, and say whether a machine is actually short of memory.
Tell a service that never launched from one that started and then exited.
Write an address range in CIDR notation, and the security group and network ACL rules that let a reply back out.
Say from a proxy's status code and its error log whether the service behind it refused, hung up or never answered.
First because it is how you prove who you are to a server, and its causes — the wrong user, a key never offered, file permissions sshd will not trust — show where SSH looks before it lets anyone in.
The other direction: the server proving who it is to you. Together with the first step it is the whole of SSH trust, and the safe response to it is a habit worth forming before the day it matters.
The first reading most people get wrong on a healthy machine. Learn why free looks alarming and available is the number to watch before diagnosing anything that uses memory.
Now the services on the box. The exit status splits the failure into never launched and launched-then-quit, which tells you whether to read the unit file or the program's own output.
Placed between processes and networking on purpose: every socket costs a file descriptor, so this is where a per-process limit starts to look like a network fault. One of its fixes lives in the unit file from the previous step.
Addresses before rules about addresses. Work out what a /24 and a /26 contain, because the firewall rules in the next step are written in this notation.
Stateful against stateless filtering, and the outbound rule for ephemeral ports that people miss. It needs CIDR blocks to read, which is why it follows the calculator.
Traffic has reached your proxy; now the proxy reports on the service behind it. A 502 is an attempt that ended and a 504 is one that never did, and they need opposite fixes.
The cause list for the more common of the two. It starts from the proxy's error log, which records whether the upstream refused the connection, hung up, or answered with something unusable.
The proxy again, this time refusing the request itself instead of reporting on the service behind it. Reading which layer wrote the error page is the same skill as reading a 502.
Ends on trust again, now for TLS. A chain of signatures leading back to an authority you already trust answers the same question the host key answered in step two, and every HTTPS client asks it.
Check yourself
On a server you run, pick a service and find its main PID with systemctl show -p MainPID <service>. Count its open descriptors with ls /proc/<pid>/fd | wc -l and compare that with the Max open files line in /proc/<pid>/limits. Then explain to someone else why free shows so little free memory on the same machine.
Git without losing work
You commit and push every day, and you reach for a search engine when Git refuses something.
Ordered around one line: whether a commit is still only yours, or somebody else may already have it. Every refusal in this path is Git protecting history that someone may depend on, and every safe fix starts from knowing which side of that line you are on.
Before you start: You can commit, push and pull, and you have a repository with a remote on GitHub, GitLab or similar.
By the end you can
Choose between merge and rebase by asking whether anyone else already has the commits.
Recover from a rejected push without --force, and know when --force-with-lease is the right exception.
Undo a pushed commit with revert and an unpushed one with reset, without losing uncommitted work.
Explain why a committed credential has to be revoked, not just deleted.
First because it draws the line the rest of the path keeps returning to: rebase rewrites commits, which is harmless on work only you have and trouble on anything others have pulled.
The first refusal you will meet, and a direct consequence of the previous step: the remote has commits yours is not built on. The fix is to bring them in, not to force.
The same refusal from the other end: two histories with no commit in common, usually because the repository was created on the host with its own first commit. It only makes sense once you know a merge starts from a shared commit.
Undoing comes after combining because the right tool depends on the line from step one: reset rewrites, revert adds. It is also where uncommitted work can be lost for good, with reset --hard.
The undo that does not exist. After reset and revert it is clear why deleting a committed secret leaves it reachable, in your history and in every clone already made.
Ends with the host stopping the push for you. Fixing it means rewriting commits you have not pushed yet, which is everything above at once, and replacing the credential if it is real.
Check yourself
In a scratch repository with a second clone, commit in both and push both, so the second push is rejected. Fix it without --force. Then undo one of your commits with git revert <commit> and confirm with git log --graph --oneline that no existing commit was rewritten.
Terraform: state, plans and the errors between them
You run terraform apply on infrastructure other people share, or you are about to.
Ordered around state, because nearly every confusing Terraform error is about it: what it records, who may write it, and what happens when reality or another run moves it. The configuration language comes after state for the same reason.
Before you start: You can write a resource block and run init, plan and apply. A remote backend helps as context but is not required.
By the end you can
Say what state records that neither the configuration nor the cloud does.
Decide whether a stuck lock is safe to release, and what to check before you do.
Tell real drift from a provider describing the same value differently.
Choose count or for_each for a resource, and predict what removing one item will do.
First because everything else here follows from it: state maps blocks of code to real resources, and it is the only record of things the provider does not report back.
Who may write state, and the one command in this path that can corrupt it. The whole question is whether the run holding the lock is dead or still alive, which only matters once you know what state is.
Reality moving out from under the state, or a provider describing the same value in a different form. The two look identical in a plan and need opposite responses.
A saved plan is worked out against one version of state. If anything writes state before you apply it, Terraform refuses, and after the lock and drift steps it is clear why there is no flag to force it.
Now the language, because how instances are named decides what a plan does when a list changes: removing an item from the front of a count list rewrites every instance after it.
The error that comes with choosing for_each: every key must be known before apply. The fix the message suggests reads naturally once you know why instances need stable names.
Last because it is about the providers rather than state. The lock file pins their versions and belongs in version control next to the configuration; the error is the two disagreeing.
Check yourself
In an empty directory with the default local backend, create three terraform_data resources from a list of three names, once with count and once with for_each over the same names, and apply both. Remove the first name and run terraform plan on each. Count shows two changes and a destroy; for_each shows one destroy. Explain why from the instance addresses in the plan.
Reliability: what to promise, what to page on
You own a service in production and decide, or will soon decide, what wakes someone up.
Ordered from the promise to the alarm: what you commit to, how much failure that leaves room for, and which signals deserve a page. The failure steps come after, because each is a case where a check passed while users suffered.
Before you start: You run something in production and have some kind of metrics system. The Prometheus step assumes you have seen a scrape configuration; the rest does not.
By the end you can
Write an SLO for a service and say how much downtime it allows in a month.
Sort a list of alerts into those that should page someone and those that should not.
Tell a monitoring failure from a service failure.
Explain why a green deploy is weaker evidence than a user-facing measurement.
First because every other step uses the words: what you measure, the target you hold yourself to, and the contract, which should promise less than the target.
Turns a target into minutes. 99.9% over 30 days allows about 43 minutes of downtime, and seeing that number is what makes a target a decision rather than a slogan.
With a budget in hand the paging rule follows: page on what users feel, because a cause can look unusual while users are fine, and paging on causes is the route to alert fatigue.
The monitoring system failing on its own terms: a target too slow to answer is marked down. Worth meeting early, because a missed scrape leaves a gap in every graph and alert built on that target.
Health checks are signals too. The wrong one in the wrong place turns a slow start into a restart loop, which is a reliability problem the platform creates by itself.
The case the path builds to: every check passed and the application is broken. It shows why the measurement behind an SLO has to be what users experience, not what the platform reports.
What clients do when you fail. Retries without backoff and jitter add load to a service that is already struggling, so this belongs next to the failure it amplifies.
Last because it is where the earlier choices land on people: alerts on causes rather than symptoms show up here as a rotation nobody can sustain.
Check yourself
Take the pages your service sent in the last month. For each, say whether it fired on a symptom users felt or on a cause, and work out what share of the month's error budget the incident behind it used. Any page that used none of it is a candidate to stop paging on.
If you already have a question
Four kinds of explanation, each shaped for a different kind of question. The shape matters: a failure is a sequence, a confusion is a distinction, and flattening either into the other loses the thing that made it useful.