Automation with Ansible
Changing one router by hand is fine. Changing fifty the same way, and proving they're all the same, needs automation. Ansible connects to each router over SSH and uses modules that understand VyOS: the vyos.vyos collection.
- The inventory lists the routers and how to reach them (network_cli over SSH, as the
vyosuser). vyos_factsgathers what the router is: hostname, version and more. Useful for audits and for templates.vyos_configapplies configuration lines and only reportschangedif something actually changed.
The banner playbook shows the workflow:
--check --diff(a dry run) shows exactly what would change, without touching the router.--diffapplies it and shows the diff.- Running it again reports
changed=0: the router already matches, so nothing is done. That's idempotence, and it's what makes it safe to run a playbook against every router on a schedule.
Two notes from the lab:
- The output contains stray
[?2004lstrings, including inside the gathered hostname. That's the router shell's bracketed-paste mode leaking through thevyos.vyos4.1.0 collection packaged with Debian 12, which is older than this VyOS build. Use a current collection from Ansible Galaxy, and test it against your VyOS version. - The lab logs in with the default
vyospassword and turns off host key checking. In production use SSH keys, keep secrets in Ansible Vault, and keep host key checking on.
[routers]
r1 ansible_host=198.51.100.1
[routers:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_network_os=vyos.vyos.vyos
ansible_user=vyos
ansible_password=vyos- Line 2: One router.
- Lines 5–6: SSH, with VyOS-aware modules.
- Lines 7–8: Lab credentials only: use keys and Vault in production.
- name: Standard login banner on every router
hosts: routers
gather_facts: false
tasks:
- name: Set the banner
vyos.vyos.vyos_config:
lines:
- set system login banner pre-login 'Authorised access only. Managed by Ansible.'- Lines 5–7: vyos_config: apply lines, report only real changes.
root@ops:/ansible# ansible-playbook facts.yml
PLAY [What is this router?] ****************************************************
TASK [Gather facts] ************************************************************
[WARNING]: ansible-pylibssh not installed, falling back to paramiko
ok: [r1]
TASK [Show the interesting ones] ***********************************************
ok: [r1] => {
"msg": "\u001b[?2004l\n\nR1 runs VyOS 2026.10.16-0024-rolling (serial Hardware)"
}
PLAY RECAP *********************************************************************
r1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
root@ops:/ansible# ansible-playbook banner.yml --check --diff
PLAY [Standard login banner on every router] ***********************************
TASK [Set the banner] **********************************************************
[WARNING]: ansible-pylibssh not installed, falling back to paramiko
[WARNING]: To ensure idempotency and correct diff the input configuration lines
should be similar to how they appear if present in the running configuration on
device
[?2004l
[system login]
+ banner {
+ pre-login "Authorised access only. Managed by Ansible."
+ }
[?2004h[edit]
changed: [r1]
PLAY RECAP *********************************************************************
r1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
root@ops:/ansible# ansible-playbook banner.yml --diff
PLAY [Standard login banner on every router] ***********************************
TASK [Set the banner] **********************************************************
[WARNING]: ansible-pylibssh not installed, falling back to paramiko
[WARNING]: To ensure idempotency and correct diff the input configuration lines
should be similar to how they appear if present in the running configuration on
device
[?2004l
[system login]
+ banner {
+ pre-login "Authorised access only. Managed by Ansible."
+ }
[?2004h[edit]
changed: [r1]
PLAY RECAP *********************************************************************
r1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
root@ops:/ansible# ansible-playbook banner.yml
PLAY [Standard login banner on every router] ***********************************
TASK [Set the banner] **********************************************************
[WARNING]: ansible-pylibssh not installed, falling back to paramiko
ok: [r1]
PLAY RECAP *********************************************************************
r1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0- Line 11: Facts gathered (with a stray escape code from the old collection).
- Line 17: Dry run: what would change…
- Lines 29–31: …shown as a diff…
- Line 38: …but nothing changed yet.
- Lines 40–61: The real run.
- Line 63: Run again…
- Line 72: …changed=0: idempotent.
Check your understanding
Why does running the same playbook again report changed=0?
- vyos_config compares the desired lines with the running config and does nothing if they match
- The second run was a dry run
- Ansible caches the result
- The router rejected the change