How to write a tutorial someone else can reproduce

Most broken tutorials were not wrong on the day they were written. They worked on the author’s laptop, with the author’s shell history, the author’s cached packages and a dozen small decisions the author made years ago and no longer remembers. The reader has none of that. A reproducible tutorial is one that still works when every one of those invisible advantages is taken away.
This is the checklist we use when we read a tutorial draft. None of it is complicated. It is mostly about writing down things you stopped noticing a long time ago.
Start from a machine that is not yours
The single most useful habit is to run the whole tutorial, from the first command to the last, on a system that has never seen your project. Your own machine lies to you: a library you installed for something else last year will quietly satisfy a dependency that your reader does not have.
A throwaway container is the cheapest clean machine there is. If your tutorial targets Debian or Ubuntu, start one like this:
docker run --rm -it debian:bookworm bashThe --rm flag deletes the container when you exit, so every run starts from the same empty state. If the tutorial assumes a language runtime, use an image that already has it, for example node:22-bookworm or python:3.12-bookworm, and say in the post that this is what you tested on.
Inside that container, the first thing you will usually discover is that even basic tools are missing. A minimal Debian image does not include curl or git. That is not a nuisance; it is information. If your reader might be in the same position, the tutorial needs a line like this near the top:
apt-get update && apt-get install -y curl gitContainers are not the only option. A fresh user account, a new virtual machine or a colleague’s laptop all work. What matters is that nothing from your own setup leaks in.
Write down every version
“Install Node” is not an instruction anyone can reproduce, because it does not say which Node. Six months from now the answer to “which one did you mean?” will matter, and you will not remember it.
Before you write the first step, record the versions of everything the tutorial touches, and put them in the post. Most tools will tell you:
uname -sm
node --version
npm --version
python3 --version
git --versionRun only the lines that apply to your tutorial. Then turn the results into a short “Tested with” line near the top of the post: the operating system, the runtime and the main libraries, each with its exact version. Readers on a different setup can then see at a glance how far they are from what you tested, and a reader who hits a problem can tell you something more useful than “it doesn’t work”.
That line also becomes the starting point when you revise the post later. We wrote about that separately in Updating a technical post without breaking trust.
Pin what you install
A command that installs “the latest” version of something will install a different thing every few months. Sometimes the difference is harmless. Sometimes a function was renamed and step seven fails with an error the reader cannot connect to anything in your post.
Pin versions in the commands themselves. With npm:
npm install --save-exact express@4.21.2The --save-exact flag writes 4.21.2 into package.json instead of a range such as ^4.21.2. If you publish a sample repository alongside the post, commit package-lock.json and tell readers to run npm ci, which installs exactly what the lockfile lists and stops with an error if the lockfile and package.json disagree.
With Python, create a virtual environment so nothing depends on the system interpreter, and pin inside it:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install "requests==2.32.3"
python -m pip freeze > requirements.txtThe quotes around requests==2.32.3 are not required in bash, but they keep the line safe if a reader later changes it to a range such as requests>=2.32, where an unquoted > would be read by the shell as a redirection.
Pinning does not mean you should never upgrade. It means the post describes one specific, working combination, and upgrading becomes a deliberate change instead of an accident.
Show the expected result after each step
A reader following a twenty-step tutorial needs to know, after every step, whether they are still on track. If you only show the result at the end, a mistake at step three surfaces as a confusing failure at step eighteen.
After any command that prints something meaningful, show what it should print. Keep the output in its own block, separate from the command, so copying the command never drags the output along with it:
python3 --versionPython 3.11.2That is what Debian 12 prints, for example. When output varies between machines, such as timestamps, paths or process IDs, say which parts will differ. When a step prints nothing on success, say that too: “No output means it worked” saves readers from wondering whether they missed something. How to format these blocks so they copy cleanly is the subject of Code blocks readers can copy and paste safely.
Do a full run-through before you publish
Once the draft is finished, start a new clean container and follow the post exactly as a reader would. Copy each command from the draft, not from your terminal history. Do not fix anything from memory. Every time you have to type something that is not in the post, or change a command to make it work, write it down.
That list is the most valuable thing you will produce all day. Typical entries look like this:
- A directory had to exist before a file could be written into it.
- An environment variable was set in your shell profile and never mentioned.
- A command needed
sudoon a normal system but ran as root in the container. - A port was already in use, or a service had to be started first.
Fix the post, then run it again from scratch. Two clean passes in a row is a good bar. When a step deletes files, changes system settings or needs elevated privileges, say so plainly before the command, as the publishing guidelines ask.
Say what you left out
No tutorial covers every platform, and it does not need to. What it needs is to be honest about its edges. If you tested on Linux and not on macOS or Windows, write that. If you skipped production concerns such as TLS, backups or authentication to keep the post focused, say so in a sentence and, where you can, point to where those topics are covered properly.
Readers forgive a narrow scope. What they do not forgive is discovering the scope on their own, three hours in. A short “What this post does not cover” paragraph near the start costs you a minute and saves them the afternoon.
On TechDug, code blocks keep their indentation and long posts get a table of contents, so the structure above maps directly onto the page; the features page has the details. The structure, the versions and the clean-machine test are still up to you.




Comments 0
Comments are reviewed before they appear. Oldest first.
No comments yet. Spotted a typo, a better flag, or a step that failed for you? Say so below.