diff --git a/interface-definitions/include/version/dns-dynamic-version.xml.i b/interface-definitions/include/version/dns-dynamic-version.xml.i
new file mode 100644
index 000000000..b25fc6e76
--- /dev/null
+++ b/interface-definitions/include/version/dns-dynamic-version.xml.i
@@ -0,0 +1,3 @@
+<!-- include start from include/version/dns-dynamic-version.xml.i -->
+<syntaxVersion component='dns-dynamic' version='1'></syntaxVersion>
+<!-- include end -->
diff --git a/interface-definitions/xml-component-version.xml.in b/interface-definitions/xml-component-version.xml.in
index e05f64643..8c9e816d1 100644
--- a/interface-definitions/xml-component-version.xml.in
+++ b/interface-definitions/xml-component-version.xml.in
@@ -1,48 +1,49 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   #include <include/version/bgp-version.xml.i>
   #include <include/version/broadcast-relay-version.xml.i>
   #include <include/version/cluster-version.xml.i>
   #include <include/version/config-management-version.xml.i>
   #include <include/version/conntrack-sync-version.xml.i>
   #include <include/version/conntrack-version.xml.i>
   #include <include/version/container-version.xml.i>
   #include <include/version/dhcp-relay-version.xml.i>
   #include <include/version/dhcp-server-version.xml.i>
   #include <include/version/dhcpv6-server-version.xml.i>
+  #include <include/version/dns-dynamic-version.xml.i>
   #include <include/version/dns-forwarding-version.xml.i>
   #include <include/version/firewall-version.xml.i>
   #include <include/version/flow-accounting-version.xml.i>
   #include <include/version/https-version.xml.i>
   #include <include/version/interfaces-version.xml.i>
   #include <include/version/ids-version.xml.i>
   #include <include/version/ipoe-server-version.xml.i>
   #include <include/version/ipsec-version.xml.i>
   #include <include/version/isis-version.xml.i>
   #include <include/version/l2tp-version.xml.i>
   #include <include/version/lldp-version.xml.i>
   #include <include/version/mdns-version.xml.i>
   #include <include/version/monitoring-version.xml.i>
   #include <include/version/nat66-version.xml.i>
   #include <include/version/nat-version.xml.i>
   #include <include/version/ntp-version.xml.i>
   #include <include/version/openconnect-version.xml.i>
   #include <include/version/ospf-version.xml.i>
   #include <include/version/policy-version.xml.i>
   #include <include/version/pppoe-server-version.xml.i>
   #include <include/version/pptp-version.xml.i>
   #include <include/version/qos-version.xml.i>
   #include <include/version/quagga-version.xml.i>
   #include <include/version/rip-version.xml.i>
   #include <include/version/rpki-version.xml.i>
   #include <include/version/salt-version.xml.i>
   #include <include/version/snmp-version.xml.i>
   #include <include/version/ssh-version.xml.i>
   #include <include/version/sstp-version.xml.i>
   #include <include/version/system-version.xml.i>
   #include <include/version/vrf-version.xml.i>
   #include <include/version/vrrp-version.xml.i>
   #include <include/version/vyos-accel-ppp-version.xml.i>
   #include <include/version/wanloadbalance-version.xml.i>
   #include <include/version/webproxy-version.xml.i>
 </interfaceDefinition>
diff --git a/src/migration-scripts/dns-dynamic/0-to-1 b/src/migration-scripts/dns-dynamic/0-to-1
new file mode 100755
index 000000000..cf0983b01
--- /dev/null
+++ b/src/migration-scripts/dns-dynamic/0-to-1
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# T5144:
+# - migrate "service dns dynamic interface ..."
+#        to "service dns dynamic address ..."
+# - migrate "service dns dynamic interface <interface> use-web ..."
+#        to "service dns dynamic address <address> web-options ..."
+# - migrate "service dns dynamic interface <interface> rfc2136 <config> record ..."
+#        to "service dns dynamic address <address> rfc2136 <config> host-name ..."
+# - migrate "service dns dynamic interface <interface> service <config> login ..."
+#        to "service dns dynamic address <address> service <config> username ..."
+# - apply global 'ipv6-enable' to per <config> 'ip-version: ipv6'
+# - apply service protocol mapping upfront, they are not 'auto-detected' anymore
+
+import sys
+from vyos.configtree import ConfigTree
+
+service_protocol_mapping = {
+    'afraid': 'freedns',
+    'changeip': 'changeip',
+    'cloudflare': 'cloudflare',
+    'dnspark': 'dnspark',
+    'dslreports': 'dslreports1',
+    'dyndns': 'dyndns2',
+    'easydns': 'easydns',
+    'namecheap': 'namecheap',
+    'noip': 'noip',
+    'sitelutions': 'sitelutions',
+    'zoneedit': 'zoneedit1'
+}
+
+if (len(sys.argv) < 1):
+    print("Must specify file name!")
+    sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+    config_file = f.read()
+
+config = ConfigTree(config_file)
+
+old_base_path = ['service', 'dns', 'dynamic', 'interface']
+new_base_path = ['service', 'dns', 'dynamic', 'address']
+
+if not config.exists(old_base_path):
+    # Nothing to do
+    sys.exit(0)
+
+# Migrate "service dns dynamic interface"
+#      to "service dns dynamic address"
+config.rename(old_base_path, new_base_path[-1])
+
+for address in config.list_nodes(new_base_path):
+    # Migrate "service dns dynamic interface <interface> rfc2136 <config> record"
+    #      to "service dns dynamic address <address> rfc2136 <config> host-name"
+    if config.exists(new_base_path + [address, 'rfc2136']):
+        for rfc_cfg in config.list_nodes(new_base_path + [address, 'rfc2136']):
+            if config.exists(new_base_path + [address, 'rfc2136', rfc_cfg, 'record']):
+                config.rename(new_base_path + [address, 'rfc2136', rfc_cfg, 'record'], 'host-name')
+
+    # Migrate "service dns dynamic interface <interface> service <config> login"
+    #      to "service dns dynamic address <address> service <config> username"
+    if config.exists(new_base_path + [address, 'service']):
+        for svc_cfg in config.list_nodes(new_base_path + [address, 'service']):
+            if config.exists(new_base_path + [address, 'service', svc_cfg, 'login']):
+                config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username')
+            # Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6'
+            if config.exists(new_base_path + [address, 'ipv6-enable']):
+                config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'],
+                           value='ipv6', replace=False)
+                config.delete(new_base_path + [address, 'ipv6-enable'])
+            # Apply service protocol mapping upfront, they are not 'auto-detected' anymore
+            if svc_cfg in service_protocol_mapping:
+                config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'],
+                           value=service_protocol_mapping.get(svc_cfg), replace=False)
+
+    # Migrate "service dns dynamic interface <interface> use-web"
+    #      to "service dns dynamic address <address> web-options"
+    # Also, rename <address> to 'web' literal for backward compatibility
+    if config.exists(new_base_path + [address, 'use-web']):
+        config.rename(new_base_path + [address], 'web')
+        config.rename(new_base_path + ['web', 'use-web'], 'web-options')
+
+try:
+    with open(file_name, 'w') as f:
+        f.write(config.to_string())
+except OSError as e:
+    print("Failed to save the modified config: {}".format(e))
+    sys.exit(1)