diff --git a/charts/maas/Chart.yaml b/charts/maas/Chart.yaml
index 1453619..99ab411 100755
--- a/charts/maas/Chart.yaml
+++ b/charts/maas/Chart.yaml
@@ -15,7 +15,7 @@
 apiVersion: v1
 description: Chart to run Canonical MaaS
 name: maas
-version: 0.1.2
+version: 0.1.3
 home: https://docs.ubuntu.com/maas
 sources:
   - https://git.launchpad.net/maas
diff --git a/charts/maas/templates/bin/_maas-test.sh.tpl b/charts/maas/templates/bin/_maas-test.sh.tpl
index 0257571..4a36e81 100644
--- a/charts/maas/templates/bin/_maas-test.sh.tpl
+++ b/charts/maas/templates/bin/_maas-test.sh.tpl
@@ -45,6 +45,16 @@ function check_rack_controllers {
     fi
 }
 
+function check_admin_api {
+    if maas local version read;
+    then
+        echo 'Admin API is responding'
+        return 0
+    else
+        return 1
+    fi
+}
+
 function establish_session {
     maas login local ${MAAS_URL} ${MAAS_API_KEY}
     return $?
@@ -74,5 +84,13 @@ then
     exit 1
 fi
 
+check_admin_api
+
+if [[ $? -eq 1 ]]
+then
+    echo "Admin API response FAILED!"
+    exit 1
+fi
+
 echo "MAAS Validation SUCCESS!"
 exit 0
diff --git a/images/maas-rack-controller-focal/3.0_ipmi_error.patch b/images/maas-rack-controller-focal/3.0_ipmi_error.patch
new file mode 100644
index 0000000..ed26007
--- /dev/null
+++ b/images/maas-rack-controller-focal/3.0_ipmi_error.patch
@@ -0,0 +1,27 @@
+diff --git a/src/provisioningserver/drivers/power/ipmi.py b/src/provisioningserver/drivers/power/ipmi.py
+index acf284e9d..edcaf9e41 100644
+--- a/src/provisioningserver/drivers/power/ipmi.py
++++ b/src/provisioningserver/drivers/power/ipmi.py
+@@ -155,6 +155,13 @@ IPMI_ERRORS = {
+         ),
+         "exception": PowerConnError,
+     },
++    "BMC error": {
++        "message": (
++            "Device not responding correctly while performing power action."
++            "  MAAS performed several retries.  Please wait and try again."
++        ),
++        "exception": PowerConnError,
++    },
+     "could not find inband device": {
+         "message": (
+             "An inband device could not be found."
+@@ -283,7 +290,7 @@ class IPMIPowerDriver(PowerDriver):
+         ),
+     ]
+     ip_extractor = make_ip_extractor("power_address")
+-    wait_time = (4, 8, 16, 32)
++    wait_time = (4, 4, 8, 8, 16, 16, 32, 32)
+ 
+     def detect_missing_packages(self):
+         if not shell.has_command_available("ipmipower"):
diff --git a/images/maas-rack-controller-focal/3.0_nic_filter.patch b/images/maas-rack-controller-focal/3.0_nic_filter.patch
new file mode 100644
index 0000000..f3c58f5
--- /dev/null
+++ b/images/maas-rack-controller-focal/3.0_nic_filter.patch
@@ -0,0 +1,13 @@
+diff --git a/src/provisioningserver/utils/network.py b/src/provisioningserver/utils/network.py
+index d8c781e38..3d2e6264c 100644
+--- a/src/provisioningserver/utils/network.py
++++ b/src/provisioningserver/utils/network.py
+@@ -1129,6 +1129,8 @@ def get_all_interfaces_definition(
+         # interfaces for guests. By themselves, they're not useful for MAAS to
+         # manage.
+         "tunnel",
++        # Always exclude non-specific ethernet interfaces.
++        "ethernet",
+     ]
+     if not running_in_container():
+         # When not running in a container, we should be able to identify
diff --git a/images/maas-rack-controller-focal/3.0_redfish_retries.patch b/images/maas-rack-controller-focal/3.0_redfish_retries.patch
new file mode 100644
index 0000000..1189bf7
--- /dev/null
+++ b/images/maas-rack-controller-focal/3.0_redfish_retries.patch
@@ -0,0 +1,12 @@
+diff --git a/src/provisioningserver/drivers/power/redfish.py b/src/provisioningserver/drivers/power/redfish.py
+index 334ea2ca8..f38b05b8c 100644
+--- a/src/provisioningserver/drivers/power/redfish.py
++++ b/src/provisioningserver/drivers/power/redfish.py
+@@ -151,6 +151,7 @@ class RedfishPowerDriver(RedfishPowerDriverBase):
+         make_setting_field("node_id", "Node ID", scope=SETTING_SCOPE.NODE),
+     ]
+     ip_extractor = make_ip_extractor("power_address")
++    wait_time = (4, 8, 16, 32)
+ 
+     def detect_missing_packages(self):
+         # no required packages
diff --git a/images/maas-rack-controller-focal/3.0_secure_headers.patch b/images/maas-rack-controller-focal/3.0_secure_headers.patch
new file mode 100644
index 0000000..f751649
--- /dev/null
+++ b/images/maas-rack-controller-focal/3.0_secure_headers.patch
@@ -0,0 +1,12 @@
+diff --git a/src/twisted/web/server.py b/src/twisted/web/server.py
+index 23e3e408e..8e3e5b772 100644
+--- a/src/twisted/web/server.py
++++ b/src/twisted/web/server.py
+@@ -178,7 +178,6 @@ class Request(Copyable, http.Request, components.Componentized):
+         self.site = self.channel.site
+ 
+         # set various default headers
+-        self.setHeader(b'server', version)
+         self.setHeader(b'date', http.datetimeToString())
+ 
+         # Resource Identification
diff --git a/images/maas-rack-controller-focal/Dockerfile b/images/maas-rack-controller-focal/Dockerfile
new file mode 100644
index 0000000..4fc927f
--- /dev/null
+++ b/images/maas-rack-controller-focal/Dockerfile
@@ -0,0 +1,82 @@
+ARG FROM=ubuntu:20.04
+FROM ${FROM}
+
+LABEL org.opencontainers.image.authors='airship-discuss@lists.airshipit.org, irc://#airshipit@freenode'
+LABEL org.opencontainers.image.url='https://airshipit.org'
+LABEL org.opencontainers.image.documentation='https://github.com/openstack/airship-maas'
+LABEL org.opencontainers.image.source='https://git.openstack.org/openstack/airship-maas'
+LABEL org.opencontainers.image.vendor='The Airship Authors'
+LABEL org.opencontainers.image.licenses='Apache-2.0'
+
+ARG HTTP_PROXY
+ARG HTTPS_PROXY
+ARG NO_PROXY
+ARG http_proxy
+ARG https_proxy
+ARG no_proxy
+
+ENV DEBIAN_FRONTEND noninteractive
+ENV container docker
+
+ENV MAAS_VERSION 1:3.0.0-10029-g.986ea3e45-0ubuntu1~20.04.1
+
+RUN apt-get -qq update \
+ && apt-get install -y \
+        avahi-daemon \
+        isc-dhcp-server \
+        jq \
+        libvirt-clients \
+        libvirt-daemon-system\
+        patch \
+        software-properties-common \
+        sudo \
+        systemd \
+        ca-certificates \
+# Don't start any optional services except for the few we need.
+# (specifically, don't start avahi-daemon, isc-dhcp-server, or libvirtd)
+ && find /etc/systemd/system \
+         /lib/systemd/system \
+         -path '*.wants/*' \
+         -not -name '*journald*' \
+         -not -name '*systemd-tmpfiles*' \
+         -not -name '*systemd-user-sessions*' \
+         -exec rm \{} \; \
+ && systemctl set-default multi-user.target \
+# Install maas from the ppa
+ && add-apt-repository -yu ppa:maas/3.0 \
+ && apt-get install -y \
+        maas-rack-controller=$MAAS_VERSION \
+ && rm -rf /var/lib/apt/lists/*
+
+# Preserve the directory structure, permissions, and contents of /var/lib/maas
+RUN mkdir -p /opt/maas/ && tar -cvzf /opt/maas/var-lib-maas.tgz /var/lib/maas
+
+# register ourselves with the region controller
+COPY scripts/register-rack-controller.service /lib/systemd/system/register-rack-controller.service
+RUN systemctl enable register-rack-controller.service
+
+# Patch so that Calico interfaces are ignored
+COPY 3.0_nic_filter.patch /tmp/3.0_nic_filter.patch
+COPY 3.0_secure_headers.patch /tmp/3.0_secure_headers.patch
+# Patch so maas knows that "BMC error" is retriable
+COPY 3.0_ipmi_error.patch /tmp/3.0_ipmi_error.patch
+# Patch to space redfish request retries apart a bit, to avoid overwhelming the BMC
+COPY 3.0_redfish_retries.patch /tmp/3.0_redfish_retries.patch
+
+RUN cd /usr/lib/python3/dist-packages/provisioningserver/utils && patch network.py < /tmp/3.0_nic_filter.patch
+RUN cd /usr/lib/python3/dist-packages/twisted/web && patch server.py < /tmp/3.0_secure_headers.patch
+RUN cd /usr/lib/python3/dist-packages/provisioningserver/drivers/power && patch ipmi.py < /tmp/3.0_ipmi_error.patch
+RUN cd /usr/lib/python3/dist-packages/provisioningserver/drivers/power && patch redfish.py < /tmp/3.0_redfish_retries.patch
+
+# echo journalctl logs to the container's stdout
+COPY scripts/journalctl-to-tty.service /etc/systemd/system/journalctl-to-tty.service
+RUN systemctl enable journalctl-to-tty.service
+
+# quiet sudo for the maas user
+RUN umask 0337; echo 'Defaults:maas !pam_session, !syslog' > /etc/sudoers.d/99-maas-no-log
+
+# avoid triggering bind9 high cpu utilization bug
+RUN sed -i -e '$a\include "/etc/bind/bind.keys";' /etc/bind/named.conf
+
+# initalize systemd
+CMD ["/bin/bash", "-c", "exec /sbin/init --log-target=console 3>&1"]
diff --git a/images/maas-rack-controller-focal/README.md b/images/maas-rack-controller-focal/README.md
new file mode 100644
index 0000000..0a11659
--- /dev/null
+++ b/images/maas-rack-controller-focal/README.md
@@ -0,0 +1 @@
+[![Docker Repository on Quay](https://quay.io/repository/airshipit/maas-rack/status "Docker Repository on Quay")](https://quay.io/repository/airshipit/maas-rack) Ubuntu MaaS Rack Controller
diff --git a/images/maas-rack-controller-focal/scripts/journalctl-to-tty.service b/images/maas-rack-controller-focal/scripts/journalctl-to-tty.service
new file mode 100644
index 0000000..2725055
--- /dev/null
+++ b/images/maas-rack-controller-focal/scripts/journalctl-to-tty.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Journald console log streamer
+Requires=systemd-journald.service
+After=systemd-journald.service
+
+[Service]
+Restart=always
+RestartSec=0
+ExecStart=/bin/journalctl -f
+StandardOutput=tty
+
+[Install]
+WantedBy=basic.target
diff --git a/images/maas-rack-controller-focal/scripts/register-rack-controller.service b/images/maas-rack-controller-focal/scripts/register-rack-controller.service
new file mode 100644
index 0000000..fb439d3
--- /dev/null
+++ b/images/maas-rack-controller-focal/scripts/register-rack-controller.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Register with MaaS Region Controller
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+PassEnvironment=MAAS_ENDPOINT MAAS_REGION_SECRET MAAS_API_KEY HOST_MOUNT_PATH
+ExecStart=/usr/local/bin/register-rack-controller.sh
+
+[Install]
+WantedBy=multi-user.target
diff --git a/images/maas-region-controller-focal/3.0_bios_grub_partition.patch b/images/maas-region-controller-focal/3.0_bios_grub_partition.patch
new file mode 100644
index 0000000..fc3a1c7
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_bios_grub_partition.patch
@@ -0,0 +1,13 @@
+diff --git a/src/maasserver/models/partition.py b/src/maasserver/models/partition.py
+index 62895f83b..832b1db4c 100644
+--- a/src/maasserver/models/partition.py
++++ b/src/maasserver/models/partition.py
+@@ -248,7 +248,7 @@ class Partition(CleanSave, TimestampedModel):
+                     return idx + 1
+             elif arch == "ppc64el" and block_device.id == boot_disk.id:
+                 return idx + 2
+-            elif arch == "amd64" and bios_boot_method != "uefi":
++            elif arch == "amd64" and bios_boot_method != "uefi" and block_device.id == boot_disk.id:
+                 if block_device.type == "physical":
+                     # Delay the `type` check because it can cause a query. Only
+                     # physical block devices get the bios_grub partition.
diff --git a/images/maas-region-controller-focal/3.0_configure_ipmi_user.patch b/images/maas-region-controller-focal/3.0_configure_ipmi_user.patch
new file mode 100644
index 0000000..39b06ae
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_configure_ipmi_user.patch
@@ -0,0 +1,52 @@
+diff --git a/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py b/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
+index 42e4c79e4..11df96313 100755
+--- a/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
++++ b/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
+@@ -396,6 +396,29 @@ class IPMI(BMCConfig):
+         return first_unused
+ 
+     def add_bmc_user(self):
++        """Create/configure an IPMI user, but with several tries"""
++        attempt = 1
++        max_attempts = 5
++        backoff_amount = 30
++        exceptions_caught = []
++        while attempt <= max_attempts:
++            print("INFO: Attempt to add IPMI BMC user - %s" % attempt)
++            try:
++                self._add_bmc_user()
++            except Exception as e:
++                exceptions_caught.append(e)
++                if (attempt + 1) > max_attempts:
++                    # This is our last attempt, exiting
++                    print("ERROR: Unable to add BMC user!\n{}".format(exceptions_caught), file=sys.stderr)
++                    sys.exit(1)
++
++            if self.password is None:
++                time.sleep(attempt * backoff_amount)
++            else:
++                return
++            attempt += 1
++
++    def _add_bmc_user(self):
+         if not self.username:
+             self.username = "maas"
+         user_number = self._pick_user_number(self.username)
+@@ -417,7 +440,7 @@ class IPMI(BMCConfig):
+                     if self._bmc_config[user_number].get(key) != value:
+                         self._bmc_set(user_number, key, value)
+             except Exception:
+-                pass
++                raise
+             else:
+                 self.password = password
+                 # Not all user settings are available on all BMC keys, its
+@@ -432,8 +455,6 @@ class IPMI(BMCConfig):
+                     "Yes",
+                 )
+                 return
+-        print("ERROR: Unable to add BMC user!", file=sys.stderr)
+-        sys.exit(1)
+ 
+     def _config_ipmi_lan_channel_settings(self):
+         """Enable IPMI-over-Lan (Lan_Channel) if it is disabled"""
diff --git a/images/maas-region-controller-focal/3.0_kernel_package.patch b/images/maas-region-controller-focal/3.0_kernel_package.patch
new file mode 100644
index 0000000..934d1f3
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_kernel_package.patch
@@ -0,0 +1,32 @@
+diff --git a/src/maasserver/preseed.py b/src/maasserver/preseed.py
+index f9ba34a18..a9f41b9c2 100644
+--- a/src/maasserver/preseed.py
++++ b/src/maasserver/preseed.py
+@@ -232,7 +232,27 @@ def compose_curtin_kernel_preseed(node):
+     The BootResourceFile table contains a mapping between hwe kernels and
+     Ubuntu package names. If this mapping is missing we fall back to letting
+     Curtin figure out which kernel should be installed"""
++
++    # previous logic to retrieve kpackage parameter
+     kpackage = BootResource.objects.get_kpackage_for_node(node)
++
++    # determine if this node has kernel parameters applied by drydock
++    # and override kpackage if we discover the right properties
++    kernel_opt_tag = "%s_kp" % (node.hostname)
++    if kernel_opt_tag in node.tag_names():
++
++        # the tag exists, retrieve it
++        kernel_opts = node.tags.get(name=kernel_opt_tag).kernel_opts
++
++        # parse the string and find our package param value
++        # e.g. kernel_package=linux-image-4.15.0-34-generic
++        kparams = kernel_opts.split()
++        kdict = dict(
++            kparam.split("=", 1) for kparam in kparams if "=" in kparam
++        )
++        if "kernel_package" in kdict:
++            kpackage = kdict["kernel_package"]
++
+     if kpackage:
+         kernel_config = {"kernel": {"package": kpackage, "mapping": {}}}
+         return [yaml.safe_dump(kernel_config)]
diff --git a/images/maas-region-controller-focal/3.0_partitiontable_does_not_exist.patch b/images/maas-region-controller-focal/3.0_partitiontable_does_not_exist.patch
new file mode 100644
index 0000000..b0f98d3
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_partitiontable_does_not_exist.patch
@@ -0,0 +1,13 @@
+diff --git a/src/maasserver/api/partitions.py b/src/maasserver/api/partitions.py
+index 5a413fb3b..6a5ad8b47 100644
+--- a/src/maasserver/api/partitions.py
++++ b/src/maasserver/api/partitions.py
+@@ -99,7 +99,7 @@ class PartitionsHandler(OperationsHandler):
+         device = BlockDevice.objects.get_block_device_or_404(
+             system_id, device_id, request.user, NodePermission.view
+         )
+-        partition_table = device.partitiontable_set.get()
++        partition_table = device.get_partitiontable()
+         if partition_table is None:
+             return []
+         else:
diff --git a/images/maas-region-controller-focal/3.0_proxy_acl.patch b/images/maas-region-controller-focal/3.0_proxy_acl.patch
new file mode 100644
index 0000000..0de535e
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_proxy_acl.patch
@@ -0,0 +1,10 @@
+18,24c18
+< http_access allow maas_proxy_manager localhost
+< http_access deny maas_proxy_manager
+< http_access deny !Safe_ports
+< http_access deny CONNECT !SSL_ports
+< http_access allow localnet
+< http_access allow localhost
+< http_access deny all
+---
+> http_access allow all
diff --git a/images/maas-region-controller-focal/3.0_regex_tags.patch b/images/maas-region-controller-focal/3.0_regex_tags.patch
new file mode 100644
index 0000000..aa438fa
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_regex_tags.patch
@@ -0,0 +1,24 @@
+diff --git a/src/maasserver/models/ownerdata.py b/src/maasserver/models/ownerdata.py
+index 5d26041ca..0e208f264 100644
+--- a/src/maasserver/models/ownerdata.py
++++ b/src/maasserver/models/ownerdata.py
+@@ -18,7 +18,7 @@ from django.db.models import (
+ from maasserver import DefaultMeta
+ from maasserver.models.cleansave import CleanSave
+ 
+-DATA_KEY_RE = re.compile(r"[\w.-]+$")
++#DATA_KEY_RE = re.compile(r"[\w.-]+$")
+ 
+ 
+ class OwnerDataManager(Manager):
+@@ -33,8 +33,8 @@ class OwnerDataManager(Manager):
+             if value is None:
+                 to_remove.add(key)
+             else:
+-                if not DATA_KEY_RE.match(key):
+-                    raise ValueError("Invalid character in key name")
++                # if not DATA_KEY_RE.match(key):
++                #    raise ValueError("Invalid character in key name")
+ 
+                 self.update_or_create(
+                     node=node, key=key, defaults={"value": value}
diff --git a/images/maas-region-controller-focal/3.0_region_secret_rotate.patch b/images/maas-region-controller-focal/3.0_region_secret_rotate.patch
new file mode 100644
index 0000000..fce4a7e
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_region_secret_rotate.patch
@@ -0,0 +1,21 @@
+diff --git a/src/maasserver/security.py b/src/maasserver/security.py
+index a9420e504..61c6f00c6 100644
+--- a/src/maasserver/security.py
++++ b/src/maasserver/security.py
+@@ -96,11 +96,11 @@ def get_shared_secret_txn():
+     elif secret_in_db == secret_on_fs:
+         secret = secret_in_db  # or secret_on_fs.
+     else:
+-        raise AssertionError(
+-            "The secret stored in the database does not match the secret "
+-            "stored on the filesystem at %s. Please investigate."
+-            % get_shared_secret_filesystem_path()
+-        )
++        # (nk613n): When we rotate secrets we only update the filesystem
++        # so if the secrets don't match we will default to the FS
++        # secret and set it in the database (set_config function)
++        secret = secret_on_fs
++        Config.objects.set_config("rpc_shared_secret", to_hex(secret))
+ 
+     return secret
+ 
diff --git a/images/maas-region-controller-focal/3.0_route.patch b/images/maas-region-controller-focal/3.0_route.patch
new file mode 100644
index 0000000..391c617
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_route.patch
@@ -0,0 +1,13 @@
+diff --git a/src/maasserver/preseed_network.py b/src/maasserver/preseed_network.py
+index 3851424fc..8bfa90e7d 100644
+--- a/src/maasserver/preseed_network.py
++++ b/src/maasserver/preseed_network.py
+@@ -309,7 +309,7 @@ class InterfaceConfiguration:
+ 
+     def _get_matching_routes(self, source):
+         """Return all route objects matching `source`."""
+-        return {route for route in self.routes if route.source == source}
++        return {route for route in self.routes if str(route.source.cidr) == str(source.cidr)}
+ 
+     def _generate_addresses(self, version=1):
+         """Generate the various addresses needed for this interface."""
diff --git a/images/maas-region-controller-focal/3.0_secure_headers.patch b/images/maas-region-controller-focal/3.0_secure_headers.patch
new file mode 100644
index 0000000..f751649
--- /dev/null
+++ b/images/maas-region-controller-focal/3.0_secure_headers.patch
@@ -0,0 +1,12 @@
+diff --git a/src/twisted/web/server.py b/src/twisted/web/server.py
+index 23e3e408e..8e3e5b772 100644
+--- a/src/twisted/web/server.py
++++ b/src/twisted/web/server.py
+@@ -178,7 +178,6 @@ class Request(Copyable, http.Request, components.Componentized):
+         self.site = self.channel.site
+ 
+         # set various default headers
+-        self.setHeader(b'server', version)
+         self.setHeader(b'date', http.datetimeToString())
+ 
+         # Resource Identification
diff --git a/images/maas-region-controller-focal/Dockerfile b/images/maas-region-controller-focal/Dockerfile
new file mode 100644
index 0000000..8995d5a
--- /dev/null
+++ b/images/maas-region-controller-focal/Dockerfile
@@ -0,0 +1,90 @@
+ARG FROM=ubuntu:20.04
+FROM ${FROM}
+
+LABEL org.opencontainers.image.authors='airship-discuss@lists.airshipit.org, irc://#airshipit@freenode'
+LABEL org.opencontainers.image.url='https://airshipit.org'
+LABEL org.opencontainers.image.documentation='https://github.com/openstack/airship-maas'
+LABEL org.opencontainers.image.source='https://git.openstack.org/openstack/airship-maas'
+LABEL org.opencontainers.image.vendor='The Airship Authors'
+LABEL org.opencontainers.image.licenses='Apache-2.0'
+
+ARG HTTP_PROXY
+ARG HTTPS_PROXY
+ARG NO_PROXY
+ARG http_proxy
+ARG https_proxy
+ARG no_proxy
+
+ENV DEBIAN_FRONTEND noninteractive
+ENV container docker
+
+ENV MAAS_VERSION 1:3.0.0-10029-g.986ea3e45-0ubuntu1~20.04.1
+
+RUN apt-get -qq update \
+ && apt-get install -y \
+        avahi-daemon \
+        jq \
+        patch \
+        software-properties-common \
+        sudo \
+        systemd \
+        cron \
+        ca-certificates \
+# Don't start any optional services except for the few we need.
+# (specifically, don't start avahi-daemon)
+ && find /etc/systemd/system \
+         /lib/systemd/system \
+         -path '*.wants/*' \
+         -not -name '*journald*' \
+         -not -name '*systemd-tmpfiles*' \
+         -not -name '*systemd-user-sessions*' \
+         -exec rm \{} \; \
+ && systemctl set-default multi-user.target \
+# Install maas from the ppa
+ && add-apt-repository -yu ppa:maas/3.0 \
+ && apt-get install -y \
+        maas-region-api=$MAAS_VERSION \
+        # tcpdump is required by /usr/lib/maas/beacon-monitor
+        tcpdump \
+ && rm -rf /var/lib/apt/lists/*
+
+# Preserve the directory structure, permissions, and contents of /var/lib/maas
+RUN mkdir -p /opt/maas/ && tar -cvzf /opt/maas/var-lib-maas.tgz /var/lib/maas
+
+# MAAS workarounds
+COPY 3.0_route.patch /tmp/3.0_route.patch
+COPY 3.0_kernel_package.patch /tmp/3.0_kernel_package.patch
+COPY 3.0_bios_grub_partition.patch /tmp/3.0_bios_grub_partition.patch
+# sh8121att: allow all requests via the proxy to allow it to work
+# behind ingress
+COPY 3.0_proxy_acl.patch /tmp/3.0_proxy_acl.patch
+# Patch to add retrying to MaaS BMC user setup, and improve exception handling
+COPY 3.0_configure_ipmi_user.patch /tmp/3.0_configure_ipmi_user.patch
+COPY 3.0_secure_headers.patch /tmp/3.0_secure_headers.patch
+COPY 3.0_region_secret_rotate.patch /tmp/3.0_region_secret_rotate.patch
+COPY 3.0_partitiontable_does_not_exist.patch /tmp/3.0_partitiontable_does_not_exist.patch
+# Allow tags with '/' symbols
+COPY 3.0_regex_tags.patch /tmp/3.0_regex_tags.patch
+
+RUN cd /usr/lib/python3/dist-packages/maasserver && patch preseed_network.py < /tmp/3.0_route.patch
+RUN cd /usr/lib/python3/dist-packages/maasserver && patch preseed.py < /tmp/3.0_kernel_package.patch
+RUN cd /usr/lib/python3/dist-packages/maasserver/models && patch partition.py < /tmp/3.0_bios_grub_partition.patch
+RUN cd /usr/lib/python3/dist-packages/maasserver && patch security.py < /tmp/3.0_region_secret_rotate.patch
+RUN cd /usr/lib/python3/dist-packages/metadataserver/builtin_scripts/commissioning_scripts && patch bmc_config.py < /tmp/3.0_configure_ipmi_user.patch
+RUN cd /usr/lib/python3/dist-packages/provisioningserver/templates/proxy && patch maas-proxy.conf.template < /tmp/3.0_proxy_acl.patch
+RUN cd /usr/lib/python3/dist-packages/twisted/web && patch server.py < /tmp/3.0_secure_headers.patch
+RUN cd /usr/lib/python3/dist-packages/maasserver/api && patch partitions.py < /tmp/3.0_partitiontable_does_not_exist.patch
+RUN cd /usr/lib/python3/dist-packages/maasserver/models && patch ownerdata.py < /tmp/3.0_regex_tags.patch
+
+# echo journalctl logs to the container's stdout
+COPY journalctl-to-tty.service /etc/systemd/system/journalctl-to-tty.service
+RUN systemctl enable journalctl-to-tty.service
+
+# quiet sudo for the maas user
+RUN umask 0337; echo 'Defaults:maas !pam_session, !syslog' > /etc/sudoers.d/99-maas-no-log
+
+# avoid triggering bind9 high cpu utilization bug
+RUN sed -i -e '$a\include "/etc/bind/bind.keys";' /etc/bind/named.conf
+
+# initalize systemd
+CMD ["/bin/bash", "-c", "exec /sbin/init --log-target=console 3>&1"]
diff --git a/images/maas-region-controller-focal/README.md b/images/maas-region-controller-focal/README.md
new file mode 100644
index 0000000..cd33b83
--- /dev/null
+++ b/images/maas-region-controller-focal/README.md
@@ -0,0 +1 @@
+[![Docker Repository on Quay](https://quay.io/repository/airshipit/maas-rack/status "Docker Repository on Quay")](https://quay.io/repository/airshipit/maas-region) Ubuntu MaaS Region Controller
diff --git a/images/maas-region-controller-focal/journalctl-to-tty.service b/images/maas-region-controller-focal/journalctl-to-tty.service
new file mode 100644
index 0000000..2725055
--- /dev/null
+++ b/images/maas-region-controller-focal/journalctl-to-tty.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Journald console log streamer
+Requires=systemd-journald.service
+After=systemd-journald.service
+
+[Service]
+Restart=always
+RestartSec=0
+ExecStart=/bin/journalctl -f
+StandardOutput=tty
+
+[Install]
+WantedBy=basic.target