Skip to main content
RunBook Academy

← All labs in VyOS

Lab · intermediate · ~70 min

Lab: Configure, Compare, Commit, Save

B · Nested virtualisationC · Simulation

Objectives

  • Read the candidate, the active and the saved configuration separately and state which command reads which
  • Produce a reviewable diff with compare, and keep it as the change artefact
  • Demonstrate that commit moves the active configuration and the kernel, and does not touch the saved configuration
  • Demonstrate that save writes the file and that the commit archive is written by commit, not by save
  • Establish empirically what your image does when you exit configure mode with an uncommitted candidate
  • Prove the commit-without-save failure mode with a reboot that keeps one interface and loses another

Prerequisites

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 eth0 and something about eth0 is 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:

  1. What is running right now? — the active configuration.
  2. 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.
  3. Is there a candidate sitting in configure mode that nobody committed? — because if there is, your commit will 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:

Read-only / Safer1 (operational mode)
$ show configuration
interfaces {
  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:

Read-only / Safer1 (operational mode)
$ show configuration commands
set 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
Read-only / Safer1 (configure mode)
$ compare

If 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?

Read-only / Safer1 (configure mode)
$ 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:

Read-only / Safer1 (configure mode)
$ run show configuration commands | match dum0

It returns nothing at all. The active configuration has never heard of dum0.

What does the kernel say?

Read-only / Safer1 (configure mode)
$ run ip -br addr show
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             198.51.100.61/24 fe80::5054:ff:fe3a:1101/64

Illustrative 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:

Read-only / Safer1 (configure mode)
$ compare
[edit interfaces]
+dummy dum0 {
+    address 198.51.100.9/32
+    description "CONFIG-MODEL-LAB"
+}
[edit system]
-host-name vyos
+host-name r1-lab

Illustrative 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:

Read-only / Safer1 (configure mode)
$ run ip -br addr show dum0
dum0             UNKNOWN        198.51.100.9/32

Illustrative 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
Configuration changer1 (configure mode)
$ save
Saving configuration to '/config/config.boot'...
Done

Illustrative 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 discard exists 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 configure and run compare. If your edit is still there, the candidate survived your exit and is now waiting for the next person to type commit.

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:

Read-only / Safer1 (operational mode)
$ ip -br addr show | grep dum
dum0             UNKNOWN        198.51.100.9/32
dum1             UNKNOWN        198.51.100.10/32

Illustrative 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.

Service impact possibler1 (operational mode)
$ reboot

When 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, save and discard, with three columns — candidate, active, saved — and you can say for each command which columns moved.
  • You have the compare output 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 show in configure mode reporting an interface that run ip -br addr show could 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 the save.
  • You recorded verbatim what your image did on exit with a dirty candidate.
  • After the reboot, ip -br addr show shows dum0 and not dum1, and grep -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;
  • dum0 exists with 198.51.100.9/32 and description CONFIG-MODEL-LAB, both running and saved;
  • dum1 does not exist anywhere, having been destroyed by the reboot;
  • running and saved configurations agree — load /config/config.boot followed by compare prints no changes;
  • /config/archive/ contains one more entry than it did at Task 1, plus the entries from the dum1 commit 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. show in configure mode reads the candidate, show configuration in operational mode reads the active configuration, and the saved configuration is a file that you compare by loading it and diffing — not by cat-ing it against a differently formatted command output.
  • Each command moves exactly one arrow. set moves the candidate. commit moves the active configuration and the kernel and writes the archive. save moves the file. discard empties the candidate. You have a journal row proving each.
  • show in 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. commit writes the archive; save writes config.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 one grep.
  • 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 exit with a dirty candidate, compare before commit is what protects you from inheriting somebody else’s unfinished work.

Deliverables

  • · A change artefact: the compare output for the change, captured before the commit that applied it
  • · A three-column journal recording candidate, active and saved after each of set, commit, save and discard
  • · Evidence that the commit archive gained an entry at commit time and none at save time
  • · The post-reboot evidence that one interface survived and the other did not, with the reason

Verification status

Last reviewed
2026-08-19
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.