diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8dc2303883c44a3a4ad40e059a734378c6887e6f..4f7dac6a327ee188433624ae348a8691d0eb4bf9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,6 +15,7 @@ stages:
   - linting
   - static-analysis
   - unit-tests
+  - integration-tests
 linting:
   stage: linting
   script:
@@ -34,3 +35,27 @@ unit_test:
   script:
     - cd devices
     - tox -e py37
+integration_test:
+  stage: integration-tests
+  allow_failure: true
+  tags:
+    - privileged
+  services:
+    - name: docker:20.10.8-dind
+  variables:
+    DOCKER_TLS_CERTDIR: "/certs"
+# Everything below does not work currently, we need a privileged container
+# that can run the dind service
+  before_script:
+    - sudo apt update
+    - sudo apt install -y docker.io
+    - export USER=$(id | awk -F'=' '{print $2}' | awk -F'(' '{print $2}' | awk -F')' '{print $1}')
+    - echo $USER
+#    - sudo usermod -aG docker $USER
+    - sudo docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+  script:
+    - touch /home/$USER/.Xauthority
+    - source bootstrap/etc/lofar20rc.sh
+    - export HOSTNAME=$(cat /run/systemd/netif/leases/2 | grep ^ADDRESS= | awk -F'=' '{print $2}')
+    - echo $HOSTNAME
+    - sudo $CI_PROJECT_DIR/sbin/run_integration_test.sh
diff --git a/CDB/LOFAR_ConfigDb.json b/CDB/LOFAR_ConfigDb.json
index 705f701556224fa4936e35916993aa2d4d05107e..197686104afa47aeb018b0c5caade4d286a4fe9b 100644
--- a/CDB/LOFAR_ConfigDb.json
+++ b/CDB/LOFAR_ConfigDb.json
@@ -684,6 +684,24 @@
                             "OPC_Time_Out": [
                                 "5.0"
                             ],
+                            "FPGA_sdp_info_station_id_RW_default": [
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901",
+                                "901"
+                            ],
                             "polled_attr": [
                                 "fpga_temp_r",
                                 "1000",
@@ -735,6 +753,60 @@
                             ],
                             "OPC_Time_Out": [
                                 "5.0"
+                            ],
+                            "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de",
+                                "6c:2b:59:97:cb:de"
+                            ],
+                            "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250",
+                                "10.99.250.250"
+                            ],
+                            "FPGA_sst_offload_hdr_udp_destination_port_RW_default": [
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001",
+                                "5001"
                             ]
                         }
                     }
diff --git a/CDB/integration_ConfigDb.json b/CDB/integration_ConfigDb.json
new file mode 100644
index 0000000000000000000000000000000000000000..b2f9cca6dc8db917942f35bb8be25e4cb88bdb93
--- /dev/null
+++ b/CDB/integration_ConfigDb.json
@@ -0,0 +1,64 @@
+{
+    "servers": {
+        "PCC": {
+            "LTS": {
+                "PCC": {
+                    "LTS/PCC/1": {
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "pypcc-sim"
+                            ],
+                            "OPC_Server_Port": [
+                                "4842"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "SDP": {
+            "LTS": {
+                "SDP": {
+                    "LTS/SDP/1": {
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "sdptr-sim"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "SST": {
+            "LTS": {
+                "SST": {
+                    "LTS/SST/1": {
+                        "properties": {
+                            "SST_Client_Port": [
+                                "5001"
+                            ],
+                            "OPC_Server_Name": [
+                                "sdptr-sim"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/devices/.stestr.conf b/devices/.stestr.conf
index ddc59860d5117ed8bdc44faeea1d893760b5520e..07147c8697683f270e9388da8b914c20cb8e4c45 100644
--- a/devices/.stestr.conf
+++ b/devices/.stestr.conf
@@ -1,3 +1,3 @@
 [DEFAULT]
-test_path=./test
+test_path=${TESTS_DIR:-./test}
 top_dir=./
diff --git a/devices/common/lofar_environment.py b/devices/common/lofar_environment.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c191e12b42c4ceb3f400c0d57e305826057eee2
--- /dev/null
+++ b/devices/common/lofar_environment.py
@@ -0,0 +1,6 @@
+#
+#   Change manually the method to switch between modes
+#
+
+def isProduction():
+    return False
diff --git a/devices/devices/hardware_device.py b/devices/devices/hardware_device.py
index 524c378c1256eb5cc09fb9af12b21d5d0f781a09..c0e7df614d95e40f9816f9332f2832c8f3d4166c 100644
--- a/devices/devices/hardware_device.py
+++ b/devices/devices/hardware_device.py
@@ -14,8 +14,8 @@
 from abc import ABCMeta, abstractmethod
 
 # PyTango imports
-from tango.server import Device, command, DeviceMeta
-from tango import DevState, DebugIt
+from tango.server import Device, command, DeviceMeta, attribute
+from tango import DevState, DebugIt, Attribute, DeviceProxy
 # Additional import
 
 from clients.attribute_wrapper import attribute_wrapper
@@ -161,7 +161,6 @@ class hardware_device(Device, metaclass=AbstractDeviceMetas):
         self.configure_for_fault()
         self.set_state(DevState.FAULT)
 
-
     # functions that can or must be overloaded
     def configure_for_fault(self):
         pass
@@ -192,3 +191,36 @@ class hardware_device(Device, metaclass=AbstractDeviceMetas):
 
         self.Off()
         self.debug_stream("Shut down.  Good bye.")
+
+    @command()
+    @only_in_states([DevState.STANDBY, DevState.ON])
+    @DebugIt()
+    @log_exceptions()
+    def set_defaults(self):
+        """ Set hardware points to their default value.
+
+            A hardware point XXX is set to the value of the object member named XXX_default, if it exists.
+            XXX_default can be f.e. a constant, or a device_property.
+        """
+
+        # we cannot write directly to our attribute, as that would not
+        # trigger a write_{name} call. See https://www.tango-controls.org/community/forum/c/development/c/accessing-own-deviceproxy-class/?page=1#post-2021
+
+        # obtain a proxy to myself, to write values
+        proxy = DeviceProxy(self.get_name())
+
+        # for all my members
+        for name in dir(self):
+            attr = getattr(self, name)
+            # check if it's an attribute, and there is a default value available
+            if isinstance(attr, Attribute) and hasattr(self, f"{name}_default"):
+                try:
+                    default_value = getattr(self, f"{name}_default")
+
+                    # set the attribute to the configured default
+                    self.debug_stream(f"Setting attribute {name} to {default_value}")
+                    proxy.write_attribute(name, default_value)
+                except Exception as e:
+                    # log which attribute we're addressing
+                    raise Exception(f"Cannot assign default to attribute {name}") from e
+
diff --git a/devices/devices/sdp/sdp.py b/devices/devices/sdp/sdp.py
index b611f5a9f0289981da65b16fa9dd5224261a1749..c1730ab621f0da57bc486240ec662c84f6cde1ed 100644
--- a/devices/devices/sdp/sdp.py
+++ b/devices/devices/sdp/sdp.py
@@ -69,6 +69,23 @@ class SDP(hardware_device):
         mandatory=True
     )
 
+    FPGA_processing_enable_RW_default = device_property(
+        dtype='DevVarBooleanArray',
+        mandatory=False,
+        default_value=[True] * 16
+    )
+
+    FPGA_wg_enable_RW_default = device_property(
+        dtype='DevVarBooleanArray',
+        mandatory=False,
+        default_value=[[False] * 12] * 16
+    )
+    
+    FPGA_sdp_info_station_id_RW_default = device_property(
+        dtype='DevVarULongArray',
+        mandatory=True
+    )
+
     # ----------
     # Attributes
     # ----------
diff --git a/devices/devices/sdp/sst.py b/devices/devices/sdp/sst.py
index 1a62a4edcf28c84f7be865d38f7d5312417b497e..792162fd50adcefdb420fd621e853261d83da17b 100644
--- a/devices/devices/sdp/sst.py
+++ b/devices/devices/sdp/sst.py
@@ -48,6 +48,21 @@ class SST(Statistics):
     # Device Properties
     # -----------------
 
+    FPGA_sst_offload_hdr_eth_destination_mac_RW_default = device_property(
+        dtype='DevVarStringArray',
+        mandatory=True
+    )
+
+    FPGA_sst_offload_hdr_ip_destination_address_RW_default = device_property(
+        dtype='DevVarStringArray',
+        mandatory=True
+    )
+
+    FPGA_sst_offload_hdr_udp_destination_port_RW_default = device_property(
+        dtype='DevVarUShortArray',
+        mandatory=True
+    )
+
     # ----------
     # Attributes
     # ----------
diff --git a/devices/integration_test/README.md b/devices/integration_test/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3292bfa0049b5c2312f8e0536e00cc581433ed61
--- /dev/null
+++ b/devices/integration_test/README.md
@@ -0,0 +1,26 @@
+# Integration Tests
+
+## Approach
+
+A special docker container is build to perform the integration tests. This
+container will be build by the makefiles but should only be started by the
+dedicated integration test script. This script will ensure that other containers
+are running and are in the required state.
+
+* Launch pypcc-sim and sdptr-sim simulators.
+* Reconfigure dsconfig to use these simulators.
+* Create and start the integration-test container.
+
+## Running
+
+**Warning running these tests will make changes to your CDB database config!**
+
+```shell
+source bootstrap/etc/lofar20rc.sh
+$LOFAR20_DIR/sbin/run_integration_test.sh
+```
+
+## Limitations
+
+Our makefile will always launch the new container upon creation, resulting in
+the integration tests actually being run twice.
\ No newline at end of file
diff --git a/devices/integration_test/__init__.py b/devices/integration_test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/integration_test/base.py b/devices/integration_test/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..92601ec2d440753ae7f7be22fcbfad0c5028875c
--- /dev/null
+++ b/devices/integration_test/base.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+import unittest
+import testscenarios
+
+
+class BaseIntegrationTestCase(testscenarios.WithScenarios, unittest.TestCase):
+    """Integration test base class."""
+
+    def setUp(self):
+        super().setUp()
+
+
+class IntegrationTestCase(BaseIntegrationTestCase):
+    """Integration test case base class for all unit tests."""
+
+    def setUp(self):
+        super().setUp()
diff --git a/devices/integration_test/client/__init__.py b/devices/integration_test/client/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/integration_test/client/test_apsct_sim.py b/devices/integration_test/client/test_apsct_sim.py
new file mode 100644
index 0000000000000000000000000000000000000000..775c34cd207699f7febb435000314c65db97b66a
--- /dev/null
+++ b/devices/integration_test/client/test_apsct_sim.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+from opcua import Client
+
+from integration_test import base
+
+
+class TestAPSCTSim(base.IntegrationTestCase):
+
+    def setUp(self):
+        super(TestAPSCTSim, self).setUp()
+
+    def test_opcua_connection(self):
+        """Check if we can connect to apsct-sim"""
+
+        #TODO(Corne): Replace to APSCT name once simulator name has changed
+        client = Client("opc.tcp://pypcc-sim:4842")
+        root_node = None
+
+        try:
+            client.connect()
+            root_node = client.get_root_node()
+        finally:
+            client.disconnect()
+
+        self.assertNotEqual(None, root_node)
diff --git a/devices/integration_test/client/test_sdptr_sim.py b/devices/integration_test/client/test_sdptr_sim.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ba48e7d761c7ef366c8690e2d114c773de7311d
--- /dev/null
+++ b/devices/integration_test/client/test_sdptr_sim.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+from opcua import Client
+
+from integration_test import base
+
+
+class TestSDPTRSim(base.IntegrationTestCase):
+
+    def setUp(self):
+        super(TestSDPTRSim, self).setUp()
+
+    def test_opcua_connection(self):
+        """Check if we can connect to sdptr-sim"""
+
+        client = Client("opc.tcp://sdptr-sim:4840")
+        root_node = None
+
+        try:
+            client.connect()
+            root_node = client.get_root_node()
+        finally:
+            client.disconnect()
+
+        self.assertNotEqual(None, root_node)
diff --git a/devices/integration_test/devices/__init__.py b/devices/integration_test/devices/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/integration_test/devices/test_device_pcc.py b/devices/integration_test/devices/test_device_pcc.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3b7a4672dbb18790d19144aeb35bcacd68e4bfb
--- /dev/null
+++ b/devices/integration_test/devices/test_device_pcc.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+import time
+
+from tango import DeviceProxy
+from tango._tango import DevState
+
+from integration_test import base
+
+
+class TestDevicePCC(base.IntegrationTestCase):
+
+    def setUp(self):
+        super(TestDevicePCC, self).setUp()
+
+    def tearDown(self):
+        """Turn device Off in teardown to prevent blocking tests"""
+        d = DeviceProxy("LTS/PCC/1")
+
+        try:
+            d.Off()
+        except Exception as e:
+            """Failing to turn Off devices should not raise errors here"""
+            print(f"Failed to turn device off in teardown {e}")
+
+    def test_device_proxy_pcc(self):
+        """Test if we can successfully create a DeviceProxy and fetch state"""
+
+        d = DeviceProxy("LTS/PCC/1")
+
+        self.assertEqual(DevState.OFF, d.state())
+
+    def test_device_pcc_initialize(self):
+        """Test if we can transition to standby"""
+
+        d = DeviceProxy("LTS/PCC/1")
+
+        d.initialise()
+
+        self.assertEqual(DevState.STANDBY, d.state())
+
+    def test_device_pcc_on(self):
+        """Test if we can transition to on"""
+
+        d = DeviceProxy("LTS/PCC/1")
+
+        d.initialise()
+
+        d.on()
+
+        self.assertEqual(DevState.ON, d.state())
diff --git a/devices/integration_test/devices/test_device_sdp.py b/devices/integration_test/devices/test_device_sdp.py
new file mode 100644
index 0000000000000000000000000000000000000000..cfd656748054cb21e0e3bb2110ce60072d9fb28a
--- /dev/null
+++ b/devices/integration_test/devices/test_device_sdp.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+import time
+
+from tango import DeviceProxy
+from tango._tango import DevState
+
+from integration_test import base
+
+
+class TestDeviceSDP(base.IntegrationTestCase):
+
+    def setUp(self):
+        """Intentionally recreate the device object in each test"""
+        super(TestDeviceSDP, self).setUp()
+
+    def tearDown(self):
+        """Turn device Off in teardown to prevent blocking tests"""
+        d = DeviceProxy("LTS/SDP/1")
+
+        try:
+            d.Off()
+        except Exception as e:
+            """Failing to turn Off devices should not raise errors here"""
+            print(f"Failed to turn device off in teardown {e}")
+
+    def test_device_proxy_sdp(self):
+        """Test if we can successfully create a DeviceProxy and fetch state"""
+
+        d = DeviceProxy("LTS/SDP/1")
+
+        self.assertEqual(DevState.OFF, d.state())
+
+    def test_device_sdp_initialize(self):
+        """Test if we can transition to standby"""
+
+        d = DeviceProxy("LTS/SDP/1")
+
+        d.initialise()
+
+        self.assertEqual(DevState.STANDBY, d.state())
+
+    def test_device_sdp_on(self):
+        """Test if we can transition to on"""
+
+        d = DeviceProxy("LTS/SDP/1")
+
+        d.initialise()
+
+        d.on()
+
+        self.assertEqual(DevState.ON, d.state())
diff --git a/devices/toolkit/startup.py b/devices/toolkit/startup.py
index 0f4bcbe702b1bd1edb873234763d56455b6009b4..e1cc092b01b3714d80f0b8ca827856bde451c78b 100644
--- a/devices/toolkit/startup.py
+++ b/devices/toolkit/startup.py
@@ -1,36 +1,49 @@
 #! /usr/bin/env python3
+import tango
+import logging
 
+logger = logging.getLogger()
 
-def startup(device: str, force_restart: bool):
+def startup(device: str, force_restart: bool) -> tango.DeviceProxy:
     '''
     Start a LOFAR Tango device:
     pcc = startup(device = 'LTS/PCC/1', force_restart = False)
     '''
-    import tango
     proxy = tango.DeviceProxy(device)
     state = proxy.state()
 
+    # go to OFF, but only if force_restart is True
     if force_restart is True:
-        print("Forcing device {} restart.".format(device))
+        logger.warning(f"Forcing device {device} restart.")
         proxy.off()
         state = proxy.state()
         if state is not tango._tango.DevState.OFF:
-            print("Device {} cannot perform off although restart has been enforced, state = {}.  Please investigate.".format(device, state))
+            logger.error(f"Device {device} cannot perform off although restart has been enforced, state = {state}.  Please investigate.")
             return proxy
+
     if state is not tango._tango.DevState.OFF:
-        print("Device {} is not in OFF state, cannot start it.  state = {}".format(device, state))
+        logger.error(f"Device {device} is not in OFF state, cannot start it.  state = {state}")
         return proxy
-    print("Device {} is in OFF, performing initialisation.".format(device))
+
+    # Initialise device
+    logger.info(f"Device {device} is in OFF, performing initialisation.")
     proxy.initialise()
     state = proxy.state()
     if state is not tango._tango.DevState.STANDBY:
-        print("Device {} cannot perform initialise, state = {}.  Please investigate.".format(device, state))
+        logger.error(f"Device {device} cannot perform initialise, state = {state}.  Please investigate.")
         return proxy
-    print("Device {} is in STANDBY, performing on.".format(device))
+
+    # Set default values
+    logger.info(f"Device {device} is in STANDBY, setting default values.")
+    proxy.set_defaults()
+
+    # Turn on device
+    logger.info(f"Device {device} is in STANDBY, performing on.")
     proxy.on()
     state = proxy.state()
     if state is not tango._tango.DevState.ON:
-        print("Device {} cannot perform on, state = {}.  Please investigate.".format(device, state))
+        logger.error(f"Device {device} cannot perform on, state = {state}.  Please investigate.")
     else:
-        print("Device {} has successfully reached ON state.".format(device))
+        logger.info(f"Device {device} has successfully reached ON state.")
+
     return proxy
diff --git a/devices/tox.ini b/devices/tox.ini
index 18c6cda38751d7bc447e8fb23d92e63b64288ddb..94d33c3e392272ac7341e039791f567cf2a7b9b4 100644
--- a/devices/tox.ini
+++ b/devices/tox.ini
@@ -17,6 +17,14 @@ deps = -r{toxinidir}/test-requirements.txt
     -r{toxinidir}/../docker-compose/lofar-device-base/lofar-requirements.txt
 commands = stestr run {posargs}
 
+[testenv:integration]
+; Warning running integration tests will make changes to your docker system!
+; These tests should only be run by the integration-test docker container.
+passenv = TANGO_HOST
+setenv = TESTS_DIR=./integration_test
+commands =
+    stestr run --serial
+
 ; TODO(Corne): Integrate Hacking to customize pep8 rules
 [testenv:pep8]
 commands =
diff --git a/docker-compose/archiver.yml b/docker-compose/archiver.yml
index 41d5df160e011ca1aad79828bf2fd3c941958620..8006ece3b86f0013a5eedc1e066dc4c2f07b73af 100644
--- a/docker-compose/archiver.yml
+++ b/docker-compose/archiver.yml
@@ -15,7 +15,7 @@ services:
       - MYSQL_USER=tango
       - MYSQL_PASSWORD=tango
       - TANGO_HOST=${TANGO_HOST}
-    restart: on-failure
+    restart: unless-stopped
 
   hdbpp-es:
       image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-archiver:2021-05-28
@@ -34,6 +34,7 @@ services:
           wait-for-it.sh archiver-maria-db:3306 --timeout=30 --strict --
           wait-for-it.sh ${TANGO_HOST} --timeout=30 --strict --
                hdbppes-srv 01"
+      restart: unless-stopped
 
   hdbpp-cm:
       image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-archiver:${TANGO_ARCHIVER_VERSION}
diff --git a/docker-compose/elk.yml b/docker-compose/elk.yml
index cce66839b499caa0b8948eaeb0c5cc65176be2c9..bf6e22e3de6ea82571acba0ac8e7c69f3eeb2941 100644
--- a/docker-compose/elk.yml
+++ b/docker-compose/elk.yml
@@ -38,3 +38,4 @@ services:
       - "5959:5959" # logstash tcp json input
     depends_on:
       - elk-configure-host
+    restart: unless-stopped
diff --git a/docker-compose/integration-test.yml b/docker-compose/integration-test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e6a0e54939179ba0c4f5b043da3191dd9e11945d
--- /dev/null
+++ b/docker-compose/integration-test.yml
@@ -0,0 +1,29 @@
+#
+# Docker compose file that launches integration tests
+#
+# Defines:
+#   - integration: Integration testing
+#
+version: '2'
+
+services:
+  integration-test:
+    build:
+        context: itango
+        args:
+            SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
+    container_name: ${CONTAINER_NAME_PREFIX}integration-test
+    networks:
+      - control
+    volumes:
+        - ${TANGO_LOFAR_CONTAINER_MOUNT}
+    environment:
+      - TANGO_HOST=${TANGO_HOST}
+    working_dir: ${TANGO_LOFAR_CONTAINER_DIR}/devices
+    entrypoint:
+      - /usr/local/bin/wait-for-it.sh
+      - ${TANGO_HOST}
+      - --timeout=30
+      - --strict
+      - --
+      - tox -e integration
diff --git a/docker-compose/itango.yml b/docker-compose/itango.yml
index 941974e8770823c6a680de9ffcf95f301163663d..34161eb43f752716f28d44170898d94c4b6d76cd 100644
--- a/docker-compose/itango.yml
+++ b/docker-compose/itango.yml
@@ -37,4 +37,4 @@ services:
       - --strict
       - --
       - /venv/bin/itango3
-    restart: on-failure
+    restart: unless-stopped
diff --git a/docker-compose/jupyter.yml b/docker-compose/jupyter.yml
index 36cc0acbcd32631a9cf8e6bb1f10ecfb77362cf0..989601cf8d858f493ba47ed607a9e4cd5a6d2770 100644
--- a/docker-compose/jupyter.yml
+++ b/docker-compose/jupyter.yml
@@ -38,4 +38,4 @@ services:
       - --strict
       - --
       - /usr/bin/tini -- /usr/local/bin/jupyter-notebook --port=8888 --no-browser --ip=0.0.0.0 --allow-root --NotebookApp.token= --NotebookApp.password=
-    restart: on-failure
+    restart: unless-stopped
diff --git a/docker-compose/jupyter/Dockerfile b/docker-compose/jupyter/Dockerfile
index 2382319bc1a26e4e9f75b4ee8bdb45c893d23528..29f736cdca2fc843750612c6780ea7ad2dfa516e 100644
--- a/docker-compose/jupyter/Dockerfile
+++ b/docker-compose/jupyter/Dockerfile
@@ -5,12 +5,31 @@ FROM ${SOURCE_IMAGE}
 # that are needed for temporary storage the proper owner and access rights.
 ARG CONTAINER_EXECUTION_UID=1000
 
+# Create homedir
+ENV HOME=/home/user
+RUN sudo mkdir -p ${HOME}
+RUN sudo chown ${CONTAINER_EXECUTION_UID} -R ${HOME}
+USER ${CONTAINER_EXECUTION_UID}
+
 RUN sudo pip3 install jupyter
 RUN sudo pip3 install ipykernel
 RUN sudo pip3 install jupyter_bokeh
 # Install matplotlib, jupyterplot
 RUN sudo pip3 install matplotlib jupyterplot
 
+# Allow Download as -> PDF via html
+RUN sudo pip3 install nbconvert
+RUN sudo pip3 install notebook-as-pdf
+# pyppeteer-install installs in the homedir, so run it as the user that will execute the notebook
+RUN pyppeteer-install
+
+# see https://github.com/jupyter/nbconvert/issues/1434
+RUN sudo bash -c "echo DEFAULT_ARGS += [\\\"--no-sandbox\\\"] >> /usr/local/lib/python3.7/dist-packages/pyppeteer/launcher.py"
+RUN sudo apt-get install -y gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libcairo-gobject2 libxinerama1 libgtk2.0-0 libpangoft2-1.0-0 libthai0 libpixman-1-0 libxcb-render0 libharfbuzz0b libdatrie1 libgraphite2-3 libgbm1
+
+# Allow Download as -> PDF via LaTeX
+RUN sudo apt-get install -y texlive-xetex texlive-fonts-recommended texlive-latex-recommended
+
 # Configure jupyter_bokeh
 RUN sudo mkdir -p /usr/share/jupyter /usr/etc
 RUN sudo chmod a+rwx /usr/share/jupyter /usr/etc
@@ -19,6 +38,7 @@ RUN sudo jupyter nbextension enable jupyter_bokeh --py --sys-prefix
 
 # Install profiles for ipython & jupyter
 COPY ipython-profiles /opt/ipython-profiles/
+RUN sudo chown ${CONTAINER_EXECUTION_UID} -R /opt/ipython-profiles
 COPY jupyter-kernels /usr/local/share/jupyter/kernels/
 
 # Install patched jupyter executable
@@ -34,8 +54,3 @@ ENV JUPYTER_RUNTIME_DIR=/tmp
 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/bin/tini
 RUN sudo chmod +x /usr/bin/tini
 
-# Make sure Jupyter can write to the home directory
-ENV HOME=/home/user
-RUN sudo mkdir -p ${HOME}
-RUN sudo chown ${CONTAINER_EXECUTION_UID} -R ${HOME}
-RUN sudo chown ${CONTAINER_EXECUTION_UID} -R /opt/ipython-profiles
diff --git a/docker-compose/pypcc-sim/Dockerfile b/docker-compose/pypcc-sim/Dockerfile
index 4040339b1dc98ebe8e02b51c6b3b75aab55bb3d4..bf3e34d6a5a7c4660aebb4e0006e8fc73ec5665a 100644
--- a/docker-compose/pypcc-sim/Dockerfile
+++ b/docker-compose/pypcc-sim/Dockerfile
@@ -1,7 +1,9 @@
 FROM ubuntu:20.04
 
+COPY requirements.txt /requirements.txt
+
 RUN apt-get update && apt-get install -y python3 python3-pip python3-yaml git && \
-    pip3 install opcua numpy recordclass && \
+    pip3 install -r requirements.txt && \
     git clone --depth 1 --branch master https://git.astron.nl/lofar2.0/pypcc
 
 WORKDIR /pypcc
diff --git a/docker-compose/pypcc-sim/requirements.txt b/docker-compose/pypcc-sim/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2cd015945c044fcd1e39a823f49a807fc519ac67
--- /dev/null
+++ b/docker-compose/pypcc-sim/requirements.txt
@@ -0,0 +1,3 @@
+opcua
+numpy
+recordclass>=0.16,<0.16.1
\ No newline at end of file
diff --git a/docker-compose/sdptr-sim/Dockerfile b/docker-compose/sdptr-sim/Dockerfile
index ed6ac8d35059fda67231a0dc17c71c3a5983b13c..fa23fe4d6458f4b7023c24b36774566cbac2163c 100644
--- a/docker-compose/sdptr-sim/Dockerfile
+++ b/docker-compose/sdptr-sim/Dockerfile
@@ -17,4 +17,4 @@ RUN cd /sdptr && \
     bash -c "make -j `nproc` install"
 
 WORKDIR /sdptr/src
-CMD ["sdptr", "--configfile=uniboard.conf", "--nodaemon"]
+CMD ["sdptr", "--type=LTS", "--configfile=uniboard.conf", "--nodaemon"]
diff --git a/docker-compose/tango.yml b/docker-compose/tango.yml
index b3a860d7b55ea71c5e2bc23895b7b57040e3f216..9fa0f5cde06f91b7cdc078f5c6481b013442e5ae 100644
--- a/docker-compose/tango.yml
+++ b/docker-compose/tango.yml
@@ -28,7 +28,7 @@ services:
       - tangodb:/var/lib/mysql
     ports:
       - "3306:3306"
-    restart: on-failure
+    restart: unless-stopped
 
   databaseds:
     image: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-cpp:${TANGO_CPP_VERSION}
@@ -55,4 +55,4 @@ services:
       - "2"
       - -ORBendPoint
       - giop:tcp::10000
-    restart: on-failure
+    restart: unless-stopped
diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d54163625541c13816bf0309c09a2713ce35add9
--- /dev/null
+++ b/sbin/run_integration_test.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# Check if lofar20rc.sh is sourced and environment variables are set.
+if [ -z "$LOFAR20_DIR" ]; then
+  echo "\$LOFAR20_DIR is unset or blank, is lofar20rc.sh sourced correctly?"
+  exit 1
+fi
+
+# Start all required containers
+cd "$LOFAR20_DIR/docker-compose" || exit 1
+make start databaseds dsconfig device-sdp device-pcc jupyter elk sdptr-sim pypcc-sim
+
+# Update the dsconfig
+cd "$TANGO_LOFAR_LOCAL_DIR" || exit 1
+sbin/update_ConfigDb.sh CDB/integration_ConfigDb.json
+
+# Start the integration test
+cd "$LOFAR20_DIR/docker-compose" || exit 1
+make start integration-test
+
+# Run the integration test with the output displayed on stdout
+docker start -a integration-test
\ No newline at end of file