diff --git a/ansible/physical-network.yml b/ansible/physical-network.yml
index ec038faa139f73add5c98b4af2ff49fe2b1787b0..4c65abb0e7b3b15f4c666b53b4db5081a6106126 100644
--- a/ansible/physical-network.yml
+++ b/ansible/physical-network.yml
@@ -30,6 +30,7 @@
       - dell-powerconnect
       - junos
       - mellanox
+      - nclu
       - openvswitch
   tasks:
     - name: Fail if both interface name and description limits are specified
@@ -155,3 +156,13 @@
       mellanox_switch_provider: "{{ switch_mellanox_provider }}"
       mellanox_switch_config: "{{ switch_config }}"
       mellanox_switch_interface_config: "{{ switch_interface_config }}"
+
+- name: Ensure Cumulus physical switches are configured with NCLU
+  hosts: switches_of_type_nclu:&switches_in_display_mode_False
+  gather_facts: no
+  roles:
+    - role: ssh-known-host
+
+    - role: nclu-switch
+      nclu_switch_config: "{{ switch_config }}"
+      nclu_switch_interface_config: "{{ switch_interface_config }}"
diff --git a/ansible/roles/nclu-switch/README.md b/ansible/roles/nclu-switch/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0a6c9086f4c31872788f66f8aa33a03676e5dede
--- /dev/null
+++ b/ansible/roles/nclu-switch/README.md
@@ -0,0 +1,75 @@
+NCLU Switch
+===========
+
+This role configures Cumulus switches using the `nclu` Ansible module. It
+provides a fairly minimal abstraction of the configuration interface provided
+by the `nclu` module, allowing for application of arbitrary switch
+configuration options.
+
+Requirements
+------------
+
+The switches should be configured to allow SSH access.
+
+Role Variables
+--------------
+
+`nclu_switch_config` is a list of NCLU commands to apply to the switch, and
+defaults to an empty list. Commands must be formatted without the `net` prefix,
+which is added by the `nclu` module before execution on the switch.
+
+`nclu_switch_interface_config` contains interface configuration. It is a dict
+mapping switch interface names to configuration dicts. Interfaces can be switch
+physical interfaces, but also special interfaces such as bridges or bonds. Each
+dict may contain the following items:
+
+- `description` - a description to apply to the interface.
+- `config` - a list of per-interface configuration, each applied with a `net
+  add <type> <interface-name>` prefix.
+- `type` - type of interface, e.g. `bond` or `bridge`. If this field is absent,
+  the `interface` keyword is used.
+
+Dependencies
+------------
+
+None
+
+Example Playbook
+----------------
+
+The following playbook configures hosts in the `nclu-switches` group. It
+applies global configuration to configure a BGP AS and add two EBGP neighbors
+using BGP Unnumbered, enables two host interfaces with jumbo frames, and
+attaches them to a traditional bridge called `bridge1` configured with an IP
+address.
+
+    ---
+    - name: Ensure Cumulus switches are configured with NCLU
+      hosts: nclu-switches
+      gather_facts: no
+      roles:
+        - role: nclu-switch
+          nclu_switch_config:
+            - "add bgp autonomous-system 65000"
+            - "add bgp neighbor swp51 interface remote-as external"
+            - "add bgp neighbor swp52 interface remote-as external"
+          nclu_switch_interface_config:
+            swp1:
+              description: server1
+              config:
+                - "mtu 9000"
+            swp2:
+              description: server2
+              config:
+                - "mtu 9000"
+            bridge1:
+              type: bridge
+              config:
+                - "ip address 10.100.100.1/24"
+                - "ports swp1"
+                - "ports swp2"
+
+Author Information
+------------------
+
+- Pierre Riteau (<pierre@stackhpc.com>)
diff --git a/ansible/roles/nclu-switch/defaults/main.yml b/ansible/roles/nclu-switch/defaults/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d826d8aff5d01f0f8548e580b246c7ed10367e43
--- /dev/null
+++ b/ansible/roles/nclu-switch/defaults/main.yml
@@ -0,0 +1,9 @@
+---
+# List of configuration lines to apply to the switch.
+nclu_switch_config: []
+
+# Interface configuration. Dict mapping switch interface names to configuration
+# dicts. Each dict contains a 'description' item, an optional 'type' item
+# (default is 'interface'), and a 'config' item which should contain a list of
+# per-interface configuration.
+nclu_switch_interface_config: {}
diff --git a/ansible/roles/nclu-switch/tasks/main.yml b/ansible/roles/nclu-switch/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6c336f39f865df535d2d6ce577e1fb015c47af36
--- /dev/null
+++ b/ansible/roles/nclu-switch/tasks/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Ensure Cumulus switches are configured with NCLU
+  nclu:
+    template: "{{ lookup('template', 'nclu-config.j2') }}"
+    atomic: true
diff --git a/ansible/roles/nclu-switch/templates/nclu-config.j2 b/ansible/roles/nclu-switch/templates/nclu-config.j2
new file mode 100644
index 0000000000000000000000000000000000000000..8177e892086122d84d4722b7e2e6b303feac382b
--- /dev/null
+++ b/ansible/roles/nclu-switch/templates/nclu-config.j2
@@ -0,0 +1,13 @@
+#jinja2: trim_blocks: True,lstrip_blocks: True
+{% for line in nclu_switch_config %}
+{{ line }}
+{% endfor %}
+
+{% for interface, config in nclu_switch_interface_config.items() %}
+{% if config.description is defined %}
+add {{ config.type | default("interface") }} {{ interface }} alias {{ config.description }}
+{% endif %}
+{% for line in config.config %}
+add {{ config.type | default("interface") }} {{ interface }} {{ line }}
+{% endfor %}
+{% endfor %}
diff --git a/doc/source/configuration/physical-network.rst b/doc/source/configuration/physical-network.rst
index f21d62dd7975a0fcde3fb5350c9beb7f4892d677..6e27d95021f94525083e691b8d6900f27f3aca7d 100644
--- a/doc/source/configuration/physical-network.rst
+++ b/doc/source/configuration/physical-network.rst
@@ -12,8 +12,10 @@ Devices are added to the Ansible inventory, and configured using Ansible's
 networking modules.  Configuration is applied via the ``kayobe physical network
 configure`` command.  See :ref:`physical-network` for details.
 
-The following switches are currently supported:
+The following switch operating systems are currently supported:
 
+* Cumulus Linux (via `Network Command Line Utility (NCLU)
+  <https://docs.cumulusnetworks.com/display/DOCS/Network+Command+Line+Utility+-+NCLU>`__)
 * Dell OS 6
 * Dell OS 9
 * Dell PowerConnect
@@ -173,6 +175,23 @@ example:
 Device-specific Configuration Variables
 =======================================
 
+Cumulus Linux (with NCLU)
+-------------------------
+
+Configuration for these devices is applied using the ``nclu`` Ansible module.
+
+``switch_type`` should be set to ``nclu``.
+
+SSH configuration
+^^^^^^^^^^^^^^^^^
+
+As with any non-switch host in the inventory, the ``nclu`` module relies on the
+default connection parameters used by Ansible:
+
+* ``ansible_host`` is the hostname or IP address.  Optional.
+
+* ``ansible_user`` is the SSH username.
+
 Dell OS6 and OS9
 ----------------
 
diff --git a/releasenotes/notes/cumulus-switch-baf554118ef23afe.yaml b/releasenotes/notes/cumulus-switch-baf554118ef23afe.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..08a6a7fb8c4f3aeb85bdaeb722c9a35d642f50d6
--- /dev/null
+++ b/releasenotes/notes/cumulus-switch-baf554118ef23afe.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Adds support for configuring Cumulus switches using the `Network Command
+    Line Utility (NCLU)
+    <https://docs.cumulusnetworks.com/display/DOCS/Network+Command+Line+Utility+-+NCLU>`__.
+    This is integrated with the ``kayobe physical network configure`` command.