VyOSLIV · API and AutomationAutomation
VyOS Ansible integration — vyos.vyos collection over network_cli, modules, idempotent playbooks
What you'll learn
- Install the vyos.vyos collection and configure the Ansible inventory for the network_cli connection
- Write a vyos_config task that declaratively configures the router
- Write a vyos_command task that retrieves operational state
- Recognise idempotency and use it to make the playbook safe to re-run
Prerequisites
Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-18
Ansible drives VyOS through the vyos.vyos collection.
The collection does not talk to the router’s HTTP API. It
opens an SSH session to the router’s CLI and drives
the same configure / set / delete / commit
sequence an operator would type by hand. The playbook is
the operator’s declarative description of the desired
router state; the transport underneath is plain SSH.
On VyOS 1.5 LTS, the vyos.vyos collection includes the
vyos_config module (declarative configuration), the
vyos_command module (operational commands), a
vyos_facts module, and a family of resource modules for
interfaces, BGP, OSPF, firewall rules, route maps and
prefix lists. The collection is the foundation for the
configuration-as-code pipeline and the change-management
workflow.
This lesson covers the collection installation, the connection variables that actually work, the two core modules, and the production patterns for idempotent playbooks.
There is no httpapi connection for VyOS
The vyos.vyos collection
The collection is published on Ansible Galaxy:
$ ansible-galaxy collection install vyos.vyosWhat has to be true for it to work:
- On the control node — a supported
ansible-core. The collection’s README records testing againstansible-core2.15 and later. The collection depends onansible.netcommon, whichansible-galaxypulls in automatically. The VyOS documentation additionally calls for the Paramiko Python module, packaged on Debian and Ubuntu aspython3-paramiko;network_cliuses it as its default SSH implementation. - On the router — the SSH service enabled, and a login
account Ansible can authenticate as. Nothing is
installed on the router:
network_clidrives the CLI, so no Python interpreter and no agent is needed on the VyOS side.
The collection’s modules record testing against VyOS 1.3.8, 1.4.2, 1.5, and the spring-2025 rolling release.
flowchart LR
PB["Playbook task<br/>vyos_config or vyos_command"] --> CP["cliconf plugin<br/>vyos.vyos.vyos"]
CP --> NC["Connection plugin<br/>ansible.netcommon.network_cli"]
NC -->|"SSH port 22"| SSHD["VyOS sshd"]
SSHD --> CLI["Interactive VyOS CLI session"]
CLI --> CMD["configure then set and delete then commit"]
The module never touches the router directly. It hands the
commands to the cliconf plugin, the cliconf plugin knows
how to enter configuration mode and how to read the VyOS
prompt back, and network_cli carries the bytes over SSH.
Every command in the playbook is a command that would work
if you typed it at the router’s prompt.
The router side
The router needs SSH reachable from the control node and an account for the automation to use. Public-key authentication is the production choice:
configure
set service ssh port '22'
set system login user automation authentication public-keys ansible-control type 'ssh-ed25519'
set system login user automation authentication public-keys ansible-control key 'AAAAC3NzaC1lZDI1NTE5AAAAI...'
commit
save
exit
The ansible-control string is an arbitrary identifier
for the key, not a filename; VyOS uses it to let you hold
several keys for the same user and delete them
individually. The key value is the base64 body of the
public key on its own: the ssh-ed25519 prefix goes in
the type node and the trailing comment is dropped.
Inventory and credentials
The inventory maps hostname to connection variables:
# inventory/hosts.yml
all:
vars:
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: vyos.vyos.vyos
ansible_user: automation
ansible_ssh_private_key_file: ~/.ssh/vyos_automation
children:
edge:
hosts:
edge-01:
ansible_host: 10.99.0.1
edge-02:
ansible_host: 10.99.0.2
The variables specify:
- Connection —
ansible.netcommon.network_cliopens a persistent SSH session to the router and drives the CLI through it. - Network OS —
vyos.vyos.vyosselects the VyOS cliconf and terminal plugins, sonetwork_cliknows what the prompt looks like and how to enter configuration mode. - Credentials —
ansible_useris the VyOS login name.ansible_ssh_private_key_filepoints at the matching private key. If you authenticate with a password instead of a key, setansible_passwordand source it from Ansible Vault:
ansible_user: automation
ansible_password: "{{ vault_vyos_password }}"
If the routers sit behind a bastion, network_cli takes
the jump host through the Paramiko proxy variable
documented on the platform-options page:
ansible_paramiko_proxy_command: '-o ProxyCommand="ssh -W %h:%p -q bastion01"'
That page also warns that passwords cannot be passed
through ProxyCommand, precisely so a secret does not end
up on a process command line. Use a key for the bastion
hop.
The defensive idiom: credentials live in Ansible Vault or in an SSH key that never leaves the control node, not in plaintext in the inventory file. The inventory file is committed to version control; the vault file and the private key are not.
vyos_config — declarative configuration
The vyos_config module applies a set of configuration
commands to the router and commits them. Its lines
argument takes the literal VyOS set and delete
commands:
# playbook.yml
- name: Configure BGP on edge routers
hosts: edge
gather_facts: false
tasks:
- name: Configure BGP ASN and neighbour
vyos.vyos.vyos_config:
lines:
- set protocols bgp system-as 64512
- set protocols bgp parameters router-id 10.99.0.1
- set protocols bgp neighbor 10.0.0.1 remote-as 65001
- set protocols bgp neighbor 10.0.0.1 password '{{ vault_bgp_password }}'
comment: 'ansible: bgp baseline'
backup: true
The options that matter in production, with their documented defaults:
lines— the ordered set of commands. They must be the exact commands as they appear in the device’s running configuration, which is what makes the comparison in the next section work.src— a path to a source config file instead of inline lines. The file may be in bracket format or set format and may contain Jinja2 template variables.match—lineby default, meaning the desired config is compared against the active config and only the deltas are loaded.noneignores the active configuration and always loads every line.backup—falseby default. Whentrue, the module copies the device’s active configuration to the Ansible control host before making any change, into abackupdirectory beside the playbook unlessbackup_optionssays otherwise.comment— the commit description, defaulting toconfigured by vyos_config. It shows up in the router’s commit log, which is the difference between an audit trail that names the change and one that does not.save—falseby default. Saving is independent of committing: the module commits whatever changes it sends, andsave: trueadditionally writes the active configuration to disk so it survives a reboot. A playbook that commits without saving leaves a router that reverts on its next reload.confirmandconfirm_timeout— covered below.allow_password_change—plaintextby default, which permits plaintext password changes and filters outencrypted-passwordkeys. Set it toall,encryptedornoneto change which password lines survive.
The src form is what the configuration-as-code pipeline
uses:
- name: Render a Jinja2 template onto the VyOS router
vyos.vyos.vyos_config:
src: vyos_template.j2
vyos_command — operational commands
The vyos_command module runs operational commands and
returns their output. It is read-only: it does not enter
configuration mode and does not modify the router.
- name: Retrieve BGP summary
vyos.vyos.vyos_command:
commands:
- show ip bgp summary
register: bgp_summary
- name: Print BGP summary
ansible.builtin.debug:
var: bgp_summary.stdout_lines
The module returns stdout (the set of responses, one per
command), stdout_lines (each response split into a
list), failed_conditions and warnings.
The module also polls, which is what makes it useful for validating a change that takes time to converge:
wait_for— conditionals evaluated against the output.match—allby default;anypasses when a single conditional is satisfied.retries— how many attempts before the command is considered failed. Default9.interval— seconds between retries. Default1.
- name: Wait for the BGP session to reach Established
vyos.vyos.vyos_command:
commands:
- show ip bgp summary
wait_for:
- "result[0] contains Established"
retries: 30
interval: 5
That task retries for up to 150 seconds and fails the play if the session never comes up, which is exactly the post-change gate a production playbook needs.
Commit-confirm from a playbook
The most dangerous change is one that severs the SSH session Ansible is using — a firewall rule, an interface address, a routing change on the management path. The module has the VyOS commit-confirm mechanism built in:
- name: Push the firewall baseline, revert if we lose the session
vyos.vyos.vyos_config:
src: firewall.j2
confirm: automatic
confirm_timeout: 10
confirm takes automatic, manual, or none, and
defaults to none. With automatic, the module confirms
the configuration itself, but only if the current session
is still working with the new config; if the change locked
the session out, the confirmation never arrives and the
router reverts. With manual, the module leaves the
confirmation to you. confirm_timeout is the number of
minutes the router waits before reverting, defaulting to
10.
This is the single most valuable option in the module for remote work, and it is off by default.
Idempotency — the production safety net
Idempotency is the property that running an operation
several times has the same effect as running it once. On
VyOS the underlying commands help: set protocols ospf area 0 network 10.0.0.0/24 is a tree assignment, not an
append, so re-issuing it at the CLI does not create a
second entry. What repetition costs you is a playbook that
can never tell you whether anything changed.
# Reports changed: true on every run
- name: Configure an interface description
vyos.vyos.vyos_config:
lines:
- set int eth eth2 description 'OUTSIDE'
# Reports changed: false once the router already matches
- name: Configure an interface description
vyos.vyos.vyos_config:
lines:
- set interfaces ethernet eth2 description 'OUTSIDE'
Both tasks leave the router in the same state. Only the
second one converges: after the first successful run it
reports changed: false, it stops entering configuration
mode, and it stops writing a commit into the router’s
commit log. The first one commits a no-op every run, which
buries the one commit that mattered under a hundred that
did not, and makes changed: true worthless as a signal.
The test is mechanical and takes thirty seconds: run the
playbook twice. If the second run is not changed: false
for every task, the playbook is not idempotent yet.
Failure modes
SSH authentication fails
The control node cannot log in — wrong user, key not installed on the router, or the router’s host key changed and the control node refuses it.
Diagnostic:
fatal: [edge-01]: UNREACHABLE! => {"changed": false, "msg": "<the SSH error, verbatim from the transport>", "unreachable": true}
Fix: verify the login by hand first —
ssh automation@10.99.0.1 from the control node. An
Ansible connection problem is almost always an SSH problem
you can reproduce without Ansible. If the router was
rebuilt, its host key changed; refresh the control node’s
known_hosts deliberately rather than disabling host-key
checking globally.
Commit fails
The module applies the configuration and commits. If the commit fails — the VyOS validator catches a typo, or a referenced object does not exist — the module reports an error, and the router’s active configuration is unchanged.
Diagnostic:
TASK [Configure BGP ASN and neighbour] ****************
fatal: [edge-01]: FAILED! => {"changed": false, "msg": "<the commit error the router printed>"}
Fix: correct the configuration and re-run. Because the commit is atomic, there is no half-applied state to clean up.
Every run reports changed
The playbook is green but never converges: run 1, run 2
and run 50 all report changed: true on the same task.
Diagnostic: run with --diff and read what the module
proposes to send. If it is proposing lines the router
already has, the lines are not in the router’s own text
form.
Fix: run show configuration commands | match ethernet on
the router, copy the line the router prints, and use that
text in the playbook. Check that the task is not using
match: none.
The change locks the playbook out
A firewall or interface change severs the SSH session the module is running over. The task hangs, then fails, and the router is left holding a configuration nobody can reach.
Diagnostic: the task times out; the router is unreachable afterwards.
Fix: confirm: automatic with a confirm_timeout on
every task that touches the management path. The router
reverts on its own when the confirmation does not arrive.
Recovery without it means console access.
Inventory mismatch
The inventory lists edge-01 at 10.99.0.1, but the
router is at 10.99.0.10.
Diagnostic:
fatal: [edge-01]: UNREACHABLE! => {"changed": false, "msg": "<connection timed out to 10.99.0.1>", "unreachable": true}
Fix: correct the inventory. The defensive idiom: use DNS names in the inventory, not IP addresses, so the address of record lives in one place.
Rollback
An Ansible-driven change is reversible through the standard VyOS mechanisms and through the module itself:
- Before the change —
backup: truewrites the router’s pre-change active configuration to the control node. That file is the input to the rollback. - During the change —
confirm: automaticwithconfirm_timeoutmakes the router revert by itself if the session dies. - After the change —
rollback Nthencommiton the router reverts to a previous configuration revision, the same as any hand-made change.
The VyOS commit validator applies to a rollback configuration exactly as it applies to a forward change, so a rollback playbook fails loudly rather than half- applying.
Production discipline
Cross-course references
LIV-VyOS-Automation(vyos-liv-01-vyos-http-api, the previous lesson) covers the VyOS HTTP API. It is a parallel automation surface for tools you write yourself; thevyos.vyoscollection does not use it.LIV-VyOS-Automation(vyos-liv-03-config-as-code) covers the pipeline that renders the templatesvyos_config’ssrcargument consumes.LIV-VyOS-Automation(vyos-liv-04-automated-validation) covers the validation patterns that complement these playbooks.VI-VyOS-CommitRollback(vyos-vi-02-commit-confirm) covers the commit-confirm mechanism that the module’sconfirmoption drives.- The Ansible course’s
IV-Ansible-Inventorycovers inventory structure, andXII-Ansible-Idempotencycovers the idempotency model thechangedflag reports against.
Quiz
Knowledge check · 4 questions
Q1. Which Ansible connection does the vyos.vyos collection use to reach a VyOS router?
Q2. By default `vyos_config` compares the lines you supply against the router's active configuration and sends only the difference; `match: none` turns that comparison off and sends every line on every run.
Q3. A playbook task sets an interface description with `vyos.vyos.vyos_config` and `lines: - set int eth eth2 description 'OUTSIDE'`. The router has the description configured correctly, yet every scheduled run reports `changed: true` and writes another commit into the router's commit log. The team has stopped trusting the `changed` flag. What is happening and how do you fix it?
A nightly Ansible run against two VyOS edge routers. One vyos_config task reports changed: true on every run for months. `show configuration commands | match eth2` on the router shows the description is already correct.
Q4. An operator pushes a firewall baseline to a remote VyOS router with `vyos_config` and `src: firewall.j2`. The rendered rules do not permit SSH from the automation network. The task hangs and then fails, and the router is now unreachable from both the control node and the operator's laptop. What should have been in the task, and what are the options now?
A remote VyOS edge router, no console server, reachable only over SSH from the automation network. An Ansible firewall push has just cut that path.
Passing score: 75%. Answers are checked in this browser.