diff --git a/software/metabase/test/test.py b/software/metabase/test/test.py
index 9bbed0cb8fbd9897326e78dd3a4b20acbcd8b681..3a349d282cc376983b6da1f033ebaeac08ba0318 100644
--- a/software/metabase/test/test.py
+++ b/software/metabase/test/test.py
@@ -25,23 +25,32 @@
 #
 ##############################################################################
 
+import contextlib
 import os
 import json
 from urllib import parse
+import shutil
+import subprocess
+import time
+
 import requests
 
 from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
+from slapos.testing.utils import CrontabMixin
+
 
-setUpModule, MetabaseTestCase = makeModuleSetUpAndTestCaseClass(
+setUpModule, BaseTestCase = makeModuleSetUpAndTestCaseClass(
     os.path.abspath(
         os.path.join(os.path.dirname(__file__), '..', 'software.cfg')))
 
 
-class TestMetabaseSetup(MetabaseTestCase):
+class MetabaseTestCase(BaseTestCase):
   __partition_reference__ = 'S'  # postgresql use a socket in data dir
   # instance can take time, /api/session/properties timeout at the beginning.
   instance_max_retry = 30
 
+
+class TestMetabaseSetup(MetabaseTestCase):
   def test_setup(self):
     url = self.computer_partition.getConnectionParameterDict()['url']
     resp = requests.get(parse.urljoin(url, '/setup'), verify=False)
@@ -94,3 +103,62 @@ class TestMetabaseSetup(MetabaseTestCase):
             "password": password
         }).json()
     self.assertTrue(session.get('id'))
+
+
+class TestMetabaseBackup(MetabaseTestCase, CrontabMixin):
+  def test_backup(self):
+    self._executeCrontabAtDate('postgresql-backup-crontab-entry', '2100-01-01')
+    with open(
+      os.path.join(
+        self.computer_partition_root_path, 'srv', 'backup', 'backup.pg_dump'),
+      'rb') as f:
+      self.assertIn(b'CREATE DATABASE metabase_db', f.read())
+
+  def test_restore(self):
+    # restore a "known good" backup and check we can login with the
+    # user from the backup.
+    url = self.computer_partition.getConnectionParameterDict()['url']
+
+    shutil.copyfile(
+      os.path.join(os.path.dirname(__file__), 'testdata', 'backup.pg_dump'),
+      os.path.join(
+        self.computer_partition_root_path, 'srv', 'backup', 'backup.pg_dump')
+    )
+
+    with self.slap.instance_supervisor_rpc as supervisor:
+      # stop metabase, so that it does not interfere with restoring the backup
+      info, = [i for i in
+         supervisor.getAllProcessInfo() if 'metabase-instance' in i['name']]
+      metabase_process_name = f"{info['group']}:{info['name']}"
+      supervisor.stopProcess(metabase_process_name)
+
+      # restart postgres, to terminate all current connections
+      info, = [i for i in
+         supervisor.getAllProcessInfo() if 'postgres' in i['name']]
+      postresql_process_name = f"{info['group']}:{info['name']}"
+      supervisor.stopProcess(postresql_process_name)
+      supervisor.startProcess(postresql_process_name)
+
+    subprocess.check_output(
+      os.path.join(
+        self.computer_partition_root_path, 'bin', 'postgresql-restore-backup'))
+
+    with self.slap.instance_supervisor_rpc as supervisor:
+      supervisor.startProcess(metabase_process_name)
+
+    for _ in range(30):
+      with contextlib.suppress(requests.exceptions.RequestException):
+        time.sleep(1)
+        resp = requests.post(
+          parse.urljoin(url, '/api/session'),
+          verify=False,
+          json={
+            "username": "youlooknicetoday@email.com",
+            "password": "passwordformbackup123"
+          }, timeout=5)
+        if resp.ok:
+          session = resp.json()
+          break
+    else:
+      resp.raise_for_status()
+    self.assertTrue(session.get('id'))
diff --git a/software/metabase/test/testdata/backup.pg_dump b/software/metabase/test/testdata/backup.pg_dump
new file mode 100644
index 0000000000000000000000000000000000000000..b9906dffe1ae8902a1e417f5761aa31dbffd490e
Binary files /dev/null and b/software/metabase/test/testdata/backup.pg_dump differ