diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index d96e7acab2..f22016c258 100644
--- a/ansible/group_vars/all.yml
+++ b/ansible/group_vars/all.yml
@@ -431,16 +431,20 @@ nova_console: "novnc"
 # Valid options are [ public, internal, admin ]
 openstack_interface: "admin"
 
+# Enable core OpenStack services. This includes:
+# glance, keystone, neutron, nova, heat, and horizon.
+enable_openstack_core: "yes"
+
 # These roles are required for Kolla to be operation, however a savvy deployer
 # could disable some of these required roles and run their own services.
-enable_glance: "yes"
+enable_glance: "{{ enable_openstack_core | bool }}"
 enable_haproxy: "yes"
 enable_keepalived: "{{ enable_haproxy | bool }}"
-enable_keystone: "yes"
+enable_keystone: "{{ enable_openstack_core | bool }}"
 enable_mariadb: "yes"
 enable_memcached: "yes"
-enable_neutron: "yes"
-enable_nova: "yes"
+enable_neutron: "{{ enable_openstack_core | bool }}"
+enable_nova: "{{ enable_openstack_core | bool }}"
 enable_rabbitmq: "{{ 'yes' if om_rpc_transport == 'rabbit' or om_notify_transport == 'rabbit' else 'no' }}"
 enable_outward_rabbitmq: "{{ enable_murano | bool }}"
 
@@ -480,8 +484,8 @@ enable_fluentd: "yes"
 enable_freezer: "no"
 enable_gnocchi: "no"
 enable_grafana: "no"
-enable_heat: "yes"
-enable_horizon: "yes"
+enable_heat: "{{ enable_openstack_core | bool }}"
+enable_horizon: "{{ enable_openstack_core | bool }}"
 enable_horizon_blazar: "{{ enable_blazar | bool }}"
 enable_horizon_cloudkitty: "{{ enable_cloudkitty | bool }}"
 enable_horizon_congress: "{{ enable_congress | bool }}"
@@ -546,7 +550,7 @@ enable_nova_ssh: "yes"
 enable_octavia: "no"
 enable_onos: "no"
 enable_opendaylight: "no"
-enable_openvswitch: "{{ neutron_plugin_agent != 'linuxbridge' }}"
+enable_openvswitch: "{{ enable_neutron | bool and neutron_plugin_agent != 'linuxbridge' }}"
 enable_ovs_dpdk: "no"
 enable_osprofiler: "no"
 enable_panko: "no"
diff --git a/ansible/roles/ironic/defaults/main.yml b/ansible/roles/ironic/defaults/main.yml
index dd45736dea..5f9415594a 100644
--- a/ansible/roles/ironic/defaults/main.yml
+++ b/ansible/roles/ironic/defaults/main.yml
@@ -182,7 +182,7 @@ ironic_console_serial_speed: "115200n8"
 ironic_ipxe_url: http://{{ api_interface_address }}:{{ ironic_ipxe_port }}
 ironic_enable_rolling_upgrade: "yes"
 ironic_inspector_kernel_cmdline_extras: []
-ironic_inspector_pxe_filter: iptables
+ironic_inspector_pxe_filter: "{% if enable_neutron | bool %}iptables{% else %}none{% endif %}"
 
 ####################
 ## Kolla
diff --git a/ansible/roles/ironic/templates/inspector.ipxe.j2 b/ansible/roles/ironic/templates/inspector.ipxe.j2
index c38277e50c..4675a0588d 100644
--- a/ansible/roles/ironic/templates/inspector.ipxe.j2
+++ b/ansible/roles/ironic/templates/inspector.ipxe.j2
@@ -3,6 +3,14 @@
 :retry_dhcp
 dhcp || goto retry_dhcp
 
+{# Standalone ironic: use ironic-configured PXE configs #}
+{% if not enable_neutron | bool %}
+# load the MAC-specific file or fail if it's not found
+:boot_system
+chain pxelinux.cfg/${mac:hexhyp} || goto inspector_ipa
+{% endif %}
+
+:inspector_ipa
 :retry_boot
 imgfree
 kernel --timeout 30000 {{ ironic_ipxe_url }}/ironic-agent.kernel ipa-inspection-callback-url=http://{{ kolla_internal_vip_address }}:{{ ironic_inspector_port }}/v1/continue systemd.journald.forward_to_console=yes BOOTIF=${mac} initrd=agent.ramdisk {{ ironic_inspector_kernel_cmdline_extras | join(' ') }} || goto retry_boot
diff --git a/ansible/roles/ironic/templates/ironic-inspector.conf.j2 b/ansible/roles/ironic/templates/ironic-inspector.conf.j2
index e7c93a4187..f423a33ee5 100644
--- a/ansible/roles/ironic/templates/ironic-inspector.conf.j2
+++ b/ansible/roles/ironic/templates/ironic-inspector.conf.j2
@@ -2,6 +2,9 @@
 debug = {{ ironic_logging_debug }}
 log_dir = /var/log/kolla/ironic-inspector
 
+{% if not enable_keystone | bool %}
+auth_strategy = noauth
+{% endif %}
 listen_address = {{ api_interface_address }}
 listen_port = {{ ironic_inspector_port }}
 transport_url = {{ rpc_transport_url }}
@@ -10,6 +13,7 @@ transport_url = {{ rpc_transport_url }}
 transport_url = {{ notify_transport_url }}
 
 [ironic]
+{% if enable_keystone | bool %}
 auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
 auth_type = password
 project_domain_id = {{ default_project_domain_id }}
@@ -18,7 +22,12 @@ project_name = service
 username = {{ ironic_inspector_keystone_user }}
 password = {{ ironic_inspector_keystone_password }}
 os_endpoint_type = internalURL
+{% else %}
+auth_type = none
+endpoint_override = {{ ironic_internal_endpoint }}
+{% endif %}
 
+{% if enable_keystone | bool %}
 [keystone_authtoken]
 www_authenticate_uri = {{ internal_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_public_port }}
 auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
@@ -32,6 +41,7 @@ password = {{ ironic_inspector_keystone_password }}
 memcache_security_strategy = ENCRYPT
 memcache_secret_key = {{ memcache_secret_key }}
 memcached_servers = {% for host in groups['memcached'] %}{{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ memcached_port }}{% if not loop.last %},{% endif %}{% endfor %}
+{% endif %}
 
 {% if ironic_policy_file is defined %}
 [oslo_policy]
diff --git a/ansible/roles/ironic/templates/ironic.conf.j2 b/ansible/roles/ironic/templates/ironic.conf.j2
index c21b8b1e21..718b2421d4 100644
--- a/ansible/roles/ironic/templates/ironic.conf.j2
+++ b/ansible/roles/ironic/templates/ironic.conf.j2
@@ -59,7 +59,6 @@ memcache_secret_key = {{ memcache_secret_key }}
 memcached_servers = {% for host in groups['memcached'] %}{{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ memcached_port }}{% if not loop.last %},{% endif %}{% endfor %}
 {% endif %}
 
-
 {% if enable_cinder | bool %}
 [cinder]
 auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
@@ -69,8 +68,9 @@ user_domain_id = default
 project_name = service
 username = {{ ironic_keystone_user }}
 password = {{ ironic_keystone_password }}
-
 {% endif %}
+
+{% if enable_glance | bool %}
 [glance]
 glance_api_servers = {{ internal_protocol }}://{{ kolla_internal_fqdn }}:{{ glance_api_port }}
 auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
@@ -80,7 +80,9 @@ user_domain_id = default
 project_name = service
 username = {{ ironic_keystone_user }}
 password = {{ ironic_keystone_password }}
+{% endif %}
 
+{% if enable_neutron | bool %}
 [neutron]
 url = {{ internal_protocol }}://{{ kolla_internal_fqdn }}:{{ neutron_server_port }}
 auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
@@ -91,9 +93,11 @@ project_name = service
 username = {{ ironic_keystone_user }}
 password = {{ ironic_keystone_password }}
 cleaning_network = {{ ironic_cleaning_network }}
+{% endif %}
 
 [inspector]
 enabled = true
+{% if enable_keystone | bool %}
 auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
 auth_type = password
 project_domain_id = default
@@ -101,7 +105,10 @@ user_domain_id = default
 project_name = service
 username = {{ ironic_keystone_user }}
 password = {{ ironic_keystone_password }}
-service_url = {{ ironic_inspector_internal_endpoint }}
+{% else %}
+auth_type=none
+{% endif %}
+endpoint_override = {{ ironic_inspector_internal_endpoint }}
 
 [agent]
 deploy_logs_local_path = /var/log/kolla/ironic
@@ -128,3 +135,8 @@ http_url = {{ ironic_ipxe_url }}
 
 [oslo_middleware]
 enable_proxy_headers_parsing = True
+
+{% if not enable_neutron | bool %}
+[dhcp]
+dhcp_provider = none
+{% endif %}
diff --git a/etc/kolla/globals.yml b/etc/kolla/globals.yml
index 5686aad338..0b95913d3c 100644
--- a/etc/kolla/globals.yml
+++ b/etc/kolla/globals.yml
@@ -171,6 +171,19 @@ kolla_internal_vip_address: "10.10.10.254"
 # Valid options are [ none, novnc, spice, rdp ]
 #nova_console: "novnc"
 
+# These roles are required for Kolla to be operation, however a savvy deployer
+# could disable some of these required roles and run their own services.
+#enable_glance: "{{ enable_openstack_core | bool }}"
+#enable_haproxy: "yes"
+#enable_keepalived: "{{ enable_haproxy | bool }}"
+#enable_keystone: "{{ enable_openstack_core | bool }}"
+#enable_mariadb: "yes"
+#enable_memcached: "yes"
+#enable_neutron: "{{ enable_openstack_core | bool }}"
+#enable_nova: "{{ enable_openstack_core | bool }}"
+#enable_rabbitmq: "{{ 'yes' if om_rpc_transport == 'rabbit' or om_notify_transport == 'rabbit' else 'no' }}"
+#enable_outward_rabbitmq: "{{ enable_murano | bool }}"
+
 # OpenStack services can be enabled or disabled with these options
 #enable_aodh: "no"
 #enable_barbican: "no"
@@ -202,9 +215,8 @@ kolla_internal_vip_address: "10.10.10.254"
 #enable_freezer: "no"
 #enable_gnocchi: "no"
 #enable_grafana: "no"
-#enable_haproxy: "yes"
-#enable_heat: "yes"
-#enable_horizon: "yes"
+#enable_heat: "{{ enable_openstack_core | bool }}"
+#enable_horizon: "{{ enable_openstack_core | bool }}"
 #enable_horizon_blazar: "{{ enable_blazar | bool }}"
 #enable_horizon_cloudkitty: "{{ enable_cloudkitty | bool }}"
 #enable_horizon_congress: "{{ enable_congress | bool }}"
@@ -264,7 +276,8 @@ kolla_internal_vip_address: "10.10.10.254"
 #enable_octavia: "no"
 #enable_onos: "no"
 #enable_opendaylight: "no"
-#enable_openvswitch: "{{ neutron_plugin_agent != 'linuxbridge' }}"
+#enable_openstack_core: "yes"
+#enable_openvswitch: "{{ enable_neutron | bool and neutron_plugin_agent != 'linuxbridge' }}"
 #enable_ovs_dpdk: "no"
 #enable_osprofiler: "no"
 #enable_panko: "no"
diff --git a/releasenotes/notes/ironic-standalone-66dbb02a190c8b5d.yaml b/releasenotes/notes/ironic-standalone-66dbb02a190c8b5d.yaml
new file mode 100644
index 0000000000..2fd4f13636
--- /dev/null
+++ b/releasenotes/notes/ironic-standalone-66dbb02a190c8b5d.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    Adds a new flag, ``enable_openstack_core``, which defaults to ``yes``.
+    Setting this flag to ``no`` will disable the core OpenStack services,
+    including Glance, Heat, Horizon, Keystone, Neutron, and Nova.
+  - |
+    Improves the default configuration of OpenStack Ironic when used in
+    standalone mode.