diff --git a/ansible/filter_plugins/networks.py b/ansible/filter_plugins/networks.py
index 3c5d03f1a28a60e9442f6a8457d5c689a485600a..427fd2d5d62e8fbed1bd7736c68d9f5a9a66e53e 100644
--- a/ansible/filter_plugins/networks.py
+++ b/ansible/filter_plugins/networks.py
@@ -127,6 +127,7 @@ net_mtu = _make_attr_filter('mtu')
 net_routes = _make_attr_filter('routes')
 net_rules = _make_attr_filter('rules')
 net_physical_network = _make_attr_filter('physical_network')
+net_bootproto = _make_attr_filter('bootproto')
 
 
 @jinja2.contextfilter
@@ -200,6 +201,7 @@ def net_interface_obj(context, name, inventory_hostname=None):
     if routes:
         routes = [_route_obj(route) for route in routes]
     rules = net_rules(context, name, inventory_hostname)
+    bootproto = net_bootproto(context, name, inventory_hostname)
     interface = {
         'device': device,
         'address': ip,
@@ -209,7 +211,7 @@ def net_interface_obj(context, name, inventory_hostname=None):
         'mtu': mtu,
         'route': routes,
         'rules': rules,
-        'bootproto': 'static',
+        'bootproto': bootproto or 'static',
         'onboot': 'yes',
     }
     interface = {k: v for k, v in interface.items() if v is not None}
@@ -241,6 +243,7 @@ def net_bridge_obj(context, name, inventory_hostname=None):
     if routes:
         routes = [_route_obj(route) for route in routes]
     rules = net_rules(context, name, inventory_hostname)
+    bootproto = net_bootproto(context, name, inventory_hostname)
     interface = {
         'device': device,
         'address': ip,
@@ -251,7 +254,7 @@ def net_bridge_obj(context, name, inventory_hostname=None):
         'ports': ports,
         'route': routes,
         'rules': rules,
-        'bootproto': 'static',
+        'bootproto': bootproto or 'static',
         'onboot': 'yes',
     }
     interface = {k: v for k, v in interface.items() if v is not None}
@@ -289,6 +292,7 @@ def net_bond_obj(context, name, inventory_hostname=None):
     if routes:
         routes = [_route_obj(route) for route in routes]
     rules = net_rules(context, name, inventory_hostname)
+    bootproto = net_bootproto(context, name, inventory_hostname)
     interface = {
         'device': device,
         'address': ip,
@@ -305,7 +309,7 @@ def net_bond_obj(context, name, inventory_hostname=None):
         'bond_lacp_rate': lacp_rate,
         'route': routes,
         'rules': rules,
-        'bootproto': 'static',
+        'bootproto': bootproto or 'static',
         'onboot': 'yes',
     }
     interface = {k: v for k, v in interface.items() if v is not None}
@@ -389,14 +393,14 @@ def net_configdrive_network_device(context, name, inventory_hostname=None):
     cidr = net_cidr(context, name, inventory_hostname)
     netmask = net_mask(context, name, inventory_hostname)
     gateway = net_gateway(context, name, inventory_hostname)
-    bootproto = 'static' if ip is not None else 'dhcp'
+    bootproto = net_bootproto(context, name, inventory_hostname)
     mtu = net_mtu(context, name, inventory_hostname)
     interface = {
         'device': device,
         'address': ip,
         'netmask': netmask,
         'gateway': gateway,
-        'bootproto': bootproto,
+        'bootproto': bootproto or 'static',
         'mtu': mtu,
     }
     interface = {k: v for k, v in interface.items() if v is not None}
@@ -459,6 +463,7 @@ class FilterModule(object):
             'net_routes': net_routes,
             'net_rules': net_rules,
             'net_physical_network': net_physical_network,
+            'net_bootproto': net_bootproto,
             'net_interface_obj': net_interface_obj,
             'net_bridge_obj': net_bridge_obj,
             'net_bond_obj': net_bond_obj,
diff --git a/ansible/ip-allocation.yml b/ansible/ip-allocation.yml
index 6d66e700db247333d884600f6f56e399ce6cf201..cb8520f9542f3bba5a1cf582b26d072cff6325b1 100644
--- a/ansible/ip-allocation.yml
+++ b/ansible/ip-allocation.yml
@@ -24,7 +24,9 @@
             }]
           }}
       with_items: "{{ network_interfaces }}"
-      when: item|net_cidr != None
+      when:
+        - item | net_cidr != None
+        - item | net_bootproto != 'dhcp'
   roles:
     - role: ip-allocation
       ip_allocation_filename: "{{ kayobe_config_path }}/network-allocation.yml"
diff --git a/doc/source/configuration/network.rst b/doc/source/configuration/network.rst
index 27f6b9d8bd62c05aec107b90bb556a5d9367e688..c976c50369d177259bbd7c2a27e66af6b9c81c21 100644
--- a/doc/source/configuration/network.rst
+++ b/doc/source/configuration/network.rst
@@ -236,6 +236,10 @@ The following attributes are supported:
 
 ``interface``
     The name of the network interface attached to the network.
+``bootproto``
+    Boot protocol for the interface. Valid values are ``static`` and ``dhcp``.
+    The default is ``static``. When set to ``dhcp``, an external DHCP server
+    must be provided.
 ``bridge_ports``
     For bridge interfaces, a list of names of network interfaces to add to the
     bridge.
diff --git a/releasenotes/notes/bootproto-52316b6dbc30e98c.yaml b/releasenotes/notes/bootproto-52316b6dbc30e98c.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..73c896d761efab8e15ee1928a5c747318ac3be41
--- /dev/null
+++ b/releasenotes/notes/bootproto-52316b6dbc30e98c.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - Adds support for setting the boot protocol of a network interface. Valid
+    values are ``static`` and ``dhcp``.  The default is ``static``. When set to
+    an external DHCP server must be provided.