Writing a migration guide readers can trust

A reader opens a migration guide at a bad moment. An upgrade is waiting, a nightly job or real data depends on it, and they cannot tell by looking whether your steps are right. What they can judge is whether the guide behaves as if its author has done this: does it say who it is for, how to check each step, and how to get back?
In our changelog post we said that a migration longer than a few lines deserves its own guide. This is how we would write one. The example continues with tidepool, the fictional command-line tool from that post, moving from 2.4.1 to 3.0.0. Tidepool does not exist, so its behaviour is invented for the example. The shell commands below are real: we ran them against sample files on macOS 26 (bash 3.2) and in a Debian 13 container (bash 5.2), and the outputs shown are from Debian.
Open with the exact journey
Before step one, readers need to know whether this page describes their situation. Put that at the very top, as ordinary text:
Moving from: tidepool 2.4.x. On anything older, upgrade to 2.4.1 first.
Moving to: tidepool 3.0.0
You need: Python 3.10 or newer (check with: python3 --version)
Pause first: any scheduled job that runs tidepool
Tested from: a fresh 2.4.1 install on Debian 13, 3 November 2026The first line carries the most weight. A guide tested from 2.4.1 says nothing about someone jumping from 2.1, and “should work” is not a tested path. If you have not tried a jump, say so and send those readers through a version you have tried.
Follow the header with the changes in a sentence or two each, and why they happened. In our case: the config file moves, --force gives way to --overwrite, and the data folder gets a new format. A reader who knows why can adapt the steps when their setup differs from yours.
Let readers check whether they are affected
A list of breaking changes makes people read and guess. A command gives them an answer. Tell them to replace ~/scripts with wherever they keep their scripts:
test -f ~/.tidepoolrc && echo "old config found"
grep -rn 'tidepool sync.*--force' ~/scripts
crontab -l 2>/dev/null | grep -n 'tidepool sync.*--force'old config found
/home/you/scripts/nightly.sh:3:tidepool sync --force ./dataThen say what the result means, including the empty case: no output from any of the three means nothing here needs changing.
Keep the pattern narrow. Searching for --force alone also matches rsync --force in an unrelated script, and the reader starts editing lines that were never broken. Also be open about what the check misses. This one cannot see a flag built up in a shell variable, or a job defined in a CI config outside that folder, and a sentence saying so is worth more than a check that implies certainty.
Back up, then prove the backup works
Name exactly what to save. Tidepool 2.x keeps its config in one file and its data in one folder:
tar -czf ~/tidepool-before-3.0.tar.gz -C ~ .tidepoolrc .local/share/tidepoolMany guides stop there, and a backup nobody has restored is only a hope. Have the reader unpack it somewhere else and compare, while the originals still exist:
restore=$(mktemp -d)
tar -xzf ~/tidepool-before-3.0.tar.gz -C "$restore"
diff -r "$restore/.local/share/tidepool" ~/.local/share/tidepool && echo "data matches"
diff "$restore/.tidepoolrc" ~/.tidepoolrc && echo "config matches"data matches
config matchesIf your tool has a running service, tell readers to stop it first, since files being written during the backup can be captured half-finished. If it has its own export or dump command, use that instead and explain why.
One change, one check, in an order you can defend
Put reversible steps first and leave deletions for last. This is why the config is copied, not moved. Its format has not changed, 2.4.1 still needs the old file, and the reader may yet need 2.4.1.
mkdir -p ~/.config/tidepool
cp ~/.tidepoolrc ~/.config/tidepool/config.toml
cmp ~/.tidepoolrc ~/.config/tidepool/config.toml && echo "config copied"config copiedEvery step should end like this, with something the reader can compare. Next, the script:
sed -i.bak 's/tidepool sync --force/tidepool sync --overwrite --yes/' ~/scripts/nightly.sh
diff ~/scripts/nightly.sh.bak ~/scripts/nightly.sh3c3
< tidepool sync --force ./data
---
> tidepool sync --overwrite --yes ./dataThree details deserve a sentence each in the guide. The --yes matters because --overwrite asks for confirmation, and nobody is awake to answer a nightly job. The form -i.bak works in both GNU and BSD sed, where a bare -i does not, and it leaves an undo file behind. And the rewrite only matches that exact spelling, so tidepool sync --verbose --force survives it. Repeat the search to catch those, skipping the backup copy:
grep -rn --exclude='*.bak' 'tidepool sync.*--force' ~/scriptsNo output means the scripts are done.
Describe the state between steps as well. The edited script will not run on 2.4.1, which is why the header says to pause scheduled jobs. Then mark the step that cannot be undone, in words that stop someone skimming:
Point of no return. The first run of 3.0.0 converts your data folder, and 2.4.1 cannot read the result. Going back after this means restoring the backup and losing anything synced since.
Our publishing guidelines ask authors to flag anything that deletes data or alters system settings. A migration guide is where that flag does the most work.
Write the way back before anyone needs it
Rollback belongs in the guide as a tested procedure. Say first what it costs: these commands delete the new config and the converted data, so anything written since the upgrade is lost.
rm -rf ~/.config/tidepool ~/.local/share/tidepool
tar -xzf ~/tidepool-before-3.0.tar.gz -C ~
mv ~/scripts/nightly.sh.bak ~/scripts/nightly.sh
tail -n 1 ~/scripts/nightly.shtidepool sync --force ./dataAfter that comes the pinned install of the old version. Also state when rollback stops being safe, for example once other machines have synced with the new format. If there is truly no way back, say that plainly near the top, because it changes whether the reader starts today.
Cleanup goes at the very end and is optional: remove ~/.tidepoolrc, the .bak file and the archive only after the new version has run long enough for the reader to trust it. Removing them earlier takes the rollback away.
Test it from the old version, then keep testing
The classic failure is a guide checked on a machine that was already upgraded, or on a fresh install of the new version. Neither has anything to migrate. Start from a clean machine, install the old version, and use it the way a user would: write a config, create some data, schedule a script with the old flag. Then follow your draft, copying each command from the page.
While you are there, collect three more things:
- The errors people hit when they skip a step. Quote them exactly in the guide, with the fix beside them, because a stuck reader searches for that text.
- A rollback run. Afterwards, the check from the second section should report the reader as affected again. If it does not, the rollback is incomplete.
- What you did not test. Other operating systems, older versions and large data sets get one honest line each.
Publish the guide with the release and link to it from the changelog entry, so people find it before they upgrade. On TechDug, top-level sections are numbered and long posts get a table of contents, as the features page describes, so a guide built from one section per stage already reads as a procedure. When a reader reports a step that failed, fix the guide and update its “tested from” line, since that line is the reason anyone believed it.




Comments 0
Moderated before publication. Sorted oldest first.
No comments yet. Spotted a typo, a better flag, or a step that failed for you? Say so below.