Objective
A VyOS router holds three configurations at once — the candidate you are editing, the active one driving the kernel, and the saved one on disk that survives a reboot — and almost every “where did my change go” incident is an operator who believed two of them were one.
By the end of this lab you will be able to read all three separately, name which command reads which, and state after any command which of the three just moved. You will finish by rebooting a router with two interfaces configured, one saved and one not, and watching exactly one of them survive.
Architecture
One VyOS router. No topology, no peer, no traffic.
management network (SSH or console)
|
eth0 <- your access path. Never touched.
+--------+
| r1 |
+--------+
dum0 dum1 <- created and destroyed by this lab
198.51.100.9/32 198.51.100.10/32
Every change in this lab lands on dummy interfaces and on the system
hostname. A dummy interface is a software-only interface with no wire
behind it: it can be created, addressed and deleted without any possibility
of affecting reachability, which makes it the right instrument for studying
the configuration model rather than the network.
The hostname is here for a different reason. It renders in your shell prompt, so a committed hostname change is the one configuration change on a VyOS box whose effect you can see without running a command at all.
Requirements
- One VyOS 1.5.x (circinus) virtual machine with at least one network interface. 512 MB RAM is plenty.
- Console access, or the ability to get it. Task 8 reboots the router.
If your only path in is SSH over
eth0and something abouteth0is fragile, fix that before you start rather than after the reboot. - Permission to reboot this router. If you cannot reboot it, this is the wrong router for this lab — Task 8 is the point of the whole exercise and reading about it is not a substitute.
- About 20 minutes of the estimate is Task 8 waiting for a boot. Plan accordingly.
Scenario
You have inherited a router. A colleague changed something on it an hour ago and has gone home. Before you make any change of your own, you need answers to three questions, and each one is a different question:
- What is running right now? — the active configuration.
- What will be running after the next reboot? — the saved configuration. If it differs from the active one, somebody committed without saving, and an unrelated power event will silently undo their work at some unpredictable future date.
- Is there a candidate sitting in configure mode that nobody committed?
— because if there is, your
commitwill apply it along with your own change, and you will own the result.
Those three questions are the standing pre-check for every VyOS change. This lab is how you learn to answer them in under a minute.
Tasks
Task 1: Read all three configurations before touching anything
Start from operational mode — the $ prompt.
The active configuration. From operational mode, show configuration
reads what is running:
$ show configurationinterfaces {
ethernet eth0 {
address dhcp
}
loopback lo {
}
}
system {
host-name vyos
login {
user vyos {
authentication {
encrypted-password "$6$..."
}
}
}
syslog {
global {
facility all {
level info
}
}
}
}Illustrative output
The same configuration in the other serialisation:
$ show configuration commandsset interfaces ethernet eth0 address 'dhcp'
set interfaces loopback lo
set system host-name 'vyos'
set system login user vyos authentication encrypted-password '$6$...'
set system syslog global facility all level 'info'Illustrative output
Two views of one thing. The tree format is what gets written to disk; the commands format is what you type, and it is the form to paste into a change ticket because a reviewer can read it line by line.
The saved configuration is a file, and you read it as a file:
mkdir -p ~/config-lab
sudo cat /config/config.boot > ~/config-lab/pre-saved.txt
show configuration commands > ~/config-lab/pre-active-commands.txt
head -n 20 ~/config-lab/pre-saved.txt
The commit archive is the third thing on disk, and it is not the same as the saved configuration:
sudo ls -1t /config/archive/ | head -n 5
sudo ls -1 /config/archive/ | wc -l
Write the count down. Task 6 checks it again.
Task 2: Answer “is running the same as saved?” honestly
This is the pre-check that matters most, and there is a widely repeated
one-liner for it that cannot work. You now have both formats in front of
you, so you can see why for yourself: /config/config.boot is the
curly-brace tree, show configuration commands emits set lines. Diffing
those two compares a tree against a list of commands, and it will report
every line of the file as different no matter how perfectly the two agree.
The comparison that does work uses the configuration engine to do the translation. Load the saved file into the candidate, and ask for a diff against the active configuration:
configure
load /config/config.boot
compare
$ compareIf compare prints nothing — or a message saying there is nothing to
commit; the exact wording varies by image, so record what yours says —
running and saved agree, and a reboot changes nothing. Anything else is
somebody’s uncommitted-to-disk work, and you want to know whose before you
build a change on top of it.
Task 3: Build a candidate, and watch show tell you something that is not true yet
Enter configure mode and make the change. The [edit] marker and the #
prompt tell you which command set you are in — this matters, because show
means a different thing on each side of that boundary.
configure
set system host-name 'r1-lab'
set interfaces dummy dum0 address '198.51.100.9/32'
set interfaces dummy dum0 description 'CONFIG-MODEL-LAB'
Now ask three questions without leaving configure mode.
What does the candidate say?
$ show interfaces dummy dum0+dummy dum0 {
+ address 198.51.100.9/32
+ description "CONFIG-MODEL-LAB"
+}Illustrative output
The interface is there, and the leading + markers are the shell telling
you this part of the candidate is not in the running configuration yet.
What does the active configuration say? The run prefix executes an
operational-mode command from inside configure mode:
$ run show configuration commands | match dum0It returns nothing at all. The active configuration has never heard of
dum0.
What does the kernel say?
$ run ip -br addr showlo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 198.51.100.61/24 fe80::5054:ff:fe3a:1101/64Illustrative output
Nothing either. And your shell prompt still says vyos, not r1-lab.
Three commands, three answers, and only one of them describes reality. Put
this in your journal as a row: after set, the candidate has the change and
nothing else does.
While you are here, look at the shape of the tree. edit descends into a
node and show becomes relative to it:
edit interfaces dummy dum0
show
up
show
top
The path you type in a set command is a walk from the root of a tree to a
leaf, and edit moves your position in that walk. This is the whole of the
configuration tree model: interfaces is a node, dummy is a node, dum0
is a node you named, and address is a leaf carrying a value.
Task 4: Produce the change artefact
Now the review surface. compare is the diff between candidate and active,
and it is the only artefact in this entire lab that a colleague, a change
board or a post-incident review can read:
$ compare[edit interfaces]
+dummy dum0 {
+ address 198.51.100.9/32
+ description "CONFIG-MODEL-LAB"
+}
[edit system]
-host-name vyos
+host-name r1-labIllustrative output
Read it as three claims: one interface is being added with two attributes, and one existing value is being replaced. A modification shows as a removal followed by an addition, because the tree stores a leaf’s value rather than its history.
Keep it. Copy the compare output into your lab journal now, before you
commit anything — which is exactly what you do in a real change window,
because the diff belongs in the ticket rather than in a file on the router
that is about to be changed. A diff stored only on the router is a diff you
lose at the moment you most need it.
Task 5: Commit, and prove exactly what moved
commit
The prompt should change from vyos@vyos# to vyos@r1-lab#. That is the
hostname change reaching the running system, and it is the fastest feedback
loop VyOS offers.
Now prove each of the three configurations again, and record the row.
Active — moved:
run show configuration commands | match dum0
Kernel — moved:
$ run ip -br addr show dum0dum0 UNKNOWN 198.51.100.9/32Illustrative output
The interface exists, is addressed, and is up. The commit engine turned
your set lines into kernel operations and executed them.
Candidate — now empty of differences:
compare
There is nothing left to compare: the candidate and the active configuration are the same tree now.
Saved — did not move. Prove it rather than believing it. Use the same technique as Task 2:
load /config/config.boot
compare
This time compare is not empty. It shows your change in reverse — because
the candidate now holds the old saved configuration, and the diff to the
running configuration is your change being undone. Read it carefully: that
output is a preview of what a reboot would do right now.
Then throw the candidate away again:
discard
compare
Task 6: Save, and find out who really writes the archive
save
$ saveSaving configuration to '/config/config.boot'...
DoneIllustrative output
Prove it moved, with the same load-and-compare technique:
load /config/config.boot
compare
discard
compare should now be empty again. Running and saved agree, and a reboot
would be a no-op. That is the state you want a router to be in at the end
of every change window.
Now the detail that surprises people. Leave configure mode and count the archive again:
sudo ls -1 /config/archive/ | wc -l
sudo ls -1t /config/archive/ | head -n 3
Compare against the count from Task 1. It went up by one — and it went up
at commit, not at save. Confirm the direction of that claim by reading
the newest file’s timestamp against the moment you committed.
Task 7: Find out what your image really does on exit with a dirty candidate
This is the one task in the lab where you are gathering a fact rather than confirming one, because the course’s own material is not consistent about it and the answer changes how you treat an interrupted change.
Build a candidate and do not commit it:
configure
set interfaces dummy dum0 description 'HALF-FINISHED'
compare
exit
Record exactly what happened, verbatim:
- If the shell refused to exit — the expected behaviour, and the reason
the command
exit discardexists at all — write down the message. It is a safety feature: the router will not let you walk away from an uncommitted change without saying explicitly that you meant to abandon it. - If it exited silently, re-enter
configureand runcompare. If your edit is still there, the candidate survived your exit and is now waiting for the next person to typecommit.
Either way, clear it deliberately. If the shell refused to exit you are
still at the # prompt and can type this directly; if it exited, run
configure first to get back in:
exit discard
Then confirm from operational mode that the running configuration still has the description you committed in Task 5, not the one you just abandoned:
show configuration commands | match dum0
Task 8: The reboot that keeps one interface and destroys the other
Everything above has been argument. This is proof.
Add a second dummy interface, commit it, and deliberately do not save:
configure
set interfaces dummy dum1 address '198.51.100.10/32'
set interfaces dummy dum1 description 'NEVER-SAVED'
compare
commit
exit
Confirm both interfaces exist right now:
$ ip -br addr show | grep dumdum0 UNKNOWN 198.51.100.9/32
dum1 UNKNOWN 198.51.100.10/32Illustrative output
Write down the prediction before you reboot. dum0 was committed and
saved in Tasks 5 and 6. dum1 was committed and never saved. State which
one you expect to find after the reboot and why.
$ rebootWhen it comes back, log in and look:
ip -br addr show | grep dum
show configuration commands | match dum
dum0 is present. dum1 is gone — configuration, address, interface, all
of it. Nothing warned you at any point, nothing logged an error, and the
router is behaving exactly as designed.
Now find the lost change in the archive, which is where it still exists:
sudo grep -l dum1 /config/archive/* | tail -n 1
That file is the record of a change that ran on this router and no longer does. In a real incident, that grep is how you reconstruct what was lost and when — and the timestamp on the file is how you show that the change predated the reboot by weeks.
Validation
Confirm each of these before you tear the lab down.
- Your journal has a row for each of
set,commit,saveanddiscard, with three columns — candidate, active, saved — and you can say for each command which columns moved. - You have the
compareoutput from Task 4 recorded, and it contains exactly three changes: one interface added with two attributes, and one hostname replaced. - In Task 3 you observed
showin configure mode reporting an interface thatrun ip -br addr showcould not see. You can state which of those two commands describes the router’s actual behaviour. - The archive count from Task 6 is exactly one higher than the count from
Task 1, and the increase is attributable to the
commit, not thesave. - You recorded verbatim what your image did on
exitwith a dirty candidate. - After the reboot,
ip -br addr showshowsdum0and notdum1, andgrep -l dum1 /config/archive/*finds at least one file. Both facts, together, are the lab’s central point: the archive remembers what the saved configuration forgot.
Expected Outcome
A router whose:
- hostname is
r1-lab, both running and saved; dum0exists with198.51.100.9/32and descriptionCONFIG-MODEL-LAB, both running and saved;dum1does not exist anywhere, having been destroyed by the reboot;- running and saved configurations agree —
load /config/config.bootfollowed bycompareprints no changes; /config/archive/contains one more entry than it did at Task 1, plus the entries from thedum1commit and the Cleanup commit below.
Nothing about eth0 has changed at any point.
Troubleshooting
commit reports that the configuration is not changed. The candidate
and the active configuration are identical — usually because you already
committed, or because a previous discard removed your edits. Run
compare to see what the candidate actually contains before typing
anything else.
set interfaces dummy dum0 address is rejected. Check the address is a
host route, 198.51.100.9/32, and that you are in configure mode. A set
typed at the $ prompt is not a configuration command at all and produces
a shell error rather than a validation error — the two look nothing alike,
and reading which one you got tells you which prompt you are at.
run is not recognised. You are in operational mode already. run is
the configure-mode prefix for reaching operational commands; from the $
prompt you type the operational command directly.
compare after load /config/config.boot shows enormous differences you
did not make. Either somebody committed a large change without saving it,
which is exactly what Task 2 is designed to detect, or the file you loaded
is not the one this router boots from. discard immediately, do not
commit, and find out which before proceeding.
The prompt did not change after committing the hostname. Some sessions
render the prompt from a cached value. Confirm with
run show configuration commands | match host-name rather than by looking
at the prompt.
After the reboot, dum0 is also gone. Your Task 6 save did not
happen or did not succeed. Check sudo ls -l /config/config.boot — if its
modification time predates your save, look for an error in the save
output. Re-apply and re-save; the archive holds the configuration you lost.
You cannot reboot the router. Then you cannot complete this lab honestly. Do not substitute reading the outcome for observing it — the entire value of Task 8 is that you watched it happen. Build a disposable VM instead.
Cleanup
Cleanup restores rather than merely deletes. dum1 is already gone; remove
dum0 and put the hostname back:
configure
delete interfaces dummy dum0
set system host-name 'vyos'
compare
commit
save
exit
Substitute the hostname from ~/config-lab/pre-active-commands.txt if it
was not vyos. Read the compare output before committing — it should
show one interface removed and one hostname replaced, and nothing else.
Confirm you are back where you started:
show configuration commands > ~/config-lab/post-active-commands.txt
diff -u ~/config-lab/pre-active-commands.txt ~/config-lab/post-active-commands.txt \
&& echo 'ACTIVE CONFIGURATION RESTORED'
That diff compares two outputs of the same command, which is why it is a
valid comparison and the config.boot one-liner in Task 2 is not.
Then confirm running and saved agree, so the router is left in the state every router should be left in:
configure
load /config/config.boot
compare
discard
exit
Empty output. Keep ~/config-lab/ — it is four small text files and it is
the evidence you did the lab.
Note what Cleanup cannot do: the archive entries this lab created are permanent until they age out of the retention window. That is correct behaviour. The archive is a log, and a log you can edit is not a log.
Production notes
The three questions from the Scenario are a real pre-check. Before any
change on any VyOS router: read the active configuration, confirm running
and saved agree with load plus compare plus discard, and confirm the
candidate is empty. It takes under a minute and it is the difference
between changing a router and changing a router plus whatever somebody
else left behind.
save is a separate decision from commit, and sometimes the right
answer is not to save. A change you are still evaluating is safer
unsaved: the reboot that undoes it is a free rollback. But an unsaved
change is only safe while somebody remembers it is unsaved. If the change
outlives your shift, either save it or revert it — do not leave it for a
power event to decide.
Commit-confirm changes this arithmetic entirely, and it is the next
lab. Everything here assumed you could always fix a bad commit because
you were sitting in front of the router. When the change is to the link
carrying your session, commit is not recoverable and the pattern you need
is commit-confirm.
The diff is the artefact. In production the compare output goes into
the ticket before the commit, not after. It is what a reviewer approves,
what an auditor reads, and what you will be grateful for when someone asks
in three months what changed that Tuesday.
What You Learned
- Three configurations, and you can read each one separately.
showin configure mode reads the candidate,show configurationin operational mode reads the active configuration, and the saved configuration is a file that you compare by loading it and diffing — not bycat-ing it against a differently formatted command output. - Each command moves exactly one arrow.
setmoves the candidate.commitmoves the active configuration and the kernel and writes the archive.savemoves the file.discardempties the candidate. You have a journal row proving each. showin configure mode is not confirmation. You watched it report an interface the kernel had never heard of. Confirmation always comes from the other side of the prompt boundary.- The archive and the saved configuration are different things that
disagree on purpose.
commitwrites the archive;savewritesconfig.boot. A change can be permanently recorded and simultaneously doomed by the next reboot — and you recovered exactly such a change from the archive with onegrep. - You proved the commit-without-save failure mode instead of reading about it. Two interfaces, one reboot, one survivor, and you predicted which one before you pressed the button.
- The candidate is shared state. Whatever your image does on
exitwith a dirty candidate,comparebeforecommitis what protects you from inheriting somebody else’s unfinished work.