From 0af473a275a033b3bd22b667b331a1e402b82d51 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Thu, 2 Nov 2023 08:53:51 +0000
Subject: [PATCH] L2SS-1336: Deploy station control software to nomad

---
 .editorconfig                                 |    2 +-
 .gitignore                                    |    7 +
 .gitlab-ci.yml                                |  117 +-
 CDB/README.md                                 |   22 +-
 CDB/stations/cs001.json                       |  254 +---
 CDB/stations/hba_core.json                    |  326 +++--
 CDB/stations/hba_remote.json                  |  150 ++-
 CDB/stations/lba.json                         |  162 ++-
 CDB/stations/testenv_cs001.json.orig          | 1186 +++++++++++++++++
 README.md                                     |   43 +
 bin/dump_ConfigDb.sh                          |   10 +-
 bin/itango_console.sh                         |   48 +-
 bin/itango_shell.sh                           |    6 +-
 deploy/README.md                              |   44 -
 deploy/ansible.cfg                            |    3 -
 deploy/deploy.yml                             |  106 --
 deploy/tasks/check_binary_install.yml         |    9 -
 deploy/tasks/update_database_config.yml       |    5 -
 deploy/tasks/verify_database_config.yml       |    9 -
 docker-compose/Makefile                       |   38 +-
 docker-compose/README.md                      |    5 +-
 docker-compose/device-afh.yml                 |    3 +-
 docker-compose/device-afl.yml                 |    2 +-
 docker-compose/device-aps.yml                 |    3 +-
 docker-compose/device-apsct.yml               |    3 +-
 docker-compose/device-apspu.yml               |    3 +-
 docker-compose/device-beamlet.yml             |    3 +-
 docker-compose/device-bst.yml                 |    3 +-
 docker-compose/device-calibration.yml         |    5 +-
 docker-compose/device-ccd.yml                 |    3 +-
 docker-compose/device-configuration.yml       |    5 +-
 docker-compose/device-digitalbeam.yml         |    3 +-
 docker-compose/device-docker.yml              |    3 +-
 docker-compose/device-ec.yml                  |    3 +-
 docker-compose/device-observation-control.yml |    5 +-
 docker-compose/device-pcon.yml                |    3 +-
 docker-compose/device-psoc.yml                |    3 +-
 docker-compose/device-recvh.yml               |    3 +-
 docker-compose/device-recvl.yml               |    3 +-
 docker-compose/device-sdp.yml                 |    3 +-
 docker-compose/device-sdpfirmware.yml         |    3 +-
 docker-compose/device-sst.yml                 |    3 +-
 docker-compose/device-station-manager.yml     |    3 +-
 docker-compose/device-temperature-manager.yml |    3 +-
 docker-compose/device-tilebeam.yml            |    3 +-
 docker-compose/device-unb2.yml                |    3 +-
 docker-compose/device-xst.yml                 |    3 +-
 docker-compose/grafana/Dockerfile             |    1 +
 docker-compose/http-json-schemas.yml          |   32 -
 docker-compose/http-json-schemas/Dockerfile   |    2 -
 docker-compose/http-json-schemas/README.md    |    3 -
 docker-compose/integration-test.yml           |    1 +
 docker-compose/jupyter-lab.yml                |    9 +-
 docker-compose/jupyter-lab/Dockerfile         |   89 +-
 .../startup/01-devices.py                     |   16 +-
 .../startup/02-stationcontrol.py              |    7 +
 docker-compose/jupyter-lab/jupyter-notebook   |   30 -
 .../jupyter-lab/jupyter_notebook_config.py    |    6 +
 docker-compose/jupyter-lab/requirements.txt   |   11 +-
 docker-compose/logstash.yml                   |   52 -
 docker-compose/logstash/Dockerfile            |   11 -
 docker-compose/logstash/README.md             |   41 -
 docker-compose/logstash/logstash.conf         |  220 ---
 docker-compose/logstash/logstash.yml          |    4 -
 docker-compose/logstash/patterns/mariadb.txt  |    2 -
 docker-compose/object-storage.yml             |   34 +-
 docker-compose/prometheus/prometheus.yml      |   19 +-
 docker-compose/tango-prometheus-exporter.yml  |    6 -
 .../tango-prometheus-exporter/Dockerfile      |    2 +-
 .../code/pip-requirements.txt                 |    1 -
 .../code/tango-prometheus-client.py           |   83 +-
 docker-compose/tango.yml                      |    3 +-
 infra/dev/jobs/.keep                          |    0
 infra/dev/jobs/station/.keep                  |    0
 infra/dev/main.hcl                            |   32 +
 infra/dev/nomad/config/nomad/client.hcl       |   55 +
 infra/dev/nomad/config/nomad/consul.hcl       |   21 +
 infra/dev/nomad/nomad.hcl                     |  105 ++
 infra/dev/nomad/tmp/.keep                     |    0
 infra/dev/nomad/variables.hcl                 |    7 +
 infra/dev/services.hcl                        |   21 +
 infra/env.yaml                                |   72 +
 infra/imds/meta-data                          |    3 -
 infra/imds/user-data                          |  205 ---
 infra/install-station.sh                      |  159 ---
 infra/jobs/Makefile                           |   12 +
 infra/jobs/station/Makefile                   |   18 +
 infra/jobs/station/device-server.levant.nomad |   89 ++
 infra/jobs/station/dsconfig.levant.nomad      |   52 +
 infra/jobs/station/ec-sim.levant.nomad        |   35 +
 infra/jobs/station/jupyter.levant.nomad       |   69 +
 infra/jobs/station/logging.levant.nomad       |  155 +++
 infra/jobs/station/monitoring.levant.nomad    |  395 ++++++
 infra/jobs/station/nomad-client.nomad         |  391 ++++++
 .../jobs/station/object-storage.levant.nomad  |  115 ++
 infra/jobs/station/sdptr.levant.nomad         |   43 +
 .../tango-prometheus-exporter.levant.nomad    |  103 ++
 infra/jobs/station/tango.levant.nomad         |  164 +++
 infra/qemu-test.pkr.hcl                       |  459 -------
 infra/start-cloud-init.sh                     |   14 -
 infra/station-config/consul.hcl               |   57 -
 infra/station-config/nomad.hcl                |   59 -
 sbin/dsconfig.sh                              |   96 ++
 sbin/load_ConfigDb.sh                         |   21 +-
 sbin/prepare_dev_env.sh                       |   74 +
 sbin/run_integration_test.sh                  |  126 +-
 sbin/run_service_test.sh                      |   39 +-
 sbin/tag_and_push_docker_image.sh             |    4 +-
 sbin/update_ConfigDb.sh                       |   32 +-
 setup.sh                                      |   27 +-
 tangostationcontrol/MANIFEST.in               |    2 +
 tangostationcontrol/VERSION                   |    2 +-
 .../docs/source/configure_station.rst         |    2 +-
 .../docs/source/devices/configure.rst         |    4 +-
 tangostationcontrol/requirements.txt          |    6 +-
 tangostationcontrol/setup.cfg                 |   29 +-
 .../beam/managers/_base.py                    |    2 +-
 .../beam/managers/_digitalbeam.py             |    2 +-
 .../beam/managers/_tilebeam.py                |    2 +-
 .../tangostationcontrol/common/calibration.py |    5 +-
 .../common/configuration.py                   |   21 +-
 .../tangostationcontrol/common/consul.py      |   42 +
 .../{devices => common}/device_decorators.py  |    0
 .../common/lofar_logging.py                   |   37 +-
 .../configuration/__init__.py                 |   17 +-
 .../configuration/_schemas.py                 |   24 +
 .../configuration/configuration_base.py       |   45 +-
 .../configuration/schemas}/dithering.json     |    1 +
 .../configuration/schemas}/hba.json           |    3 +-
 .../schemas}/observation-settings.json        |    7 +-
 .../configuration/schemas}/pointing.json      |    3 +-
 .../configuration/schemas}/sap.json           |    5 +-
 .../schemas}/station-configuration.json       |   26 +-
 .../device_server/__init__.py                 |   72 +
 .../tangostationcontrol/devices/__init__.py   |   60 +
 .../tangostationcontrol/devices/aps.py        |   15 +-
 .../tangostationcontrol/devices/apsct.py      |   17 +-
 .../tangostationcontrol/devices/apspu.py      |   21 +-
 .../antennafield_device.py                    |   23 +-
 .../base_device_classes/beam_device.py        |   22 +-
 .../base_device_classes/lofar_device.py       |   28 +-
 .../base_device_classes/opcua_device.py       |   62 +-
 .../base_device_classes/power_hierarchy.py    |    2 +-
 .../base_device_classes/recv_device.py        |   14 +-
 .../devices/calibration.py                    |   26 +-
 .../tangostationcontrol/devices/ccd.py        |   17 +-
 .../devices/configuration.py                  |   17 +-
 .../tangostationcontrol/devices/docker.py     |   16 +-
 .../tangostationcontrol/devices/ec.py         |   19 +-
 .../devices/observation.py                    |   21 +-
 .../devices/observation_control.py            |   21 +-
 .../tangostationcontrol/devices/pcon.py       |   19 +-
 .../tangostationcontrol/devices/psoc.py       |   23 +-
 .../tangostationcontrol/devices/recv/recvh.py |   18 +-
 .../tangostationcontrol/devices/recv/recvl.py |   15 +-
 .../devices/sdp/beamlet.py                    |   12 +-
 .../tangostationcontrol/devices/sdp/bst.py    |   18 +-
 .../devices/sdp/digitalbeam.py                |   14 +-
 .../devices/sdp/firmware.py                   |   18 +-
 .../tangostationcontrol/devices/sdp/sdp.py    |   13 +-
 .../tangostationcontrol/devices/sdp/sst.py    |   18 +-
 .../tangostationcontrol/devices/sdp/xst.py    |   11 +-
 .../devices/station_manager.py                |   28 +-
 .../devices/temperature_manager.py            |   16 +-
 .../tangostationcontrol/devices/tilebeam.py   |   11 +-
 .../tangostationcontrol/devices/unb2.py       |   17 +-
 .../tangostationcontrol/metrics/__init__.py   |    9 +
 .../metrics/_collectors.py                    |   43 +
 .../metrics/_decorators.py                    |  133 ++
 tangostationcontrol/test-requirements.txt     |    1 -
 .../test/common/test_calibration.py           |    6 +
 .../test/configuration/_mock_requests.py      |  246 ----
 .../test_observation_settings.py              |   35 +-
 .../test/configuration/test_pointing.py       |   27 +-
 .../test/configuration/test_sap_settings.py   |   31 +-
 175 files changed, 4924 insertions(+), 3267 deletions(-)
 create mode 100644 CDB/stations/testenv_cs001.json.orig
 delete mode 100644 deploy/README.md
 delete mode 100644 deploy/ansible.cfg
 delete mode 100644 deploy/deploy.yml
 delete mode 100644 deploy/tasks/check_binary_install.yml
 delete mode 100644 deploy/tasks/update_database_config.yml
 delete mode 100644 deploy/tasks/verify_database_config.yml
 delete mode 100644 docker-compose/http-json-schemas.yml
 delete mode 100644 docker-compose/http-json-schemas/Dockerfile
 delete mode 100644 docker-compose/http-json-schemas/README.md
 delete mode 100755 docker-compose/jupyter-lab/jupyter-notebook
 create mode 100644 docker-compose/jupyter-lab/jupyter_notebook_config.py
 delete mode 100644 docker-compose/logstash.yml
 delete mode 100644 docker-compose/logstash/Dockerfile
 delete mode 100644 docker-compose/logstash/README.md
 delete mode 100644 docker-compose/logstash/logstash.conf
 delete mode 100644 docker-compose/logstash/logstash.yml
 delete mode 100644 docker-compose/logstash/patterns/mariadb.txt
 create mode 100644 infra/dev/jobs/.keep
 create mode 100644 infra/dev/jobs/station/.keep
 create mode 100644 infra/dev/main.hcl
 create mode 100644 infra/dev/nomad/config/nomad/client.hcl
 create mode 100644 infra/dev/nomad/config/nomad/consul.hcl
 create mode 100644 infra/dev/nomad/nomad.hcl
 create mode 100644 infra/dev/nomad/tmp/.keep
 create mode 100644 infra/dev/nomad/variables.hcl
 create mode 100644 infra/dev/services.hcl
 create mode 100644 infra/env.yaml
 delete mode 100644 infra/imds/meta-data
 delete mode 100644 infra/imds/user-data
 delete mode 100644 infra/install-station.sh
 create mode 100644 infra/jobs/Makefile
 create mode 100644 infra/jobs/station/Makefile
 create mode 100644 infra/jobs/station/device-server.levant.nomad
 create mode 100644 infra/jobs/station/dsconfig.levant.nomad
 create mode 100644 infra/jobs/station/ec-sim.levant.nomad
 create mode 100644 infra/jobs/station/jupyter.levant.nomad
 create mode 100644 infra/jobs/station/logging.levant.nomad
 create mode 100644 infra/jobs/station/monitoring.levant.nomad
 create mode 100644 infra/jobs/station/nomad-client.nomad
 create mode 100644 infra/jobs/station/object-storage.levant.nomad
 create mode 100644 infra/jobs/station/sdptr.levant.nomad
 create mode 100644 infra/jobs/station/tango-prometheus-exporter.levant.nomad
 create mode 100644 infra/jobs/station/tango.levant.nomad
 delete mode 100644 infra/qemu-test.pkr.hcl
 delete mode 100755 infra/start-cloud-init.sh
 delete mode 100644 infra/station-config/consul.hcl
 delete mode 100644 infra/station-config/nomad.hcl
 create mode 100755 sbin/dsconfig.sh
 create mode 100755 sbin/prepare_dev_env.sh
 mode change 100644 => 100755 sbin/run_service_test.sh
 create mode 100644 tangostationcontrol/tangostationcontrol/common/consul.py
 rename tangostationcontrol/tangostationcontrol/{devices => common}/device_decorators.py (100%)
 create mode 100644 tangostationcontrol/tangostationcontrol/configuration/_schemas.py
 rename {docker-compose/http-json-schemas/definitions => tangostationcontrol/tangostationcontrol/configuration/schemas}/dithering.json (96%)
 rename {docker-compose/http-json-schemas/definitions => tangostationcontrol/tangostationcontrol/configuration/schemas}/hba.json (92%)
 rename {docker-compose/http-json-schemas/definitions => tangostationcontrol/tangostationcontrol/configuration/schemas}/observation-settings.json (94%)
 rename {docker-compose/http-json-schemas/definitions => tangostationcontrol/tangostationcontrol/configuration/schemas}/pointing.json (97%)
 rename {docker-compose/http-json-schemas/definitions => tangostationcontrol/tangostationcontrol/configuration/schemas}/sap.json (87%)
 rename {docker-compose/http-json-schemas/definitions => tangostationcontrol/tangostationcontrol/configuration/schemas}/station-configuration.json (70%)
 create mode 100644 tangostationcontrol/tangostationcontrol/device_server/__init__.py
 create mode 100644 tangostationcontrol/tangostationcontrol/metrics/__init__.py
 create mode 100644 tangostationcontrol/tangostationcontrol/metrics/_collectors.py
 create mode 100644 tangostationcontrol/tangostationcontrol/metrics/_decorators.py
 delete mode 100644 tangostationcontrol/test/configuration/_mock_requests.py

diff --git a/.editorconfig b/.editorconfig
index 2823e7bda..546869377 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -147,5 +147,5 @@ ij_yaml_space_before_colon = false
 ij_yaml_spaces_within_braces = true
 ij_yaml_spaces_within_brackets = true
 
-[{*.hcl,*.hcl.tmpl}]
+[{*.hcl,*.hcl.tmpl,*.nomad}]
 indent_size = 2
diff --git a/.gitignore b/.gitignore
index 95968218c..4ccabd371 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@
 **/env
 **/pending_log_messages.db
 **/vscode-server.tar
+.orig
 
 tangostationcontrol/build
 tangostationcontrol/cover
@@ -34,4 +35,10 @@ deploy/hosts
 docker-compose/alerta-web/alerta-secrets.json
 docker-compose/tmp
 
+infra/dev/jobs/*.nomad
+infra/dev/nomad/tmp/*
+!infra/dev/nomad/tmp/.keep
+
 **/CDB/dump*.json
+
+bin/jumppad
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 627270c72..a68710f0a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -118,14 +118,13 @@ docker_build_image:
     matrix:
       - IMAGE:
           - ec-sim
-          - http-json-schemas
           - prometheus
           - tango-prometheus-exporter
           - itango
           - grafana
           - loki
-          - logstash
           - jupyter-lab
+          - dsconfig
   rules:
     - if: $CI_PIPELINE_SOURCE == "merge_request_event"
       changes:
@@ -224,7 +223,7 @@ CDB_correctness:
 
 sast:
   stage: static-analysis
-  needs: []
+  needs: [ ]
   variables:
     SAST_EXCLUDED_PATHS: "*.tox"
     SAST_EXCLUDED_ANALYZERS: brakeman, flawfinder, kubesec, nodejs-scan, phpcs-security-audit,
@@ -232,13 +231,13 @@ sast:
 
 dependency_scanning:
   stage: static-analysis
-  needs: []
+  needs: [ ]
   before_script:
     - rm -r `ls | grep -v "^tangostationcontrol$"`
 
 secret_detection:
   stage: static-analysis
-  needs: []
+  needs: [ ]
 
 sphinx_documentation:
   stage: documentation
@@ -270,16 +269,18 @@ unit_test:
 
 .test_docker:
   stage: integration-tests
-  image: docker:23.0.5 # latest ships with docker compose v2.19, which has the following bug: https://github.com/docker/compose/issues/10668
-#  needs:
-#    - unit_test
-#    - docker_build_image_device_base
+  image: docker:latest
+  needs:
+    - unit_test
+    - docker_build_image_device_base
   tags:
-    - privileged
+    - integration_tests
   services:
     - name: docker:dind
   variables:
-    DOCKER_TLS_CERTDIR: "/certs"
+    DOCKER_DRIVER: overlay2
+    DOCKER_TLS_CERTDIR: ""
+    JUMPPAD_HOME: $CI_PROJECT_DIR
   before_script:
     - |
       if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" && -z "$CI_COMMIT_TAG" ]]; then
@@ -290,33 +291,23 @@ unit_test:
         echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
       fi
     - apk update
+    - apk add git
     - apk add --update make bash
     - apk add --update bind-tools
-    - apk add --update postgresql14-client gzip
+    - apk add --update postgresql14-client gzip socat
     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
     - touch /root/.Xauthority
     #    Hack BASH_SOURCE into sourced files, docker its sh shell won't set this
     - export BASH_SOURCE=$(pwd)/setup.sh
-    #    Hack HOSTNAME env variable into host.docker.internal, set in docker-compose
-    - export HOSTNAME=host.docker.internal
     #    source the lofarrc file and mask its non zero exit code
     - . setup.sh || true
     #    TANGO_HOST must be unset our databaseds will be unreachable
     - unset TANGO_HOST
-    #    Do not remove 'bash' or statement will be ignored by primitive docker shell
-    - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh pull $tag
     - export TAG="$tag"
-  after_script:
-    #    Collect output of all containers
-    - |
-      mkdir -p log
-      for container in $(docker ps -a --format "{{.Names}}")
-      do
-        echo "Saving log for container $container"
-        docker logs "${container}" >& "log/${container}.log"
-      done
-    #    Collect content of the TangoDB
-    - $CI_PROJECT_DIR/bin/dump_ConfigDb.sh >& log/dump_ConfigDb.log
+    # Forward nomad API port to docker host
+    - socat tcp-listen:4646,reuseaddr,fork tcp:docker:4646 &
+    # Forward consul dns port to docker host
+    - socat udp-listen:8600,reuseaddr,fork udp:docker:8600 &
   artifacts:
     when: always
     paths:
@@ -324,22 +315,24 @@ unit_test:
 
 integration_test_docker:
   extends: .test_docker
+  allow_failure: true # until there is a machine that can properly run them
   script:
     #    Do not remove 'bash' or statement will be ignored by primitive docker shell
-    - bash -e $CI_PROJECT_DIR/sbin/run_integration_test.sh --no-build
+    - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh pull $tag
+    #    Do not remove 'bash' or statement will be ignored by primitive docker shell
+    - bash -e $CI_PROJECT_DIR/sbin/run_integration_test.sh --no-build --save-logs
 
 service_test_docker:
   extends: .test_docker
   script:
-    # Fix logstash healthcheck
     - export HOSTNAME=localhost
     #    Do not remove 'bash' or statement will be ignored by primitive docker shell
     - bash -e $CI_PROJECT_DIR/sbin/run_service_test.sh
 
 multi_project_integration_test:
   stage: integration-tests
-#  needs:
-#    - docker_build_image_device_base
+  needs:
+    - docker_build_image_device_base
   variables:
     TANGO_DEFAULT_BRANCH: $CI_DEFAULT_BRANCH
     TANGO_CURRENT_BRANCH: $CI_COMMIT_BRANCH
@@ -382,41 +375,37 @@ release_job:
     tag_name: '$CI_COMMIT_TAG'
     description: '$CI_COMMIT_TAG'
 
-.base_deploy:
+deploy_nomad:
   stage: deploy
-  image: ubuntu:bionic
+  image:
+    name: hashicorp/levant
+    entrypoint: [ "" ]
   when: manual
   rules:
     - if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH) || $CI_COMMIT_TAG
-  before_script:
-    - apt-get update
-    - apt-get install ansible openssh-client -y
-    # Use Gitlab protected variable to provide key
-    - echo "$DEPLOY_KEY" > id_rsa
-    - chmod 400 id_rsa
-    - ssh-keygen -y -f id_rsa > id_rsa.pub
-    # Add ssh key to agent
-    - eval $(ssh-agent)
-    - ssh-add id_rsa
-    - ansible --version
-  script:
-    - echo "Deploying version $CI_COMMIT_TAG"
-    - cd deploy
-    # Prevent error of ansible being run in world writeable directory
-    - chmod o-w .
-    # Generate hosts file for deployment
-    - echo "[all]" > hosts
-    - echo "stat ansible_host=$DEPLOY_HOST ansible_user=$DEPLOY_USER" >> hosts
-    # Run deployment with populated variables
-    - ansible-playbook -v deploy.yml --extra-vars station_version=$CI_COMMIT_TAG --extra-vars station_config=$DEPLOY_CONFIG
-.deploy_l2ts_base:
-  extends: .base_deploy
-  variables:
-    DEPLOY_USER: $L2TS_USERNAME
-    DEPLOY_HOST: $L2TS_HOSTNAME
-    DEPLOY_CONFIG: $L2TS_CONFIG
-    DEPLOY_KEY: $L2TS_DEPLOY_KEY
-deploy_l2ts_start:
-  extends: .deploy_l2ts_base
+  parallel:
+    matrix:
+      - STATION:
+          - cs001
+        COMPONENT:
+          - monitoring
+          - logging
+          - tango
+          - object-storage
+          - sdptr
+          - device-server
+          - dsconfig
+          - ec-sim
+          - jupyter
+          - nomad-client
+          - tango-prometheus-exporter
   environment:
-    name: l2ts
+    name: $STATION
+  script:
+    - |
+      levant deploy \
+        -address="http://${STATION}c.control.lofar:4646" \
+        -var-file=infra/env.yaml \
+        -var image_tag="latest" \
+        -var station="${STATION}" \
+        infra/jobs/station/${COMPONENT}.levant.nomad
diff --git a/CDB/README.md b/CDB/README.md
index 794fd25f3..3ec4da94d 100644
--- a/CDB/README.md
+++ b/CDB/README.md
@@ -4,7 +4,7 @@ The ConfigDb.json files in this directory are used to populate the Tango configu
 To load a configuration file, use
 
 ```bash
-../sbin/update_ConfigDb.sh file.json
+../sbin/dsconfig.sh --update file.json
 ```
 
 The tool ``tangostationcontrol.toolkit.analyse_dsconfig_hierarchies`` is provided to check the consistency of
@@ -12,13 +12,13 @@ hierarchies defined between the devices in a set of configuration files.
 
 The following files are provided:
 
-| File                                       | Description                                                 | Usage               |
-|--------------------------------------------|-------------------------------------------------------------|---------------------|
-| `LOFAR_ConfigDb.json`                      | Generic base configuration, registering all of the devices. | Always              |
-| `test_environment_ConfigDb.json`           | Base delta for the unit- and integration test suites.       | Tests & development |
-| `stations/simulators_ConfigDb.json`        | A "station" configuration that points to our simulators.    | Tests & development |
-| `stations/dummy_positions_ConfigDb.json`   | An antenna configuration, just to have one (it's CS001).    | Tests & development |
-| `stations/LTS_ConfigDb.json`               | The configuration for the Lab Test Station.                 | Load on LTS         |
-| `stations/DTS_ConfigDb.json`               | The configuration for the Dwingeloo Test Station.           | Load on DTS-lab     |
-| `stations/DTS_Outside_ConfigDb.json`       | The configuration for the Dwingeloo Test Station Outside.   | Load on DTS-outside |
-| `integrations/`                            | Configurations required by the integration test suites.     | Integration tests   |
+| File                                     | Description                                                 | Usage               |
+|------------------------------------------|-------------------------------------------------------------|---------------------|
+| `LOFAR_ConfigDb.json`                    | Generic base configuration, registering all of the devices. | Always              |
+| `test_environment_ConfigDb.json`         | Base delta for the unit- and integration test suites.       | Tests & development |
+| `stations/simulators_ConfigDb.json`      | A "station" configuration that points to our simulators.    | Tests & development |
+| `stations/dummy_positions_ConfigDb.json` | An antenna configuration, just to have one (it's CS001).    | Tests & development |
+| `stations/LTS_ConfigDb.json`             | The configuration for the Lab Test Station.                 | Load on LTS         |
+| `stations/DTS_ConfigDb.json`             | The configuration for the Dwingeloo Test Station.           | Load on DTS-lab     |
+| `stations/DTS_Outside_ConfigDb.json`     | The configuration for the Dwingeloo Test Station Outside.   | Load on DTS-outside |
+| `integrations/`                          | Configurations required by the integration test suites.     | Integration tests   |
diff --git a/CDB/stations/cs001.json b/CDB/stations/cs001.json
index 3d73427ee..649c41aa8 100644
--- a/CDB/stations/cs001.json
+++ b/CDB/stations/cs001.json
@@ -34,7 +34,7 @@
               "Station_Name": [
                 "CS001"
               ],
-              "Station_Number":[
+              "Station_Number": [
                 "1"
               ]
             }
@@ -76,7 +76,7 @@
           "STAT/EC/1": {
             "properties": {
               "OPC_Server_Name": [
-                  "ec-sim"
+                "ec-sim.service.consul"
               ]
             }
           }
@@ -437,7 +437,7 @@
                 "SPARSE_ODD",
                 "ALL"
               ],
-              "Antenna_Set_Masks":[
+              "Antenna_Set_Masks": [
                 "111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000",
                 "000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111",
                 "101010101010101010101010101010101010101010101000101010101010101010101010101010101010101010101010",
@@ -543,8 +543,7 @@
                 "2", "95"
               ],
               "Power_to_RECV_mapping": [
-                "1", "0",
-                "1", "2",
+                "1", "0","1", "2",
                 "1", "4",
                 "1", "6",
                 "1", "8",
@@ -641,7 +640,7 @@
                 "2", "94"
               ],
               "Antenna_to_SDP_Mapping": [
-                "0", "0",
+                                "0", "0",
                 "0", "1",
                 "0", "2",
                 "0", "3",
@@ -739,8 +738,8 @@
                 "15", "5"
               ],
               "Antenna_Names": [
-                 "L0",  "L1",  "L2",  "L3",  "L4",  "L5",
-                 "L6",  "L7",  "L8",  "L9", "L10", "L11",
+                "L0",  "L1",  "L2",  "L3",  "L4",  "L5",
+                "L6",  "L7",  "L8",  "L9", "L10", "L11",
                 "L12", "L13", "L14", "L15", "L16", "L17",
                 "L18", "L19", "L20", "L21", "L22", "L23",
                 "L24", "L25", "L26", "L27", "L28", "L29",
@@ -757,7 +756,7 @@
                 "L90", "L91", "L92", "L93", "L94", "L95"
               ],
               "Antenna_Cables": [
-                 "80m",  "80m",  "80m",  "80m",  "80m",  "80m",
+                "80m",  "80m",  "80m",  "80m",  "80m",  "80m",
                  "80m",  "80m",  "80m",  "80m",  "80m",  "80m",
                  "80m", "115m",  "80m",  "80m",  "80m",  "80m",
                  "80m",  "80m",  "80m",  "80m",  "80m",  "80m",
@@ -778,7 +777,7 @@
                 "3826923.503", "460915.488", "5064643.517"
               ],
               "Antenna_Reference_ETRS": [
-                "3826923.942", "460915.117", "5064643.229",
+                                "3826923.942", "460915.117", "5064643.229",
                 "3826921.923", "460914.874", "5064644.767",
                 "3826922.604", "460917.222", "5064644.043",
                 "3826924.742", "460917.480", "5064642.415",
@@ -888,7 +887,7 @@
               "Antenna_Sets": [
                 "ALL"
               ],
-              "Antenna_Set_Masks":[
+              "Antenna_Set_Masks": [
                 "111111111111111111111111"
               ],
               "Control_to_RECV_mapping": [
@@ -918,7 +917,7 @@
                 "1", "47"
               ],
               "Power_to_RECV_mapping": [
-                "1", "0",
+                                "1", "0",
                 "1", "2",
                 "1", "4",
                 "1", "6",
@@ -974,6 +973,7 @@
                  "H6",  "H7",  "H8",  "H9", "H10", "H11",
                 "H12", "H13", "H14", "H15", "H16", "H17",
                 "H18", "H19", "H20", "H21", "H22", "H23"
+
               ],
               "Antenna_Cables": [
                 "115m", "115m", "115m", "115m", "115m", "115m",
@@ -982,7 +982,7 @@
                 "115m", "115m", "115m", "115m", "115m", "115m"
               ],
               "Antenna_Field_Reference_ITRF": [
-                "3826896.192", "460979.502", "5064658.231"
+                                "3826896.192", "460979.502", "5064658.231"
               ],
               "Antenna_Reference_ETRS": [
                 "3826886.142", "460980.772", "5064665.668",
@@ -1028,7 +1028,7 @@
               "Antenna_Sets": [
                 "ALL"
               ],
-              "Antenna_Set_Masks":[
+              "Antenna_Set_Masks": [
                 "111111111111111111111111"
               ],
               "Control_to_RECV_mapping": [
@@ -1084,7 +1084,7 @@
                 "1", "94"
               ],
               "Antenna_to_SDP_Mapping": [
-                "0", "0",
+                                "0", "0",
                 "0", "1",
                 "0", "2",
                 "0", "3",
@@ -1166,10 +1166,10 @@
         }
       }
     },
-    "SDPFirmware":{
+    "SDPFirmware": {
       "STAT": {
         "SDPFirmware": {
-          "STAT/SDPFirmware/LBA":{
+          "STAT/SDPFirmware/LBA": {
             "properties": {
               "Control_Children": [
                 "STAT/SDP/LBA",
@@ -1197,7 +1197,7 @@
               ]
             }
           },
-          "STAT/SDPFirmware/HBA0":{
+          "STAT/SDPFirmware/HBA0": {
             "properties": {
               "Control_Children": [
                 "STAT/SDP/HBA0",
@@ -1225,7 +1225,7 @@
               ]
             }
           },
-          "STAT/SDPFirmware/HBA1":{
+          "STAT/SDPFirmware/HBA1": {
             "properties": {
               "Control_Children": [
                 "STAT/SDP/HBA1",
@@ -1327,222 +1327,6 @@
           }
         }
       }
-    },
-    "BST": {
-      "STAT": {
-        "BST": {
-          "STAT/BST/LBA": {
-            "properties": {
-              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          },
-          "STAT/BST/HBA0": {
-            "properties": {
-              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          },
-          "STAT/BST/HBA1": {
-            "properties": {
-              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "SST": {
-      "STAT": {
-        "SST": {
-          "STAT/SST/LBA": {
-            "properties": {
-              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          },
-          "STAT/SST/HBA0": {
-            "properties": {
-              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          },
-          "STAT/SST/HBA1": {
-            "properties": {
-              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "XST": {
-      "STAT": {
-        "XST": {
-          "STAT/XST/LBA": {
-            "properties": {
-              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          },
-          "STAT/XST/HBA0": {
-            "properties": {
-              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          },
-          "STAT/XST/HBA1": {
-            "properties": {
-              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7",
-                "3c:ec:ef:86:2f:b7"
-              ]
-            }
-          }
-        }
-      }
     }
   }
 }
diff --git a/CDB/stations/hba_core.json b/CDB/stations/hba_core.json
index f03fbc657..0dd47880d 100644
--- a/CDB/stations/hba_core.json
+++ b/CDB/stations/hba_core.json
@@ -43,7 +43,7 @@
                 "STAT/SDP/HBA0"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba0"
+                "sdptr-hba0.service.consul"
               ],
               "OPC_Server_Port": [
                 "4842"
@@ -95,7 +95,7 @@
                 "STAT/SDP/HBA1"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba1"
+                "sdptr-hba1.service.consul"
               ],
               "OPC_Server_Port": [
                 "4844"
@@ -162,7 +162,7 @@
                 "STAT/RECVH/H0"
               ],
               "HBAT_single_element_selection_GENERIC_201512": [
-                 "0", "10",  "4",  "3", "14",  "0",
+                "0", "10",  "4",  "3", "14",  "0",
                  "5",  "5",  "3", "13", "10",  "3",
                 "12",  "2",  "7", "15",  "6", "14",
                  "7",  "5",  "7",  "9",  "0", "15"
@@ -265,7 +265,7 @@
                 "STAT/XST/HBA0"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba0"
+                "sdptr-hba0.service.consul"
               ],
               "OPC_Server_Port": [
                 "4842"
@@ -291,7 +291,7 @@
                 "STAT/XST/HBA1"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba1"
+                "sdptr-hba1.service.consul"
               ],
               "OPC_Server_Port": [
                 "4844"
@@ -312,7 +312,7 @@
               "Control_Children": [
               ],
               "OPC_Server_Name": [
-                "sdptr-hba0"
+                "sdptr-hba0.service.consul"
               ],
               "OPC_Server_Port": [
                 "4842"
@@ -435,7 +435,7 @@
               "Control_Children": [
               ],
               "OPC_Server_Name": [
-                "sdptr-hba1"
+                "sdptr-hba1.service.consul"
               ],
               "OPC_Server_Port": [
                 "4844"
@@ -568,7 +568,7 @@
                 "5113"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba0"
+                "sdptr-hba0.service.consul"
               ],
               "OPC_Server_Port": [
                 "4842"
@@ -576,23 +576,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "FPGA_bst_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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_bst_offload_hdr_udp_destination_port_RW_default": [
                 "5013",
@@ -641,7 +659,7 @@
                 "5123"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba1"
+                "sdptr-hba1.service.consul"
               ],
               "OPC_Server_Port": [
                 "4844"
@@ -649,23 +667,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "FPGA_bst_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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_bst_offload_hdr_udp_destination_port_RW_default": [
                 "5023",
@@ -720,7 +756,7 @@
                 "5111"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba0"
+                "sdptr-hba0.service.consul"
               ],
               "OPC_Server_Port": [
                 "4842"
@@ -728,23 +764,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_sst_offload_hdr_udp_destination_port_RW_default": [
                 "5011",
@@ -775,7 +829,7 @@
                 "5121"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba1"
+                "sdptr-hba1.service.consul"
               ],
               "OPC_Server_Port": [
                 "4844"
@@ -783,23 +837,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_sst_offload_hdr_udp_destination_port_RW_default": [
                 "5021",
@@ -836,7 +908,7 @@
                 "5112"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba0"
+                "sdptr-hba0.service.consul"
               ],
               "OPC_Server_Port": [
                 "4842"
@@ -844,23 +916,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "FPGA_xst_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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_xst_offload_hdr_udp_destination_port_RW_default": [
                 "5012",
@@ -909,7 +999,7 @@
                 "5122"
               ],
               "OPC_Server_Name": [
-                "sdptr-hba1"
+                "sdptr-hba1.service.consul"
               ],
               "OPC_Server_Port": [
                 "4844"
@@ -917,23 +1007,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "FPGA_xst_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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_xst_offload_hdr_udp_destination_port_RW_default": [
                 "5022",
diff --git a/CDB/stations/hba_remote.json b/CDB/stations/hba_remote.json
index dd4e7bdcf..2f93af0ac 100644
--- a/CDB/stations/hba_remote.json
+++ b/CDB/stations/hba_remote.json
@@ -342,23 +342,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "FPGA_bst_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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_bst_offload_hdr_udp_destination_port_RW_default": [
                 "5013",
@@ -421,23 +439,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_sst_offload_hdr_udp_destination_port_RW_default": [
                 "5011",
@@ -482,23 +518,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "FPGA_xst_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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_xst_offload_hdr_udp_destination_port_RW_default": [
                 "5012",
diff --git a/CDB/stations/lba.json b/CDB/stations/lba.json
index ec0d05e81..626c98b33 100644
--- a/CDB/stations/lba.json
+++ b/CDB/stations/lba.json
@@ -22,7 +22,7 @@
                 "STAT/SDP/LBA"
               ],
               "OPC_Server_Name": [
-                "sdptr-lba"
+                "sdptr-lba.service.consul"
               ],
               "OPC_Server_Port": [
                 "4840"
@@ -209,7 +209,7 @@
                 "STAT/XST/LBA"
               ],
               "OPC_Server_Name": [
-                "sdptr-lba"
+                "sdptr-lba.service.consul"
               ],
               "OPC_Server_Port": [
                 "4840"
@@ -228,7 +228,7 @@
           "STAT/SDP/LBA": {
             "properties": {
               "OPC_Server_Name": [
-                "sdptr-lba"
+                "sdptr-lba.service.consul"
               ],
               "OPC_Server_Port": [
                 "4840"
@@ -361,7 +361,7 @@
                 "5103"
               ],
               "OPC_Server_Name": [
-                "sdptr-lba"
+                "sdptr-lba.service.consul"
               ],
               "OPC_Server_Port": [
                 "4840"
@@ -369,23 +369,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "FPGA_bst_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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_bst_offload_hdr_udp_destination_port_RW_default": [
                 "5003",
@@ -440,7 +458,7 @@
                 "5101"
               ],
               "OPC_Server_Name": [
-                "sdptr-lba"
+                "sdptr-lba.service.consul"
               ],
               "OPC_Server_Port": [
                 "4840"
@@ -448,23 +466,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_sst_offload_hdr_udp_destination_port_RW_default": [
                 "5001",
@@ -501,7 +537,7 @@
                 "5102"
               ],
               "OPC_Server_Name": [
-                "sdptr-lba"
+                "sdptr-lba.service.consul"
               ],
               "OPC_Server_Port": [
                 "4840"
@@ -509,23 +545,41 @@
               "OPC_Time_Out": [
                 "5.0"
               ],
+              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56",
+                "52:54:00:12:34:56"
+              ],
               "FPGA_xst_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"
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1",
+                "10.99.250.1"
               ],
               "FPGA_xst_offload_hdr_udp_destination_port_RW_default": [
                 "5002",
diff --git a/CDB/stations/testenv_cs001.json.orig b/CDB/stations/testenv_cs001.json.orig
new file mode 100644
index 000000000..db183dbea
--- /dev/null
+++ b/CDB/stations/testenv_cs001.json.orig
@@ -0,0 +1,1186 @@
+{
+  "servers": {
+    "APSCT": {
+      "STAT": {
+        "APSCT": {
+          "STAT/APSCT/L0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "apsct-sim"
+              ],
+              "OPC_Server_Port": [
+                "4843"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "APSCT_On_Off_timeout": [
+                "1"
+              ]
+            }
+          },
+          "STAT/APSCT/L1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "apsct-sim"
+              ],
+              "OPC_Server_Port": [
+                "4843"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "APSCT_On_Off_timeout": [
+                "1"
+              ]
+            }
+          },
+          "STAT/APSCT/H0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "apsct-sim"
+              ],
+              "OPC_Server_Port": [
+                "4843"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "APSCT_On_Off_timeout": [
+                "1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "CCD": {
+      "STAT": {
+        "CCD": {
+          "STAT/CCD/1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "ccd-sim"
+              ],
+              "OPC_Server_Port": [
+                "4843"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "CCD_On_Off_timeout": [
+                "1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "EC": {
+      "STAT": {
+        "EC": {
+          "STAT/EC/1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "ec-sim.service.consul"
+              ],
+              "OPC_Server_Port": [
+                "4850"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "OPC_Node_Path_Prefix": [
+                "3:ServerInterfaces",
+                "4:Environmental_Control"
+              ],
+              "OPC_namespace": [
+                "http://Environmental_Control"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "APSPU": {
+      "STAT": {
+        "APSPU": {
+          "STAT/APSPU/L0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "apspu-sim"
+              ],
+              "OPC_Server_Port": [
+                "4842"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ]
+            }
+          },
+          "STAT/APSPU/L1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "apspu-sim"
+              ],
+              "OPC_Server_Port": [
+                "4842"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ]
+            }
+          },
+          "STAT/APSPU/H0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "apspu-sim"
+              ],
+              "OPC_Server_Port": [
+                "4842"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "Beamlet": {
+      "STAT": {
+        "Beamlet": {
+          "STAT/Beamlet/LBA": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_beamlet_output_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_beamlet_output_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          },
+          "STAT/Beamlet/HBA0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_beamlet_output_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_beamlet_output_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          },
+          "STAT/Beamlet/HBA1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_beamlet_output_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_beamlet_output_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "DigitalBeam": {
+      "STAT": {
+        "DigitalBeam": {
+<<<<<<< HEAD
+          "STAT/DigitalBeam/LBA": {
+            "properties": {
+              "Beam_tracking_interval": [
+                "1.0"
+              ],
+              "Beam_tracking_preparation_period": [
+                "0.5"
+              ]
+=======
+            "STAT": {
+                "DigitalBeam": {
+                    "STAT/DigitalBeam/LBA": {
+                        "properties": {
+                            "Beam_tracking_interval": [
+                                "1.0"
+                            ],
+                            "Beam_tracking_preparation_period": [
+                                "0.5"
+                            ]
+                        }
+                    },
+                    "STAT/DigitalBeam/HBA0": {
+                        "properties": {
+                            "Beam_tracking_interval": [
+                                "1.0"
+                            ],
+                            "Beam_tracking_preparation_period": [
+                                "0.5"
+                            ]
+                        }
+                    },
+                    "STAT/DigitalBeam/HBA1": {
+                        "properties": {
+                            "AntennaField_Device": [
+                                "STAT/AFH/HBA1"
+                            ],
+                            "Beamlet_Device": [
+                                "STAT/Beamlet/HBA1"
+                            ],
+                            "Beam_tracking_interval": [
+                                "1.0"
+                            ],
+                            "Beam_tracking_preparation_period": [
+                                "0.5"
+                            ]
+                        }
+                    }
+                }
+>>>>>>> master
+            }
+          },
+          "STAT/DigitalBeam/HBA0": {
+            "properties": {
+              "Beam_tracking_interval": [
+                "1.0"
+              ],
+              "Beam_tracking_preparation_period": [
+                "0.5"
+              ]
+            }
+          },
+          "STAT/DigitalBeam/HBA1": {
+            "properties": {
+              "AntennaField_Device": [
+                "STAT/AntennaField/HBA1"
+              ],
+              "Beamlet_Device": [
+                "STAT/Beamlet/HBA1"
+              ],
+              "Beam_tracking_interval": [
+                "1.0"
+              ],
+              "Beam_tracking_preparation_period": [
+                "0.5"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "TemperatureManager": {
+      "STAT": {
+        "TemperatureManager": {
+          "STAT/TemperatureManager/1": {
+            "properties": {
+              "Alarm_Error_List": [
+                "APSCT, APSCT_TEMP_error_R",
+                "APSPU, APSPU_TEMP_error_R",
+                "UNB2, UNB2_TEMP_error_R",
+                "RECVH, RECV_TEMP_error_R",
+                "RECVL, RECV_TEMP_error_R"
+              ],
+              "Shutdown_Device_List": [
+                "STAT/SDPFirmware/LBA",
+                "STAT/SDPFirmware/HBA0",
+                "STAT/SDPFirmware/HBA1",
+                "STAT/SDP/LBA",
+                "STAT/SDP/HBA0",
+                "STAT/SDP/HBA1",
+                "STAT/UNB2/L0",
+                "STAT/UNB2/L1",
+                "STAT/UNB2/H0",
+                "STAT/RECVH/H0",
+                "STAT/RECVL/L0",
+                "STAT/RECVL/L1",
+                "STAT/APSCT/L0",
+                "STAT/APSCT/L1",
+                "STAT/APSCT/H0",
+                "STAT/CCD/1",
+                "STAT/APSPU/L0",
+                "STAT/APSPU/L1",
+                "STAT/APSPU/H0"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "PCON": {
+      "STAT": {
+        "PCON": {
+          "STAT/PCON/1": {
+            "properties": {
+              "SNMP_use_simulators": [
+                "True"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "PSOC": {
+      "STAT": {
+        "PSOC": {
+          "STAT/PSOC/1": {
+            "properties": {
+              "SNMP_use_simulators": [
+                "True"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "RECVH": {
+      "STAT": {
+        "RECVH": {
+          "STAT/RECVH/H0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "recvh-sim"
+              ],
+              "OPC_Server_Port": [
+                "4844"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "RCU_On_Off_timeout": [
+                "1"
+              ],
+              "RCU_DTH_On_Off_timeout": [
+                "1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "RECVL": {
+      "STAT": {
+        "RECVL": {
+          "STAT/RECVL/L0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "recvl-sim"
+              ],
+              "OPC_Server_Port": [
+                "4845"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "RCU_On_Off_timeout": [
+                "1"
+              ],
+              "RCU_DTH_On_Off_timeout": [
+                "1"
+              ]
+            }
+          },
+          "STAT/RECVL/L1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "recvl-sim"
+              ],
+              "OPC_Server_Port": [
+                "4845"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "RCU_On_Off_timeout": [
+                "1"
+              ],
+              "RCU_DTH_On_Off_timeout": [
+                "1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "SDPFirmware": {
+      "STAT": {
+        "SDPFirmware": {
+          "STAT/SDPFirmware/LBA": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "Firmware_Boot_timeout": [
+                "1.0"
+              ]
+            }
+          },
+          "STAT/SDPFirmware/HBA0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "Firmware_Boot_timeout": [
+                "1.0"
+              ]
+            }
+          },
+          "STAT/SDPFirmware/HBA1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "Firmware_Boot_timeout": [
+                "1.0"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "SDP": {
+      "STAT": {
+        "SDP": {
+          "STAT/SDP/LBA": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ]
+            }
+          },
+          "STAT/SDP/HBA0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ]
+            }
+          },
+          "STAT/SDP/HBA1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "BST": {
+      "STAT": {
+        "BST": {
+          "STAT/BST/LBA": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_bst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          },
+          "STAT/BST/HBA0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_bst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          },
+          "STAT/BST/HBA1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_bst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "SST": {
+      "STAT": {
+        "SST": {
+          "STAT/SST/LBA": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          },
+          "STAT/SST/HBA0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          },
+          "STAT/SST/HBA1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "XST": {
+      "STAT": {
+        "XST": {
+          "STAT/XST/LBA": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          },
+          "STAT/XST/HBA0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          },
+          "STAT/XST/HBA1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "sdptr-sim"
+              ],
+              "OPC_Server_Port": [
+                "4840"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB",
+                "01:23:45:67:89:AB"
+              ],
+              "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1",
+                "127.0.0.1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "UNB2": {
+      "STAT": {
+        "UNB2": {
+          "STAT/UNB2/L0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "unb2-sim"
+              ],
+              "OPC_Server_Port": [
+                "4841"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "UNB2_On_Off_timeout": [
+                "1"
+              ]
+            }
+          },
+          "STAT/UNB2/L1": {
+            "properties": {
+              "OPC_Server_Name": [
+                "unb2-sim"
+              ],
+              "OPC_Server_Port": [
+                "4841"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "UNB2_On_Off_timeout": [
+                "1"
+              ]
+            }
+          },
+          "STAT/UNB2/H0": {
+            "properties": {
+              "OPC_Server_Name": [
+                "unb2-sim"
+              ],
+              "OPC_Server_Port": [
+                "4841"
+              ],
+              "OPC_Time_Out": [
+                "5.0"
+              ],
+              "UNB2_On_Off_timeout": [
+                "1"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "TileBeam": {
+      "STAT": {
+        "TileBeam": {
+          "STAT/TileBeam/HBA0": {
+            "properties": {
+              "Tracking_enabled_RW_default": [
+                "True"
+              ]
+            }
+          },
+          "STAT/TileBeam/HBA1": {
+            "properties": {
+              "Tracking_enabled_RW_default": [
+                "True"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "StationManager": {
+      "STAT": {
+        "StationManager": {
+          "STAT/StationManager/1": {
+            "properties": {
+              "Suppress_State_Transition_Failures": [
+                "True"
+              ]
+            }
+          }
+        }
+      }
+    },
+    "ObservationControl": {
+      "STAT": {
+        "Observation": {
+          "STAT/Observation/1": {
+          }
+        },
+        "ObservationControl": {
+          "STAT/ObservationControl/1": {
+            "properties": {
+              "Power_Children": [
+                "STAT/Observation/1"
+              ],
+              "Control_Children": [
+                "STAT/Observation/1"
+              ]
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/README.md b/README.md
index dbc58631e..1dcb85bc8 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,48 @@ You will also need:
 * docker-compose
 * make
 * bash
+* dig (dnsutils package on ubuntu/debian)
+* wget
+
+## Start dev environment
+
+For local development a dev environment is needed. To setup this environment run
+
+```
+source ./sbin/prepare_dev_env.sh
+```
+
+This will install `jumppad`, if not present yet as well as creating the docker volume needed to simulate the station
+nomad cluster.
+
+Afterwards run
+
+```
+jumppad up infra/dev
+```
+
+to start the dev environment including tango.
+
+Nomad is now available at http://localhost:4646/
+
+## Start dev environment
+
+For local development a dev environment is needed. To setup this environment run
+
+```
+./sbin/prepare_dev_env.sh
+```
+
+This will install `jumppad`, if not present yet as well as creating the docker volume needed to simulate the station
+nomad cluster.
+
+Afterwards run
+
+```
+jumppad up infra/dev
+```
+
+to start the dev environment including tango.
 
 ## Bootstrap
 
@@ -115,6 +157,7 @@ Next change the version in the following places:
 
 # Release Notes
 
+* 0.23.0 Migrate execution environment to nomad
 * 0.22.0 Split `Antennafield` in `AFL` and `AFH` devices in order to separate Low-Band
          and High-Band functionalities
          Removed `Antenna_Type_R` attribute from antennafield devices  
diff --git a/bin/dump_ConfigDb.sh b/bin/dump_ConfigDb.sh
index af76d68da..b58beff1b 100755
--- a/bin/dump_ConfigDb.sh
+++ b/bin/dump_ConfigDb.sh
@@ -1,9 +1,7 @@
 #!/bin/bash
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
+#
 
-# writes the JSON dump to stdout, Do not change -i into -it incompatible with gitlab ci!
-docker exec -i dsconfig bash -c '
-  python -m dsconfig.dump > /tmp/dsconfig-configdb-dump.json
-  /manage_object_properties.py -r > /tmp/dsconfig-objectdb-dump.json
-  /merge_json.py /tmp/dsconfig-objectdb-dump.json /tmp/dsconfig-configdb-dump.json'
+"${LOFAR20_DIR}/sbin/dsconfig.sh" --dump
diff --git a/bin/itango_console.sh b/bin/itango_console.sh
index 20f2c2923..3e19073ee 100755
--- a/bin/itango_console.sh
+++ b/bin/itango_console.sh
@@ -1,5 +1,49 @@
 #!/bin/bash
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
+#
 
-exec docker exec -it itango itango3
+# defaults
+network="station"
+
+# list of arguments expected in the input
+optstring_long="help,network::"
+optstring="hn::"
+
+options=$(getopt -l ${optstring_long} -o ${optstring} -- "$@")
+
+eval set -- "$options"
+
+
+while true; do
+  case ${1} in
+    -h|--help)
+      usage
+      exit 0
+      ;;
+    -n|--network)
+      shift
+      network="$1"
+      ;;
+    --)
+    shift
+    break;;
+  esac
+  shift
+done
+
+if [ -z "$TAG" ]; then
+  TAG="latest"
+fi
+
+echo "Use docker network '$network'"
+docker_args=(run --rm  -e "TANGO_HOST=$TANGO_HOST" --network="$network" -it)
+docker_image="git.astron.nl:5000/lofar2.0/tango/itango:$TAG"
+
+if [[ -n "$DNS" ]]; then
+  echo "Set docker dns to $DNS"
+  docker_args+=(--dns "$DNS")
+fi
+
+docker "${docker_args[@]}" "$docker_image" itango3
diff --git a/bin/itango_shell.sh b/bin/itango_shell.sh
index 8a8d5e380..92e92eda3 100755
--- a/bin/itango_shell.sh
+++ b/bin/itango_shell.sh
@@ -1,5 +1,7 @@
 #!/bin/bash
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
+#
 
-exec docker exec -it itango /bin/bash
+exec docker exec -e "TANGO_HOST=$TANGO_HOST:$TANGO_PORT" -it itango /bin/bash
diff --git a/deploy/README.md b/deploy/README.md
deleted file mode 100644
index 5f49ffc5c..000000000
--- a/deploy/README.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Deployments
-
-Deploying is achieved through gitlab environments as well as ansible.
-Concretely the following files are involved:
-
-1. [deploy.yml](deploy.yml)
-2. [ansible.cfg](ansible.cfg)
-3. [.gitlab-ci.yml](../.gitlab-ci.yml)
-
-Once a tagged commit hits master a job is created for this tag that needs to
-be manually triggered. You should be able to find this pipeline on the
-[tags overview](https://git.astron.nl/lofar2.0/tango/-/tags).
-
-From here, with prior consent, the deployment can be started for any
-environment.
-
-The gitlab-ci.yml job defines each of the environments in parameterized way.
-The templated `.base_deploy` needs to be extended and have several variables
-defined. These variables need to be fed from
-[protected variables](https://git.astron.nl/lofar2.0/tango/-/settings/ci_cd)
-within gitlab.
-
-Below is a practical example of how to define an environment.
-
-```
-.deploy_example_base:
-  extends: .base_deploy
-  variables:
-    DEPLOY_USER: $EXAMPLE_USERNAME
-    DEPLOY_HOST: $EXAMPLE_HOSTNAME
-    DEPLOY_CONFIG: $EXAMPLE_CONFIG
-    DEPLOY_KEY: $EXAMPLE_KEY
-deploy_l2ts_start:
-  extends: .deploy_example_base
-  environment:
-    name: example
-```
-
-Such an environment will only appear in the
-[environments overview](https://git.astron.nl/lofar2.0/tango/-/environments)
-after the first deployment.
-
-The process of this deployment is handled through ansible as found in
-`deploy.yml`.
diff --git a/deploy/ansible.cfg b/deploy/ansible.cfg
deleted file mode 100644
index 8da5e9439..000000000
--- a/deploy/ansible.cfg
+++ /dev/null
@@ -1,3 +0,0 @@
-[defaults]
-host_key_checking = False
-inventory = hosts
diff --git a/deploy/deploy.yml b/deploy/deploy.yml
deleted file mode 100644
index 455c9da59..000000000
--- a/deploy/deploy.yml
+++ /dev/null
@@ -1,106 +0,0 @@
----
-- name: StationControl Early Deployment
-  hosts: all
-  vars:
-    base_station_config: "LOFAR_ConfigDb.json"
-    install_path: ~/git/tango
-    databaseds_port: 10000
-  tasks:
-     - name: Check make installed
-       include_tasks:
-         file: tasks/check_binary_install.yml
-       vars:
-         program: make
-     - name: Check git installed
-       include_tasks:
-         file: tasks/check_binary_install.yml
-       vars:
-         program: git
-     - name: Register tango directory status
-       shell: cd {{ install_path }}
-       args:
-         chdir: ~
-       changed_when: false
-       failed_when: tango_directory.rc not in [0,1]
-       register: tango_directory
-     - name: Register pending changes
-       shell: "! (git status | grep Changes)"
-       args:
-         chdir: "{{ install_path }}"
-       changed_when: false
-       failed_when: pending_changes.rc not in [0,1]
-       register: pending_changes
-# Several issues with this variable being unset in ansible 2.2
-# where encountered, keep debug in case needed
-     - debug: var=pending_changes
-     - name: Check tango directory status
-       fail:
-         msg: "Tango directory appears to be missing!"
-       when: tango_directory.rc not in [0]
-     - name: Check pending changes
-       fail:
-         msg: "Deployment repository seems to have pending changes!"
-       when: pending_changes.rc not in [0]
-     - name: Stop Current Station
-       changed_when: false
-       shell: "make stop"
-       args:
-         chdir: "{{ install_path }}/docker-compose"
-     - name: Git Fetch All
-       changed_when: false
-       shell: "git fetch --all"
-       args:
-         chdir: "{{ install_path }}"
-     - name: Update Sources
-       changed_when: false
-       shell: "git checkout {{ station_version }}"
-       args:
-         chdir: "{{ install_path }}"
-     - name: Pull Images
-       changed_when: false
-       shell: "make pull"
-       args:
-         chdir: "{{ install_path }}/docker-compose"
-     - name: Build Images
-       changed_when: false
-       shell: "make build"
-       args:
-         chdir: "{{ install_path }}/docker-compose"
-     - name: Start Database
-       changed_when: false
-       shell: "make minimal"
-       args:
-         chdir: "{{ install_path }}/docker-compose"
-     - name: Wait for databaseds
-       ansible.builtin.wait_for:
-         port: "{{ databaseds_port }}"
-         delay: 10
-     - name: Update Base Database Config
-       include_tasks:
-         file: tasks/update_database_config.yml
-       vars:
-         database_config: "{{ base_station_config }}"
-         base_path: "{{ install_path }}"
-     - name: Verify Base Database Config
-       include_tasks:
-         file: tasks/verify_database_config.yml
-       vars:
-         database_config: "{{ base_station_config }}"
-         base_path: "{{ install_path }}"
-     - name: Update Station Database Config
-       include_tasks:
-         file: tasks/update_database_config.yml
-       vars:
-         database_config: "stations/{{ station_config }}"
-         base_path: "{{ install_path }}"
-     - name: Verify Station Database Config
-       include_tasks:
-         file: tasks/verify_database_config.yml
-       vars:
-         database_config: "stations/{{ station_config }}"
-         base_path: "{{ install_path }}"
-     - name: Start Station
-       changed_when: false
-       shell: "make start"
-       args:
-         chdir: "{{ install_path }}/docker-compose"
diff --git a/deploy/tasks/check_binary_install.yml b/deploy/tasks/check_binary_install.yml
deleted file mode 100644
index f114fd87e..000000000
--- a/deploy/tasks/check_binary_install.yml
+++ /dev/null
@@ -1,9 +0,0 @@
- - name: Register installation status
-   command: which {{ program }}
-   changed_when: false
-   failed_when: installed.rc not in [0,1]
-   register: installed
- - name: Check installation status
-   fail:
-     msg: "{{ program }} does not appear to be installed!"
-   when: installed.rc not in [0]
\ No newline at end of file
diff --git a/deploy/tasks/update_database_config.yml b/deploy/tasks/update_database_config.yml
deleted file mode 100644
index be321c576..000000000
--- a/deploy/tasks/update_database_config.yml
+++ /dev/null
@@ -1,5 +0,0 @@
- - name: Update Database Config
-   changed_when: false
-   shell: "./sbin/load_ConfigDb.sh CDB/{{ database_config }}"
-   args:
-     chdir: "{{ base_path }}"
\ No newline at end of file
diff --git a/deploy/tasks/verify_database_config.yml b/deploy/tasks/verify_database_config.yml
deleted file mode 100644
index 1df3681e4..000000000
--- a/deploy/tasks/verify_database_config.yml
+++ /dev/null
@@ -1,9 +0,0 @@
- - name: Get Database Config
-   changed_when: false
-   shell: "./sbin/load_ConfigDb.sh CDB/{{ database_config }} 2>&1"
-   register: dsconfig_result
-   args:
-     chdir: "{{ base_path }}"
- - name: Verify Database Config
-   when: '"No changes needed" in dsconfig_result.stdout'
-   debug: msg="Database changes stored"
\ No newline at end of file
diff --git a/docker-compose/Makefile b/docker-compose/Makefile
index 4929af302..c8658b65a 100644
--- a/docker-compose/Makefile
+++ b/docker-compose/Makefile
@@ -6,19 +6,21 @@ MAKEPATH := $(abspath $(lastword $(MAKEFILE_LIST)))
 BASEDIR := $(notdir $(patsubst %/,%,$(dir $(MAKEPATH))))
 
 TAG ?= latest
+DNS ?= 127.0.0.11
 
 DOCKER_COMPOSE ?= docker compose
 
 DOCKER_COMPOSE_ENV_FILE := $(abspath .env)
 COMPOSE_FILES := $(wildcard *.yml)
-COMPOSE_FILE_ARGS := --env-file $(DOCKER_COMPOSE_ENV_FILE) $(foreach yml,$(COMPOSE_FILES),-f $(yml))
-
 ATTACH_COMPOSE_FILE_ARGS := $(foreach yml,$(filter-out tango.yml,$(COMPOSE_FILES)),-f $(yml))
+BUILD_ONLY_FILE_ARGS := -f tango.yml
+COMPOSE_FILE_ARGS := --env-file $(DOCKER_COMPOSE_ENV_FILE) $(ATTACH_COMPOSE_FILE_ARGS)
+
 
-# The default Docker network mode is tangonet.  The "host" network
+# The default Docker network mode is station.  The "host" network
 # mode will make the tangodb and archiverdb ports clash,
 # But we allow to overwrite it.
-NETWORK_MODE ?= tangonet
+NETWORK_MODE ?= station
 
 # Timeout used to await services to become healthy
 TIMEOUT ?= 300
@@ -97,11 +99,6 @@ endif
 # prevent collisions with jobs from the same project running at the same
 # time.
 #
-ifneq ($(CI_JOB_ID),)
-    NETWORK_MODE := tangonet-$(CI_JOB_ID)
-else
-    $(info Network mode cannot be host for the archiver! It won't work unless you set the env var CI_JOB_ID=local)
-endif
 
 ifeq ($(OS),Windows_NT)
     $(error Sorry, Windows is not supported yet)
@@ -166,7 +163,8 @@ DOCKER_COMPOSE_ARGS := DISPLAY=$(DISPLAY) \
     CONTAINER_EXECUTION_UID=$(shell id -u) \
     DOCKER_GID=$(DOCKER_GID) \
     TEST_MODULE=$(INTEGRATION_MODULE) \
-    TAG=$(TAG)
+    TAG=$(TAG) \
+    DNS=$(DNS)
 
 
 .PHONY: up base base-nocache down minimal context run integration start stop restart build build-nocache status clean pull help await
@@ -181,15 +179,15 @@ ifneq ($(NO_BASE),1)
 endif
 
 base-nocache: context ## Rebuild base lofar device image
-	$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) build --no-cache --progress=plain lofar-device-base
+	$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) $(BUILD_ONLY_FILE_ARGS) build --no-cache --progress=plain lofar-device-base
 
 build: base ## build images
 	# docker-compose does not support build dependencies, so manage those here
-	$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) build --parallel --progress=plain $(SERVICE)
+	$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) $(BUILD_ONLY_FILE_ARGS) build --parallel --progress=plain $(SERVICE)
 
 build-nocache: base-nocache ## rebuild images from scratch
 	# docker-compose does not support build dependencies, so manage those here
-	$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) build --no-cache --progress=plain $(SERVICE)
+	$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) $(BUILD_ONLY_FILE_ARGS) build --no-cache --progress=plain $(SERVICE)
 
 up: base minimal  ## start the base TANGO system and prepare requested services
 	$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) up --no-start --no-recreate $(SERVICE)
@@ -219,7 +217,7 @@ ifneq ($(NETWORK_MODE),host)
 	docker network inspect 9000-$(NETWORK_MODE) &> /dev/null || ([ $$? -ne 0 ] && docker network create 9000-$(NETWORK_MODE) -o com.docker.network.driver.mtu=9000)
 endif
 
-	$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) -f tango.yml -f networks.yml up --no-recreate -d
+
 
 context: ## Move and build the necessary files to create minimal docker context
 	rm -rf ./tmp; \
@@ -236,8 +234,8 @@ context: ## Move and build the necessary files to create minimal docker context
 bootstrap: pull build # first start, initialise from scratch
 	$(MAKE) start dsconfig # boot up containers to load configurations
 	sleep 5 # wait for dsconfig container to come up
-	../sbin/update_ConfigDb.sh ../CDB/LOFAR_ConfigDb.json # load default configuration
-	../sbin/update_ConfigDb.sh ../CDB/stations/simulators_ConfigDb.json # by default, use simulators
+	../sbin/dsconfig.sh --update ../CDB/LOFAR_ConfigDb.json # load default configuration
+	../sbin/dsconfig.sh --update ../CDB/stations/simulators_ConfigDb.json # by default, use simulators
 
 start: up ## start a service (usage: make start <servicename>)
 	if [ $(UNAME_S) = Linux ]; then touch ~/.Xauthority; chmod a+r ~/.Xauthority; fi
@@ -266,16 +264,18 @@ await:  ## Await every container with total max timeout of 300, do not reset tim
 		if [ -z "$${service_has_health}" ]; then \
 			continue; \
 		fi; \
+		echo -n "Whait for service $${i} to become healthy .."; \
 		while [ "$$(docker inspect -f '{{.State.Health.Status}}' $${current_service})" != "healthy" ] ; do \
-			sleep 1; \
+			echo -n '.'; \
+			sleep 2; \
 			time=$$(expr $$time + 1); \
 			if [ $${time} -gt $(TIMEOUT) ]; then \
-				echo "Timeout reached waiting for $${i} to become healthy"; \
+				echo "timeout"; \
 				docker logs $${i}; \
 				exit 1; \
 			fi; \
 		done; \
-		echo "Service $${i} is healthy"; \
+		echo ". [ok]"; \
 	done
 
 status:  ## show the container status
diff --git a/docker-compose/README.md b/docker-compose/README.md
index 05882d6ad..528f253ac 100644
--- a/docker-compose/README.md
+++ b/docker-compose/README.md
@@ -42,12 +42,9 @@ are used in production.
     - webservers / user interfaces
         - jupyterlab
         - [grafana](grafana/README.md)
-        - [http-json-schemas](http-json-schemas/README.md)
     - logging / monitoring
         - prometheus-node-exporter
-        - [tango-prometheus-exporter](tango-prometheus-exporter/README.md)
-        - logstash (grafana-logstash-output-loki)
-        - loki
+        - [tango-prometheus-exporter](tango-prometheus-exporter/README.md)        - loki
     - tango
         - itango
         - tango-rest
diff --git a/docker-compose/device-afh.yml b/docker-compose/device-afh.yml
index 849463c1c..f116d5e72 100644
--- a/docker-compose/device-afh.yml
+++ b/docker-compose/device-afh.yml
@@ -27,6 +27,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5715:5715" # unique port for this DS
       - "5815:5815" # ZeroMQ event port
@@ -50,7 +51,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-afh AFH STAT -v -ORBendPoint giop:tcp:0:5715 -ORBendPointPublish giop:tcp:${HOSTNAME}:5715
+      - l2ss-ds AFH STAT -v -ORBendPoint giop:tcp:0:5715 -ORBendPointPublish giop:tcp:${HOSTNAME}:5715
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-afl.yml b/docker-compose/device-afl.yml
index 5b47626ec..8909990d4 100644
--- a/docker-compose/device-afl.yml
+++ b/docker-compose/device-afl.yml
@@ -50,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-afl AFL STAT -v -ORBendPoint giop:tcp:0:5730 -ORBendPointPublish giop:tcp:${HOSTNAME}:5730
+      - l2ss-ds AFL STAT -v -ORBendPoint giop:tcp:0:5730 -ORBendPointPublish giop:tcp:${HOSTNAME}:5730
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-aps.yml b/docker-compose/device-aps.yml
index 23690af22..dc06afcb9 100644
--- a/docker-compose/device-aps.yml
+++ b/docker-compose/device-aps.yml
@@ -18,6 +18,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5728:5728" # unique port for this DS
       - "5828:5828" # ZeroMQ event port
@@ -41,7 +42,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-aps aps STAT -v  -v -ORBendPoint giop:tcp:device-aps:5728 -ORBendPointPublish giop:tcp:${HOSTNAME}:5728
+      - l2ss-ds APS STAT -v  -v -ORBendPoint giop:tcp:device-aps:5728 -ORBendPointPublish giop:tcp:${HOSTNAME}:5728
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-apsct.yml b/docker-compose/device-apsct.yml
index 86c24b6a6..8ebd5c104 100644
--- a/docker-compose/device-apsct.yml
+++ b/docker-compose/device-apsct.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5709:5709" # unique port for this DS
       - "5809:5809" # ZeroMQ event port
@@ -49,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-apsct Apsct STAT -v  -v -ORBendPoint giop:tcp:device-apsct:5709 -ORBendPointPublish giop:tcp:${HOSTNAME}:5709
+      - l2ss-ds APSCT STAT -v  -v -ORBendPoint giop:tcp:device-apsct:5709 -ORBendPointPublish giop:tcp:${HOSTNAME}:5709
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-apspu.yml b/docker-compose/device-apspu.yml
index ac585e6c4..5c268b081 100644
--- a/docker-compose/device-apspu.yml
+++ b/docker-compose/device-apspu.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5710:5710" # unique port for this DS
       - "5810:5810" # ZeroMQ event port
@@ -49,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-apspu Apspu STAT -v -ORBendPoint giop:tcp:device-apspu:5710 -ORBendPointPublish giop:tcp:${HOSTNAME}:5710
+      - l2ss-ds APSPU STAT -v -ORBendPoint giop:tcp:device-apspu:5710 -ORBendPointPublish giop:tcp:${HOSTNAME}:5710
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-beamlet.yml b/docker-compose/device-beamlet.yml
index 7c734e0e3..028376efc 100644
--- a/docker-compose/device-beamlet.yml
+++ b/docker-compose/device-beamlet.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5712:5712" # unique port for this DS
       - "5812:5812" # ZeroMQ event port
@@ -49,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-beamlet Beamlet STAT -v -ORBendPoint giop:tcp:0:5712 -ORBendPointPublish giop:tcp:${HOSTNAME}:5712
+      - l2ss-ds Beamlet STAT -v -ORBendPoint giop:tcp:0:5712 -ORBendPointPublish giop:tcp:${HOSTNAME}:5712
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-bst.yml b/docker-compose/device-bst.yml
index e1fd18600..23db88c15 100644
--- a/docker-compose/device-bst.yml
+++ b/docker-compose/device-bst.yml
@@ -27,6 +27,7 @@ services:
     networks:
       - control
       - data
+    dns: ${DNS}
     ports:
       - "5003:5003/udp" # port to receive SST UDP packets on (first antennafield)
       - "5103:5103/tcp" # port to emit SST TCP packets on
@@ -56,7 +57,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-bst BST STAT -v -ORBendPoint giop:tcp:0:5717 -ORBendPointPublish giop:tcp:${HOSTNAME}:5717
+      - l2ss-ds BST STAT -v -ORBendPoint giop:tcp:0:5717 -ORBendPointPublish giop:tcp:${HOSTNAME}:5717
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-calibration.yml b/docker-compose/device-calibration.yml
index 387b02286..495e8ea13 100644
--- a/docker-compose/device-calibration.yml
+++ b/docker-compose/device-calibration.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5724:5724" # unique port for this DS
       - "5824:5824" # ZeroMQ event port
@@ -51,9 +52,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-calibration Calibration STAT -v -ORBendPoint giop:tcp:0:5724 -ORBendPointPublish giop:tcp:${HOSTNAME}:5724
+      - l2ss-ds Calibration STAT -v -ORBendPoint giop:tcp:0:5724 -ORBendPointPublish giop:tcp:${HOSTNAME}:5724
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
-    depends_on:
-      - object-storage
diff --git a/docker-compose/device-ccd.yml b/docker-compose/device-ccd.yml
index 950800e33..5d4292c34 100644
--- a/docker-compose/device-ccd.yml
+++ b/docker-compose/device-ccd.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5721:5721" # unique port for this DS
       - "5821:5821" # ZeroMQ event port
@@ -49,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-ccd Ccd STAT -v -ORBendPoint giop:tcp:device-ccd:5721 -ORBendPointPublish giop:tcp:${HOSTNAME}:5721
+      - l2ss-ds CCD STAT -v -ORBendPoint giop:tcp:device-ccd:5721 -ORBendPointPublish giop:tcp:${HOSTNAME}:5721
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-configuration.yml b/docker-compose/device-configuration.yml
index 1a923f15b..2cbdfb6a5 100644
--- a/docker-compose/device-configuration.yml
+++ b/docker-compose/device-configuration.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5722:5722" # unique port for this DS
       - "5822:5822" # ZeroMQ event port
@@ -49,9 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-configuration Configuration STAT -v -ORBendPoint giop:tcp:device-configuration:5722 -ORBendPointPublish giop:tcp:${HOSTNAME}:5722
+      - l2ss-ds Configuration STAT -v -ORBendPoint giop:tcp:device-configuration:5722 -ORBendPointPublish giop:tcp:${HOSTNAME}:5722
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
-    depends_on:
-      - http-json-schemas
diff --git a/docker-compose/device-digitalbeam.yml b/docker-compose/device-digitalbeam.yml
index b093ee63e..c47268f28 100644
--- a/docker-compose/device-digitalbeam.yml
+++ b/docker-compose/device-digitalbeam.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5713:5713" # unique port for this DS
       - "5813:5813" # ZeroMQ event port
@@ -49,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-digitalbeam DigitalBeam STAT -v -ORBendPoint giop:tcp:0:5713 -ORBendPointPublish giop:tcp:${HOSTNAME}:5713
+      - l2ss-ds DigitalBeam STAT -v -ORBendPoint giop:tcp:0:5713 -ORBendPointPublish giop:tcp:${HOSTNAME}:5713
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-docker.yml b/docker-compose/device-docker.yml
index a9080eeb5..88ef8d898 100644
--- a/docker-compose/device-docker.yml
+++ b/docker-compose/device-docker.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5705:5705" # unique port for this DS
       - "5805:5805" # ZeroMQ event port
@@ -51,7 +52,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-docker Docker STAT -v -ORBendPoint giop:tcp:0:5705 -ORBendPointPublish giop:tcp:${HOSTNAME}:5705
+      - l2ss-ds Docker STAT -v -ORBendPoint giop:tcp:0:5705 -ORBendPointPublish giop:tcp:${HOSTNAME}:5705
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-ec.yml b/docker-compose/device-ec.yml
index f944c751a..ac5b62a7c 100644
--- a/docker-compose/device-ec.yml
+++ b/docker-compose/device-ec.yml
@@ -21,6 +21,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5729:5729" # unique port for this DS
       - "5829:5829" # ZeroMQ event port
@@ -44,7 +45,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-ec ec STAT -v -ORBendPoint giop:tcp:device-ec:5729 -ORBendPointPublish giop:tcp:${HOSTNAME}:5729
+      - l2ss-ds EC STAT -v -ORBendPoint giop:tcp:device-ec:5729 -ORBendPointPublish giop:tcp:${HOSTNAME}:5729
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-observation-control.yml b/docker-compose/device-observation-control.yml
index b5b7f9fe5..c21733db5 100644
--- a/docker-compose/device-observation-control.yml
+++ b/docker-compose/device-observation-control.yml
@@ -25,6 +25,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5703:5703" # unique port for this DS
       - "5803:5803" # ZeroMQ event port
@@ -48,9 +49,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-observationcontrol ObservationControl STAT -v -ORBendPoint giop:tcp:0:5703 -ORBendPointPublish giop:tcp:${HOSTNAME}:5703
+      - l2ss-ds ObservationControl STAT -v -ORBendPoint giop:tcp:0:5703 -ORBendPointPublish giop:tcp:${HOSTNAME}:5703
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
-    depends_on:
-      - http-json-schemas
diff --git a/docker-compose/device-pcon.yml b/docker-compose/device-pcon.yml
index e833bb55d..c404f8ad1 100644
--- a/docker-compose/device-pcon.yml
+++ b/docker-compose/device-pcon.yml
@@ -21,6 +21,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5720:5720" # unique port for this DS
       - "5820:5820" # ZeroMQ event port
@@ -44,7 +45,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-pcon pcon STAT -v -ORBendPoint giop:tcp:device-pcon:5720 -ORBendPointPublish giop:tcp:${HOSTNAME}:5720
+      - l2ss-ds PCON STAT -v -ORBendPoint giop:tcp:device-pcon:5720 -ORBendPointPublish giop:tcp:${HOSTNAME}:5720
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-psoc.yml b/docker-compose/device-psoc.yml
index 998a0ad2f..5e19bd580 100644
--- a/docker-compose/device-psoc.yml
+++ b/docker-compose/device-psoc.yml
@@ -21,6 +21,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5719:5719" # unique port for this DS
       - "5819:5819" # ZeroMQ event port
@@ -44,7 +45,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-psoc PSOC STAT -v -ORBendPoint giop:tcp:device-psoc:5719 -ORBendPointPublish giop:tcp:${HOSTNAME}:5719
+      - l2ss-ds PSOC STAT -v -ORBendPoint giop:tcp:device-psoc:5719 -ORBendPointPublish giop:tcp:${HOSTNAME}:5719
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-recvh.yml b/docker-compose/device-recvh.yml
index feb0ce875..d366e2bad 100644
--- a/docker-compose/device-recvh.yml
+++ b/docker-compose/device-recvh.yml
@@ -18,6 +18,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5725:5725" # unique port for this DS
       - "5825:5825" # ZeroMQ event port
@@ -41,7 +42,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-recvh RECVH STAT -v -ORBendPoint giop:tcp:device-recvh:5725 -ORBendPointPublish giop:tcp:${HOSTNAME}:5725
+      - l2ss-ds RECVH STAT -v -ORBendPoint giop:tcp:device-recvh:5725 -ORBendPointPublish giop:tcp:${HOSTNAME}:5725
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-recvl.yml b/docker-compose/device-recvl.yml
index 810505463..4a2894350 100644
--- a/docker-compose/device-recvl.yml
+++ b/docker-compose/device-recvl.yml
@@ -18,6 +18,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5726:5726" # unique port for this DS
       - "5826:5826" # ZeroMQ event port
@@ -41,7 +42,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-recvl RECVL STAT -v -ORBendPoint giop:tcp:device-recvl:5726 -ORBendPointPublish giop:tcp:${HOSTNAME}:5726
+      - l2ss-ds RECVL STAT -v -ORBendPoint giop:tcp:device-recvl:5726 -ORBendPointPublish giop:tcp:${HOSTNAME}:5726
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-sdp.yml b/docker-compose/device-sdp.yml
index 02ce3f99e..dd47fad15 100644
--- a/docker-compose/device-sdp.yml
+++ b/docker-compose/device-sdp.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5701:5701" # unique port for this DS
       - "5801:5801" # ZeroMQ event port
@@ -49,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-sdp SDP STAT -v -ORBendPoint giop:tcp:device-sdp:5701 -ORBendPointPublish giop:tcp:${HOSTNAME}:5701
+      - l2ss-ds SDP STAT -v -ORBendPoint giop:tcp:device-sdp:5701 -ORBendPointPublish giop:tcp:${HOSTNAME}:5701
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-sdpfirmware.yml b/docker-compose/device-sdpfirmware.yml
index 5fd34bf0f..c61c4a565 100644
--- a/docker-compose/device-sdpfirmware.yml
+++ b/docker-compose/device-sdpfirmware.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5727:5727" # unique port for this DS
       - "5827:5827" # ZeroMQ event port
@@ -49,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-sdpfirmware SDPFirmware STAT -v -ORBendPoint giop:tcp:device-sdpfirmware:5727 -ORBendPointPublish giop:tcp:${HOSTNAME}:5727
+      - l2ss-ds SDPFirmware STAT -v -ORBendPoint giop:tcp:device-sdpfirmware:5727 -ORBendPointPublish giop:tcp:${HOSTNAME}:5727
     restart: unless-stopped
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-sst.yml b/docker-compose/device-sst.yml
index a6ba4f2b2..66b1da00a 100644
--- a/docker-compose/device-sst.yml
+++ b/docker-compose/device-sst.yml
@@ -27,6 +27,7 @@ services:
     networks:
       - control
       - data
+    dns: ${DNS}
     ports:
       - "5001:5001/udp" # port to receive SST UDP packets on (first antennafield)
       - "5101:5101/tcp" # port to emit SST TCP packets on
@@ -56,7 +57,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-sst SST STAT -v -ORBendPoint giop:tcp:0:5702 -ORBendPointPublish giop:tcp:${HOSTNAME}:5702
+      - l2ss-ds SST STAT -v -ORBendPoint giop:tcp:0:5702 -ORBendPointPublish giop:tcp:${HOSTNAME}:5702
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-station-manager.yml b/docker-compose/device-station-manager.yml
index 044de2d17..7570440c6 100644
--- a/docker-compose/device-station-manager.yml
+++ b/docker-compose/device-station-manager.yml
@@ -21,6 +21,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5723:5723" # unique port for this DS
       - "5823:5823" # ZeroMQ event port
@@ -44,7 +45,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-station-manager StationManager STAT -v -ORBendPoint giop:tcp:device-station-manager:5723 -ORBendPointPublish giop:tcp:${HOSTNAME}:5723
+      - l2ss-ds StationManager STAT -v -ORBendPoint giop:tcp:device-station-manager:5723 -ORBendPointPublish giop:tcp:${HOSTNAME}:5723
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-temperature-manager.yml b/docker-compose/device-temperature-manager.yml
index 35e2defff..316a49301 100644
--- a/docker-compose/device-temperature-manager.yml
+++ b/docker-compose/device-temperature-manager.yml
@@ -21,6 +21,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5716:5716" # unique port for this DS
       - "5816:5816" # ZeroMQ event port
@@ -44,7 +45,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-temperaturemanager TemperatureManager STAT -v -ORBendPoint giop:tcp:0:5716 -ORBendPointPublish giop:tcp:${HOSTNAME}:5716
+      - l2ss-ds TemperatureManager STAT -v -ORBendPoint giop:tcp:0:5716 -ORBendPointPublish giop:tcp:${HOSTNAME}:5716
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-tilebeam.yml b/docker-compose/device-tilebeam.yml
index c67cb289c..6c623144d 100644
--- a/docker-compose/device-tilebeam.yml
+++ b/docker-compose/device-tilebeam.yml
@@ -21,6 +21,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5711:5711" # unique port for this DS
       - "5811:5811" # ZeroMQ event port
@@ -45,7 +46,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-tilebeam TileBeam STAT -v -ORBendPoint giop:tcp:0:5711 -ORBendPointPublish giop:tcp:${HOSTNAME}:5711
+      - l2ss-ds TileBeam STAT -v -ORBendPoint giop:tcp:0:5711 -ORBendPointPublish giop:tcp:${HOSTNAME}:5711
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-unb2.yml b/docker-compose/device-unb2.yml
index 82ef38460..8e127bb04 100644
--- a/docker-compose/device-unb2.yml
+++ b/docker-compose/device-unb2.yml
@@ -26,6 +26,7 @@ services:
         max-file: "10"
     networks:
       - control
+    dns: ${DNS}
     ports:
       - "5704:5704" # unique port for this DS
       - "5804:5804" # ZeroMQ event port
@@ -49,7 +50,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-unb2 UNB2 STAT -v -ORBendPoint giop:tcp:device-unb2:5704 -ORBendPointPublish giop:tcp:${HOSTNAME}:5704
+      - l2ss-ds UNB2 STAT -v -ORBendPoint giop:tcp:device-unb2:5704 -ORBendPointPublish giop:tcp:${HOSTNAME}:5704
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/device-xst.yml b/docker-compose/device-xst.yml
index 0890e91d7..5c3bc0735 100644
--- a/docker-compose/device-xst.yml
+++ b/docker-compose/device-xst.yml
@@ -27,6 +27,7 @@ services:
     networks:
       - control
       - data
+    dns: ${DNS}
     ports:
       - "5002:5002/udp" # port to receive XST UDP packets on (first antennafield)
       - "5102:5102/tcp" # port to emit XST TCP packets on
@@ -56,7 +57,7 @@ services:
       - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - l2ss-xst XST STAT -v -ORBendPoint giop:tcp:0:5706 -ORBendPointPublish giop:tcp:${HOSTNAME}:5706
+      - l2ss-ds XST STAT -v -ORBendPoint giop:tcp:0:5706 -ORBendPointPublish giop:tcp:${HOSTNAME}:5706
     restart: on-failure
     stop_signal: SIGINT # request a graceful shutdown of Tango
     stop_grace_period: 2s
diff --git a/docker-compose/grafana/Dockerfile b/docker-compose/grafana/Dockerfile
index b8d793c0b..3e753e666 100644
--- a/docker-compose/grafana/Dockerfile
+++ b/docker-compose/grafana/Dockerfile
@@ -1,5 +1,6 @@
 FROM git.astron.nl:5000/lofar2.0/grafana-station-dashboards:latest
 
+COPY datasources/tangodb.yaml /etc/grafana/provisioning/datasources/tangodb.yaml
 COPY stationcontrol-dashboards.yaml /etc/grafana/provisioning/dashboards/
 
 RUN mkdir -p /var/lib/grafana/database
diff --git a/docker-compose/http-json-schemas.yml b/docker-compose/http-json-schemas.yml
deleted file mode 100644
index 46db5c88a..000000000
--- a/docker-compose/http-json-schemas.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-#
-# Docker compose file that launches a LOFAR2.0 station's
-# ObservationControl device. It also runs the dynamically
-# created Observation devices.
-#
-# Defines:
-#   - http-json-schemas: LOFAR2.0 station
-#
-version: '2.1'
-
-services:
-  http-json-schemas:
-    image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/http-json-schemas:${TAG}
-    build:
-      context: http-json-schemas
-    hostname: http-json-schemas
-    container_name: http-json-schemas
-    networks:
-      - control
-    environment:
-      - NGINX_HOST=http-json-schemas
-      - NGINX_PORT=80
-    ports:
-      - 9999:80
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "100m"
-        max-file: "10"
-    restart: unless-stopped
diff --git a/docker-compose/http-json-schemas/Dockerfile b/docker-compose/http-json-schemas/Dockerfile
deleted file mode 100644
index 5d6aad5cf..000000000
--- a/docker-compose/http-json-schemas/Dockerfile
+++ /dev/null
@@ -1,2 +0,0 @@
-FROM nginx
-COPY definitions /usr/share/nginx/html
\ No newline at end of file
diff --git a/docker-compose/http-json-schemas/README.md b/docker-compose/http-json-schemas/README.md
deleted file mode 100644
index ba91c82f0..000000000
--- a/docker-compose/http-json-schemas/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# HTTP JSON Schemas
-
-Service to serve JSON schema files over HTTP. Used by configuration manager device.
diff --git a/docker-compose/integration-test.yml b/docker-compose/integration-test.yml
index 8b2cc0a33..d31670e15 100644
--- a/docker-compose/integration-test.yml
+++ b/docker-compose/integration-test.yml
@@ -20,6 +20,7 @@ services:
     container_name: integration-test
     networks:
       - control
+    dns: ${DNS}
     extra_hosts:
       - "host.docker.internal:host-gateway"
     volumes:
diff --git a/docker-compose/jupyter-lab.yml b/docker-compose/jupyter-lab.yml
index 4dfdbd852..71f695dee 100644
--- a/docker-compose/jupyter-lab.yml
+++ b/docker-compose/jupyter-lab.yml
@@ -17,9 +17,6 @@ services:
     image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/jupyter-lab:${TAG}
     build:
       context: jupyter-lab
-      args:
-        CONTAINER_EXECUTION_UID: ${CONTAINER_EXECUTION_UID}
-        SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     hostname: jupyter-lab
     container_name: jupyter-lab
     logging:
@@ -38,9 +35,5 @@ services:
       - TANGO_HOST=${TANGO_HOST}
     ports:
       - "8888:8888"
-    user: ${CONTAINER_EXECUTION_UID}
-    working_dir: /jupyter-notebooks
-    entrypoint:
-      - /opt/lofar/tango/bin/start-ds.sh
-      - jupyter lab --port=8888 --no-browser --ip=0.0.0.0 --allow-root --NotebookApp.token= --NotebookApp.password=
+    command: jupyter lab --port=8888 --no-browser --ip=0.0.0.0 --allow-root --NotebookApp.token= --NotebookApp.password= --Application.log_level=0
     restart: unless-stopped
diff --git a/docker-compose/jupyter-lab/Dockerfile b/docker-compose/jupyter-lab/Dockerfile
index 1ac48fd36..74c9bfdfc 100644
--- a/docker-compose/jupyter-lab/Dockerfile
+++ b/docker-compose/jupyter-lab/Dockerfile
@@ -1,76 +1,47 @@
-ARG SOURCE_IMAGE
-FROM ${SOURCE_IMAGE}
-
-# UID if the user that this container will run under. This is needed to give directories
-# that are needed for temporary storage the proper owner and access rights.
-ARG CONTAINER_EXECUTION_UID=1000
-
-# Create new user with uid but only if uid not used
-RUN sudo adduser --disabled-password --system --uid ${CONTAINER_EXECUTION_UID} --no-create-home --home ${HOME} user || exit 0
-RUN sudo chown ${CONTAINER_EXECUTION_UID} -R ${HOME}
-
-# Add compiler to install python packages which come with C++ code
-RUN sudo apt-get update -y && sudo apt-get install -y g++ gcc python3-dev
-
-# Install git to install pip requirements from git
-RUN sudo apt-get update -y && sudo apt-get install -y git
-
-# Install dependencies of our scripts (bin/start-ds.sh)
-RUN sudo apt-get update -y && sudo apt-get install -y rsync
+# needs to stay v 3 until https://github.com/jupyterlab/jupyterlab-git/issues/1245
+FROM jupyter/scipy-notebook:lab-3.6.3
+
+USER root
+RUN --mount=type=cache,target=/var/cache/apt \
+    apt-get update
+RUN --mount=type=cache,target=/var/cache/apt \
+    apt-get upgrade -y
+RUN --mount=type=cache,target=/var/cache/apt \
+    apt-get install -y git
+
+USER ${NB_UID}
+RUN pip install lofar-station-client --index-url https://git.astron.nl/api/v4/projects/395/packages/pypi/simple
+RUN pip install tangostationcontrol --index-url https://git.astron.nl/api/v4/projects/71/packages/pypi/simple
+RUN mamba install --yes jupyterlab-git && \
+        mamba clean --all -f -y && \
+        fix-permissions "${CONDA_DIR}" && \
+        fix-permissions "/home/${NB_USER}"
+
+RUN jupyter server extension enable --py jupyterlab_git
 
 COPY requirements.txt ./
-RUN sudo pip3 install -r requirements.txt
+RUN pip install -r requirements.txt
+RUN rm requirements.txt
 
-# Install tzdata first and supress dialog to select timezone
-RUN echo 'tzdata tzdata/Areas select Europe' | sudo debconf-set-selections
-RUN echo 'tzdata tzdata/Zones/Europe select Amsterdam' | sudo debconf-set-selections
-RUN echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections
-RUN sudo apt-get update && sudo apt-get install -y tzdata
-# Install some version of the casacore measures tables, to allow basic delay computation analysis in the notebooks
-RUN sudo apt-get update && sudo apt-get install -y casacore-data
+USER root
 
-# see https://github.com/jupyter/nbconvert/issues/1434
-RUN sudo bash -c "echo DEFAULT_ARGS += [\\\"--no-sandbox\\\"] >> /usr/local/lib/python3.10/dist-packages/pyppeteer/launcher.py"
-RUN sudo apt-get update -y && sudo apt-get install -y git 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 update -y && sudo apt-get install -y texlive-xetex texlive-fonts-recommended texlive-latex-recommended cm-super
-
-# Configure jupyter_bokeh
-RUN sudo mkdir -p /usr/share/jupyter /usr/etc
-RUN sudo chmod a+rwx /usr/share/jupyter /usr/etc
-RUN sudo jupyter nbextension install --sys-prefix --symlink --py jupyter_bokeh
-RUN sudo jupyter nbextension enable jupyter_bokeh --py --sys-prefix
+COPY jupyter_notebook_config.py /home/${NB_USER}/.jupyter/
+RUN fix-permissions /home/${NB_USER}/.jupyter/
 
 # Install profiles for ipython & jupyter
 COPY ipython-profiles /opt/ipython-profiles/
-RUN sudo chown ${CONTAINER_EXECUTION_UID} -R /opt/ipython-profiles
+RUN fix-permissions /opt/ipython-profiles
 COPY jupyter-kernels /usr/local/share/jupyter/kernels/
 
-# Install patched jupyter executable
-COPY jupyter-notebook /usr/local/bin/jupyter-notebook
-
 # Install our custom configuration
-COPY user-settings /home/tango/.jupyter/lab/user-settings
+COPY user-settings /home/${NB_USER}/.jupyter/lab/user-settings
+RUN fix-permissions /home/${NB_USER}/.jupyter/lab
 
-# Add Tini. Tini operates as a process subreaper for jupyter. This prevents kernel crashes.
-ENV TINI_VERSION v0.6.0
-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
+USER ${NB_UID}
 
-# Needed to perform certain migration actions during startup
-RUN sudo mkdir -p /home/tango/.jupyter/lab/workspaces
-RUN sudo chmod -R 0777 /home/tango/.jupyter
-
-USER ${CONTAINER_EXECUTION_UID}
-# pyppeteer-install installs in the homedir, so run it as the user that will execute the notebook
-RUN pyppeteer-install
+WORKDIR "${HOME}"
 
 # Configure using git from Jupyter Lab. Since the web service is open, we can't know who is
 # committing. We thus tie commits to this machine instead of to the user.
 RUN git config --global user.email "jupyterlab-${HOSTNAME}@lofar.eu"
 RUN git config --global user.name "JupyterLab on ${HOSTNAME}"
-
-# Enable Jupyter lab
-ENV JUPYTER_ENABLE_LAB=yes
diff --git a/docker-compose/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py b/docker-compose/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
index c76c3e2b4..721c6fba3 100644
--- a/docker-compose/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
+++ b/docker-compose/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
@@ -37,7 +37,7 @@ sst_l = OptionalDeviceProxy("STAT/SST/LBA")
 xst_l = OptionalDeviceProxy("STAT/XST/LBA")
 beamlet_l = OptionalDeviceProxy("STAT/Beamlet/LBA")
 digitalbeam_l = OptionalDeviceProxy("STAT/DigitalBeam/LBA")
-af_l = OptionalDeviceProxy("STAT/AFL/LBA")
+antennafield_l = af_l = OptionalDeviceProxy("STAT/AFL/LBA")
 
 sdpfirmware_h = OptionalDeviceProxy("STAT/SDPFirmware/HBA")
 sdp_h = OptionalDeviceProxy("STAT/SDP/HBA")
@@ -47,7 +47,7 @@ xst_h = OptionalDeviceProxy("STAT/XST/HBA")
 beamlet_h = OptionalDeviceProxy("STAT/Beamlet/HBA")
 digitalbeam_h = OptionalDeviceProxy("STAT/DigitalBeam/HBA")
 tilebeam_h = OptionalDeviceProxy("STAT/TileBeam/HBA")
-af_h = OptionalDeviceProxy("STAT/AFH/HBA")
+antennafield_h = af_h = OptionalDeviceProxy("STAT/AFH/HBA")
 
 sdpfirmware_h0 = OptionalDeviceProxy("STAT/SDPFirmware/HBA0")
 sdp_h0 = OptionalDeviceProxy("STAT/SDP/HBA0")
@@ -57,7 +57,7 @@ xst_h0 = OptionalDeviceProxy("STAT/XST/HBA0")
 beamlet_h0 = OptionalDeviceProxy("STAT/Beamlet/HBA0")
 digitalbeam_h0 = OptionalDeviceProxy("STAT/DigitalBeam/HBA0")
 tilebeam_h0 = OptionalDeviceProxy("STAT/TileBeam/HBA0")
-af_h0 = OptionalDeviceProxy("STAT/AFH/HBA0")
+antennafield_h0 = af_h0 = OptionalDeviceProxy("STAT/AFH/HBA0")
 
 sdpfirmware_h1 = OptionalDeviceProxy("STAT/SDPFirmware/HBA1")
 sdp_h1 = OptionalDeviceProxy("STAT/SDP/HBA1")
@@ -67,7 +67,7 @@ xst_h1 = OptionalDeviceProxy("STAT/XST/HBA1")
 beamlet_h1 = OptionalDeviceProxy("STAT/Beamlet/HBA1")
 digitalbeam_h1 = OptionalDeviceProxy("STAT/DigitalBeam/HBA1")
 tilebeam_h1 = OptionalDeviceProxy("STAT/TileBeam/HBA1")
-af_h1 = OptionalDeviceProxy("STAT/AFH/HBA1")
+antennafield_h1 = af_h1 = OptionalDeviceProxy("STAT/AFH/HBA1")
 
 stationmanager = OptionalDeviceProxy("STAT/StationManager/1")
 ccd = OptionalDeviceProxy("STAT/CCD/1")
@@ -96,7 +96,7 @@ devices = (
         xst_l,
         beamlet_l,
         digitalbeam_l,
-        antennafield_l,
+        af_l,
         sdpfirmware_h,
         sdp_h,
         bst_h,
@@ -105,7 +105,7 @@ devices = (
         beamlet_h,
         digitalbeam_h,
         tilebeam_h,
-        antennafield_h,
+        af_h,
         sdpfirmware_h0,
         sdp_h0,
         bst_h0,
@@ -114,7 +114,7 @@ devices = (
         beamlet_h0,
         digitalbeam_h0,
         tilebeam_h0,
-        antennafield_h0,
+        af_h0,
         sdpfirmware_h1,
         sdp_h1,
         bst_h1,
@@ -123,7 +123,7 @@ devices = (
         beamlet_h1,
         digitalbeam_h1,
         tilebeam_h1,
-        antennafield_h1,
+        af_h1,
     ]
     + apscts
     + apspus
diff --git a/docker-compose/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/02-stationcontrol.py b/docker-compose/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/02-stationcontrol.py
index d21ed1cf0..a9c046560 100644
--- a/docker-compose/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/02-stationcontrol.py
+++ b/docker-compose/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/02-stationcontrol.py
@@ -1 +1,8 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
+# noinspection PyUnresolvedReferences
+import lofar_station_client
+
+# noinspection PyUnresolvedReferences
 import tangostationcontrol
diff --git a/docker-compose/jupyter-lab/jupyter-notebook b/docker-compose/jupyter-lab/jupyter-notebook
deleted file mode 100755
index e594feb90..000000000
--- a/docker-compose/jupyter-lab/jupyter-notebook
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/python3
-# -*- coding: utf-8 -*-
-# An adjustment of the `jupyter-notebook' executable patched to:
-#  - log to logstash-loki
-#
-# We go straight for the notebook executable here, as the "jupyter" command
-# execvp's into the requested notebook subcommand, erasing all configuration
-# we set here.
-import re
-import sys
-
-from notebook.notebookapp import main
-
-from logstash_async.handler import AsynchronousLogstashHandler, LogstashFormatter
-import logging
-
-if __name__ == "__main__":
-    # log to the tcp_input of logstash in our logstash-loki container
-    handler = AsynchronousLogstashHandler(
-        "logstash", 5959, database_path="/tmp/pending_log_messages.db"
-    )
-
-    # add to logger of Jupyter traitlets Application. As that logger is configured not to propagate
-    # messages upward, we need to configure it directly.
-    logger = logging.getLogger("NotebookApp")
-    logger.addHandler(handler)
-    logger.setLevel(logging.DEBUG)
-
-    sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
-    sys.exit(main())
diff --git a/docker-compose/jupyter-lab/jupyter_notebook_config.py b/docker-compose/jupyter-lab/jupyter_notebook_config.py
new file mode 100644
index 000000000..b927d210b
--- /dev/null
+++ b/docker-compose/jupyter-lab/jupyter_notebook_config.py
@@ -0,0 +1,6 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
+NotebookApp.base_url = '/jupyter'
+NotebookApp.password_required = False
+NotebookApp.token = ''
diff --git a/docker-compose/jupyter-lab/requirements.txt b/docker-compose/jupyter-lab/requirements.txt
index c3e82e317..871af8314 100644
--- a/docker-compose/jupyter-lab/requirements.txt
+++ b/docker-compose/jupyter-lab/requirements.txt
@@ -1,14 +1,9 @@
 # Jupyter & enhancements
-ipython >=7.27.0,!=7.28.0 # BSD
-jupyter
-jupyterlab >=3,<4 # until https://github.com/jupyterlab/jupyterlab-git/issues/1245
-jupyterlab_h5web[full]  # MIT
-jupyterlab-git
-jupyterlab-skip-traceback
 ipykernel
 nbconvert
 notebook-as-pdf
 PyPDF2==2.12.1 # until https://github.com/betatim/notebook-as-pdf/issues/40 hits a notebook-as-pdf release
+jupyterlab-git
 
 # low-level access to station components
 opcua
@@ -23,15 +18,11 @@ etrs-itrs@git+https://github.com/brentjens/etrs-itrs # Apache 2
 
 # plotting
 matplotlib
-jupyter_bokeh
 jupyterplot
 
 # useful LOFAR software
 pabeam@git+https://git.astron.nl/mevius/pabeam # Apache2
-lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client # Apache2
-attributewrapper@git+https://git.astron.nl/lofar2.0/attributewrapper # Apache2
 
 # user packages
 numpy
-scipy
 astropy
diff --git a/docker-compose/logstash.yml b/docker-compose/logstash.yml
deleted file mode 100644
index d44e0c52b..000000000
--- a/docker-compose/logstash.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-#
-# Docker compose file that launches Logstash-output-loki
-#
-#
-
-version: '2.1'
-
-services:
-  logstash:
-    image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/logstash:${TAG}
-    build:
-      context: logstash
-      args:
-        SOURCE_IMAGE: grafana/logstash-output-loki:main
-    hostname: logstash
-    container_name: logstash
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "100m"
-        max-file: "10"
-    healthcheck:
-      test: python -c "import socket; s = socket.socket(); s.settimeout(10); s.connect(('$HOSTNAME', 1514));"
-      interval: 1m
-      timeout: 30s
-      retries: 3
-      start_period: 30s
-    networks:
-      - control
-    ports:
-      - "5044:5044"     # logstash beats input
-      - "1514:1514/tcp" # logstash syslog input
-      - "1514:1514/udp" # logstash syslog input
-      - "5959:5959"     # logstash tcp json input
-      - "9600:9600"
-    restart: unless-stopped
-
-  logstash-exporter:
-    image: sequra/logstash_exporter
-    hostname: logstash-exporter
-    container_name: logstash-exporter
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "100m"
-        max-file: "10"
-    networks:
-      - control
-    command: --logstash.endpoint="http://logstash:9600"
-    restart: unless-stopped
diff --git a/docker-compose/logstash/Dockerfile b/docker-compose/logstash/Dockerfile
deleted file mode 100644
index c455b2192..000000000
--- a/docker-compose/logstash/Dockerfile
+++ /dev/null
@@ -1,11 +0,0 @@
-ARG SOURCE_IMAGE
-FROM ${SOURCE_IMAGE}
-
-# Disable Elastic Search connection
-ENV ELASTIC_CONTAINER=false
-
-# Provide our logstash config
-COPY logstash.conf /home/logstash/
-COPY logstash.yml /usr/share/logstash/config/logstash.yml
-COPY logstash.conf /usr/share/logstash/pipeline/logstash.conf
-COPY patterns /usr/share/logstash/patterns
diff --git a/docker-compose/logstash/README.md b/docker-compose/logstash/README.md
deleted file mode 100644
index 156aaae85..000000000
--- a/docker-compose/logstash/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# Logstash
-
-Grafana Loki has a Logstash output plugin called logstash-output-loki that enables shipping logs to a Loki instance
-
-## Usage and configuration
-
-To configure Logstash to forward logs to Loki, simply add the loki output to your Logstash configuration file as documented below:
-
-    output {
-        loki {
-            [url => "" | default = none | required=true]
-
-            [tenant_id => string | default = nil | required=false]
-
-            [message_field => string | default = "message" | required=false]
-            
-            [include_fields => array | default = [] | required=false]
-
-            [batch_wait => number | default = 1(s) | required=false]
-
-            [batch_size => number | default = 102400(bytes) | required=false]
-
-            [min_delay => number | default = 1(s) | required=false]
-
-            [max_delay => number | default = 300(s) | required=false]
-
-            [retries => number | default = 10 | required=false]
-
-            [username => string | default = nil | required=false]
-
-            [password => secret | default = nil | required=false]
-
-            [cert => path | default = nil | required=false]
-
-            [key => path | default = nil| required=false]
-
-            [ca_cert => path | default = nil | required=false]
-
-            [insecure_skip_verify => boolean | default = false | required=false]
-        }
-    }
diff --git a/docker-compose/logstash/logstash.conf b/docker-compose/logstash/logstash.conf
deleted file mode 100644
index 71afa03e9..000000000
--- a/docker-compose/logstash/logstash.conf
+++ /dev/null
@@ -1,220 +0,0 @@
-# For syntax, see https://www.elastic.co/guide/en/logstash/current/filter-plugins.html
-
-input {
-  beats {
-    id => "input_beats"
-    port => 5044
-    # TODO (L2SS-748) add SSL encryption
-  }
-}
-
-input {
-  syslog {
-    id => "input_syslog"
-    port => 1514
-  }
-}
-
-input {
-  tcp {
-    id => "input_tcp_json"
-    port => 5959
-    codec => json
-  }
-}
-
-filter {
-  if [type] == "syslog" {
-    grok {
-      id => "grok_syslog"
-      match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
-      add_field => [ "received_at", "%{@timestamp}" ]
-      add_field => [ "received_from", "%{host}" ]
-    }
-    syslog_pri { id => "syslog_pri" }
-    date {
-      id => "date_syslog"
-      match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
-    }
-  }
-}
-
-filter {
-  if [program] == "grafana" {
-    kv { id => "kv_grafana" }
-    mutate {
-      id => "mutate_grafana"
-      rename => {
-        "t" => "timestamp"
-        "lvl" => "level"
-      }
-      uppercase => [ "level" ]
-    }
-
-    date {
-      id => "date_grafana"
-      match => [ "timestamp", "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'", "ISO8601" ]
-    }
-  }
-}
-
-filter {
-  if [program] == "prometheus" {
-    kv { id => "kv_prometheus" }
-    mutate {
-      id => "mutate_prometheus"
-      rename => {
-        "ts" => "timestamp"
-      }
-      uppercase => [ "level" ]
-    }
-    date {
-      id => "date_prometheus"
-      match => [ "timestamp", "ISO8601" ]
-    }
-  }
-}
-
-filter {
-  if [program] == "tango-rest" {
-    grok {
-      id => "grok_tango-rest"
-      match => {
-        "message" => "%{TIMESTAMP_ISO8601:timestamp} %{WORD:level} %{GREEDYDATA:message}"
-      }
-      overwrite => [ "timestamp", "level", "message" ]
-    }
-    date {
-      id => "date_tango-rest"
-      match => [ "timestamp", "YYYY-MM-dd HH:mm:ss,SSS" ]
-      timezone => "UTC"
-    }
-  }
-}
-
-filter {
-  # parse tangodb output
-  if [program] == "tangodb" {
-    grok {
-      id => "grok_tangodb"
-      patterns_dir => [ "/usr/share/logstash/patterns/" ]
-      match => {
-        "message" => [
-          "%{TIMESTAMP_ISO8601_MARIADB:timestamp} .%{WORD:level}. %{GREEDYDATA:message}",
-          "%{TIMESTAMP_ISO8601_MARIADB:timestamp} %{NONNEGINT} .%{WORD:level}. %{GREEDYDATA:message}"
-        ]
-      }
-      "overwrite" => [ "timestamp", "level", "message" ]
-    }
-    mutate {
-      id => "mutate_tangodb"
-      gsub => [
-        "level", "Note", "Info"
-      ]
-      uppercase => [ "level" ]
-    }
-    date {
-      id => "date_tangodb"
-      match => [ "timestamp", "YYYY-MM-dd HH:mm:ssZZ", "YYYY-MM-dd HH:mm:ss", "YYYY-MM-dd  H:mm:ss"  ]
-      timezone => "UTC"
-    }
-  }
-}
-
-filter {
-  if [type] == "python-logstash" {
-    # don't archive debug messages
-    if [level] == "DEBUG" {
-      drop { id => "drop_debug_message" }
-    }
-
-    # strip path from program
-    grok {
-      id => "grok_python-logstash-removepath-program"
-      match => {
-        "program" => "(?<program>[^/]+)$"
-      }
-
-      overwrite => [ "program" ]
-      tag_on_failure => [] # be quiet if there are no matches
-    }
-
-    # strip path from source reference
-    grok {
-      id => "grok_python-logstash-removepath-source"
-      match => {
-        "[extra][path]" => "(?<[extra][path]>[^/]+)$"
-      }
-
-      overwrite => [ "[extra][path]" ]
-      tag_on_failure => [] # be quiet if there are no matches
-    }
-
-    # add source reference to each log entry
-    mutate {
-      id => "mutate_python-logstash-sourceref"
-      replace => { "message" => "%{message} [%{[extra][func_name]}() at %{[extra][path]}:%{[extra][line]}]" }
-    }
-
-    # promote some fields out of extra, if present
-    if [extra][device] {
-      mutate {
-        id => "mutate_python-logstash-device"
-        add_field => {
-          "device" => "%{[extra][device]}"
-        }
-      }
-    }
-
-    if [extra][stack_trace] {
-      mutate {
-        id => "mutate_python-logstash-stacktrace"
-        replace => { "message" => "%{message} %{[extra][stack_trace]}" }
-      }
-    }
-  }
-}
-
-# Throttle spam to max 1000/min per program/device.
-# See https://www.elastic.co/guide/en/logstash/current/plugins-filters-throttle.html
-filter {
-  throttle {
-    id => "throttler"
-    before_count => -1
-    after_count => 1000
-    period => 60
-    max_age => 180
-    key => "%{host}-%{program}-%{device}"
-    add_tag => "throttled"
-  }
-
-  if "throttled" in [tags] {
-    drop { id => "drop_throttled" }
-  }
-}
-
-output {
-  loki {
-    id => "output_loki"
-    url => "http://loki:3100/loki/api/v1/push"
-    message_field => "message"
-
-    # Every unique combination of field values creates a separate stream in loki.
-    # This overloads loki, so only send fields that are key and bank on messages
-    # being regex-ed when querying instead.
-    #
-    # See https://grafana.com/blog/2020/08/27/the-concise-guide-to-labels-in-loki/
-    #
-    # So avoid putting in fields that vary, such as:
-    #  * (parts of) timestamps,
-    #  * source file references (file, line, etc).
-    # Instead, f.e. put those in the message field with a replace filter.
-    #
-    # To inspect loki's streams, look at the content of /loki/chunks in the loki container:
-    #    docker exec -it loki ls /loki/chunks
-    include_fields => [ "host", "program", "level", "device", "tags" ]
-  }
-
-  # enable this to see all logs on logstash's stdout. to view, run: docker logs logstash
-  # stdout {}
-}
diff --git a/docker-compose/logstash/logstash.yml b/docker-compose/logstash/logstash.yml
deleted file mode 100644
index a5c9f3319..000000000
--- a/docker-compose/logstash/logstash.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-http.host: "0.0.0.0"
-#xpack.monitoring.elasticsearch.hosts: [ "http://loki:3100" ]
-
-pipeline.ecs_compatibility: disabled
diff --git a/docker-compose/logstash/patterns/mariadb.txt b/docker-compose/logstash/patterns/mariadb.txt
deleted file mode 100644
index 42d981acb..000000000
--- a/docker-compose/logstash/patterns/mariadb.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# MariaDB uses ' 2' instead of '02' for hours. The following format accepts both.
-TIMESTAMP_ISO8601_MARIADB %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ] ?%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?
diff --git a/docker-compose/object-storage.yml b/docker-compose/object-storage.yml
index 03e3e89f0..434faf658 100644
--- a/docker-compose/object-storage.yml
+++ b/docker-compose/object-storage.yml
@@ -9,47 +9,19 @@
 version: '2.1'
 
 services:
-  object-storage:
-    image: minio/minio:${MINIO_VERSION}
-    hostname: object-storage
-    container_name: object-storage
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "100m"
-        max-file: "10"
-    networks:
-      - control
-    volumes:
-      - object-storage:/data:rw
-    environment:
-      - MINIO_ROOT_USER=${MINIO_ROOT_USER}
-      - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
-    ports:
-      - "9000:9000"
-      - "9001:9001"
-    command: server --console-address ":9001" /data
-    restart: unless-stopped
-
+  # the object storage is started by nomad/jumppad. This only populates the data for the integration tests
   init-object-storage:
     image: minio/mc:${MINIO_CLIENT_VERSION}
     networks:
       - control
-    depends_on:
-      - object-storage
+    dns: ${DNS}
     volumes:
       - ..:/opt/lofar/tango:rw
     entrypoint: ''
     command: >
-      sh -c "until [ \"$(curl -s -w '%{http_code}' -o /dev/null http://object-storage:9000/minio/health/live)\" -eq \"200\" ]
-             do
-               sleep 1
-             done
-             mc alias set object-storage http://object-storage:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
+      sh -c "mc alias set object-storage http://s3.service.consul:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
              mc mb --with-versioning object-storage/caltables
              mc cp --recursive /opt/lofar/tango/docker-compose/object-storage/caltables/ object-storage/caltables/
              date +'%F %T'
              echo 'Initialisation completed'"
 
-volumes:
-  object-storage:
diff --git a/docker-compose/prometheus/prometheus.yml b/docker-compose/prometheus/prometheus.yml
index 673021bc8..09a306763 100644
--- a/docker-compose/prometheus/prometheus.yml
+++ b/docker-compose/prometheus/prometheus.yml
@@ -6,46 +6,41 @@ global:
 scrape_configs:
   - job_name: tango
     static_configs:
-      - targets: ["tango-prometheus-exporter:8000"]
+      - targets: [ "tango-prometheus-exporter:8000" ]
         labels:
           "host": "localhost"
   - job_name: tango-fast
     scrape_interval: 1s
     static_configs:
-      - targets: ["tango-prometheus-fast-exporter:8000"]
+      - targets: [ "tango-prometheus-fast-exporter:8000" ]
         labels:
           "host": "localhost"
   - job_name: tango-slow
     scrape_interval: 60s
     scrape_timeout: 30s
     static_configs:
-      - targets: ["tango-prometheus-slow-exporter:8000"]
+      - targets: [ "tango-prometheus-slow-exporter:8000" ]
         labels:
           "host": "localhost"
   - job_name: host
     scrape_interval: 60s
     static_configs:
-      - targets: ["host.docker.internal:9100"]
+      - targets: [ "host.docker.internal:9100" ]
         labels:
           "host": "localhost"
   # scrape local services
   - job_name: prometheus
     static_configs:
-      - targets: ["localhost:9090"]
+      - targets: [ "localhost:9090" ]
         labels:
           "host": "localhost"
   - job_name: grafana
     static_configs:
-      - targets: ["grafana:3000"]
-        labels:
-          "host": "localhost"
-  - job_name: logstash
-    static_configs:
-      - targets: ["logstash-exporter:9198"]
+      - targets: [ "grafana:3000" ]
         labels:
           "host": "localhost"
   - job_name: loki
     static_configs:
-      - targets: ["loki:3100"]
+      - targets: [ "loki:3100" ]
         labels:
           "host": "localhost"
diff --git a/docker-compose/tango-prometheus-exporter.yml b/docker-compose/tango-prometheus-exporter.yml
index 14b66f988..69e0df046 100644
--- a/docker-compose/tango-prometheus-exporter.yml
+++ b/docker-compose/tango-prometheus-exporter.yml
@@ -28,8 +28,6 @@ services:
       - TANGO_HOST=${TANGO_HOST}
     ports:
       - "8000:8000"
-    depends_on:
-      - databaseds
     restart: unless-stopped
 
   tango-prometheus-fast-exporter:
@@ -54,8 +52,6 @@ services:
       - TANGO_HOST=${TANGO_HOST}
     ports:
       - "8001:8000"
-    depends_on:
-      - databaseds
     restart: unless-stopped
 
   tango-prometheus-slow-exporter:
@@ -80,6 +76,4 @@ services:
       - TANGO_HOST=${TANGO_HOST}
     ports:
       - "8002:8000"
-    depends_on:
-      - databaseds
     restart: unless-stopped
diff --git a/docker-compose/tango-prometheus-exporter/Dockerfile b/docker-compose/tango-prometheus-exporter/Dockerfile
index 372798189..6cf1017c0 100644
--- a/docker-compose/tango-prometheus-exporter/Dockerfile
+++ b/docker-compose/tango-prometheus-exporter/Dockerfile
@@ -10,7 +10,7 @@ USER tango
 COPY code/pip-requirements.txt /tmp/
 RUN pip install -r /tmp/pip-requirements.txt
 
-ADD code /code
+COPY code /code
 COPY lofar2-policy.json /code/
 COPY lofar2-fast-policy.json /code/
 COPY lofar2-slow-policy.json /code/
diff --git a/docker-compose/tango-prometheus-exporter/code/pip-requirements.txt b/docker-compose/tango-prometheus-exporter/code/pip-requirements.txt
index 771b44219..a0753df38 100644
--- a/docker-compose/tango-prometheus-exporter/code/pip-requirements.txt
+++ b/docker-compose/tango-prometheus-exporter/code/pip-requirements.txt
@@ -1,2 +1 @@
 prometheus_client
-python-logstash-async
diff --git a/docker-compose/tango-prometheus-exporter/code/tango-prometheus-client.py b/docker-compose/tango-prometheus-exporter/code/tango-prometheus-client.py
index 751cafde6..fdb80e699 100644
--- a/docker-compose/tango-prometheus-exporter/code/tango-prometheus-client.py
+++ b/docker-compose/tango-prometheus-exporter/code/tango-prometheus-client.py
@@ -1,7 +1,15 @@
-import time
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
 import argparse
-from prometheus_client.core import GaugeMetricFamily, REGISTRY, CounterMetricFamily
+import fnmatch
+import json
+import logging
+import time
+from functools import lru_cache
+
 from prometheus_client import start_http_server
+from prometheus_client.core import GaugeMetricFamily, REGISTRY
 from tango import (
     Database,
     DeviceProxy,
@@ -10,23 +18,10 @@ from tango import (
     DevState,
     DevFailed,
 )
-import logging
-import json
-import fnmatch
-from functools import lru_cache
-from logstash_async.handler import AsynchronousLogstashHandler, LogstashFormatter
 
 logger = logging.getLogger()
 logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s", level=logging.DEBUG)
 
-# log to memory only, with a limit of 600 seconds. this container is not important enough to keep
-# all logs around for, and as a scraper must be robust against overloading on all sides
-handler = AsynchronousLogstashHandler(
-    "logstash", 5959, database_path=None, event_ttl=600
-)
-handler.setLevel(logging.INFO)
-logger.addHandler(handler)
-
 # time to wait before reconnecting after encountering connection issues
 reconnect_timeout_time = 1.0
 
@@ -193,12 +188,12 @@ class CustomCollector(object):
                 the array as [x][y] instead of [y][x]."""
 
                 new_metric = self._to_metric(
-                    dev,
-                    attr_info,
-                    y,
-                    x,
-                    y * attr_value.dim_x + x,
-                    attr_value.value[y][x],
+                        dev,
+                        attr_info,
+                        y,
+                        x,
+                        y * attr_value.dim_x + x,
+                        attr_value.value[y][x],
                 )
                 metrics.append(new_metric) if new_metric else None
 
@@ -247,7 +242,7 @@ class CustomCollector(object):
             DevState.DISABLE,
         ]:
             logger.warning(
-                f"Error processing device {device_name}: it is in state {dev.state()}"
+                    f"Error processing device {device_name}: it is in state {dev.state()}"
             )
 
             # at least log state & status
@@ -268,7 +263,7 @@ class CustomCollector(object):
             except DevFailed as e:
                 reason = e.args[0].desc.replace("\n", " ")
                 logger.warning(
-                    f"Error processing device {device_name} attribute {attr_name}: {reason}"
+                        f"Error processing device {device_name} attribute {attr_name}: {reason}"
                 )
 
                 if "The last connection request was done less than" in e.args[0].desc:
@@ -276,7 +271,7 @@ class CustomCollector(object):
                     break
             except Exception as e:
                 logger.exception(
-                    f"Error processing device {device_name} attribute {attr_name}"
+                        f"Error processing device {device_name} attribute {attr_name}"
                 )
 
         return metrics
@@ -288,12 +283,14 @@ class CustomCollector(object):
         scrape_begin = time.time()
 
         attribute_metrics = GaugeMetricFamily(
-            "device_attribute",
-            "Device attribute value",
-            labels=["station", "device", "name", "str_value", "type", "x", "y", "idx"],
+                "device_attribute",
+                "Device attribute value",
+                labels=["station", "device", "name", "str_value", "type", "x", "y",
+                        "idx"],
         )
         scraping_metrics = GaugeMetricFamily(
-            "device_scraping", "Device scraping duration", labels=["station", "device"]
+                "device_scraping", "Device scraping duration",
+                labels=["station", "device"]
         )
 
         for device_name in self.policy.devices():
@@ -314,11 +311,11 @@ class CustomCollector(object):
                 dev_scrape_end = time.time()
 
             logger.debug(
-                f"Done processing device {device_name}. Took {dev_scrape_end - dev_scrape_begin} seconds."
+                    f"Done processing device {device_name}. Took {dev_scrape_end - dev_scrape_begin} seconds."
             )
 
             scraping_metrics.add_metric(
-                [self.station, device_name], dev_scrape_end - dev_scrape_begin
+                    [self.station, device_name], dev_scrape_end - dev_scrape_begin
             )
 
         scrape_end = time.time()
@@ -335,23 +332,23 @@ if __name__ == "__main__":
 
     parser = argparse.ArgumentParser()
     parser.add_argument(
-        "-c", "--config", type=str, required=True, help="configuration file"
+            "-c", "--config", type=str, required=True, help="configuration file"
     )
     parser.add_argument(
-        "-t",
-        "--timeout",
-        type=int,
-        required=False,
-        default=250,
-        help="device proxy timeout (ms)",
+            "-t",
+            "--timeout",
+            type=int,
+            required=False,
+            default=250,
+            help="device proxy timeout (ms)",
     )
     parser.add_argument(
-        "-p",
-        "--port",
-        type=int,
-        required=False,
-        default=8000,
-        help="HTTP server port to open",
+            "-p",
+            "--port",
+            type=int,
+            required=False,
+            default=8000,
+            help="HTTP server port to open",
     )
     args = parser.parse_args()
 
diff --git a/docker-compose/tango.yml b/docker-compose/tango.yml
index 9de7d9e1d..c4a2827d9 100644
--- a/docker-compose/tango.yml
+++ b/docker-compose/tango.yml
@@ -75,6 +75,7 @@ services:
     restart: unless-stopped
 
   dsconfig:
+    image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/dsconfig:${TAG}
     build:
       context: dsconfig
       args:
@@ -83,8 +84,6 @@ services:
     container_name: dsconfig
     networks:
       - control
-    depends_on:
-      - databaseds
     environment:
       - TANGO_HOST=${TANGO_HOST}
     command: >
diff --git a/infra/dev/jobs/.keep b/infra/dev/jobs/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/infra/dev/jobs/station/.keep b/infra/dev/jobs/station/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/infra/dev/main.hcl b/infra/dev/main.hcl
new file mode 100644
index 000000000..8122fe072
--- /dev/null
+++ b/infra/dev/main.hcl
@@ -0,0 +1,32 @@
+variable "host_volume" {
+  default = "dev_nomad_station"
+}
+
+module "nomad" {
+  source    = "./nomad"
+  variables = {
+    host_volume = "${variable.host_volume}"
+  }
+}
+
+resource "nomad_job" "tango" {
+  cluster = module.nomad.output.nomad_cluster
+
+  paths = ["./jobs/station/tango.nomad"]
+
+  health_check {
+    timeout = "300s"
+    jobs    = ["tango"]
+  }
+}
+
+resource "nomad_job" "object-storage" {
+  cluster = module.nomad.output.nomad_cluster
+
+  paths = ["./jobs/station/object-storage.nomad"]
+
+  health_check {
+    timeout = "300s"
+    jobs    = ["object-storage"]
+  }
+}
diff --git a/infra/dev/nomad/config/nomad/client.hcl b/infra/dev/nomad/config/nomad/client.hcl
new file mode 100644
index 000000000..5312a79f7
--- /dev/null
+++ b/infra/dev/nomad/config/nomad/client.hcl
@@ -0,0 +1,55 @@
+plugin "docker" {
+  config {
+    allow_privileged = true
+    volumes {
+      enabled      = true
+      selinuxlabel = "z"
+    }
+  }
+}
+
+client {
+  host_volume "monitoring-postgresql-data" {
+    path = "/localdata/volumes/monitoring-postgresql-data"
+  }
+
+  host_volume "monitoring-loki-data" {
+    path = "/localdata/volumes/monitoring-loki-data"
+  }
+
+  host_volume "monitoring-prometheus-data" {
+    path = "/localdata/volumes/monitoring-prometheus-data"
+  }
+
+  host_volume "tango-database" {
+    path = "/localdata/volumes/tango-database"
+  }
+
+  host_volume "jupyter-notebooks" {
+    path = "/localdata/volumes/jupyter-notebooks"
+  }
+
+  host_volume "object-storage-data" {
+    path = "/localdata/volumes/object-storage-data"
+  }
+
+  options = {
+    "driver.allowlist" = "docker,exec"
+  }
+}
+consul {
+  address              = "localhost:8500"
+  server_service_name  = "nomad"
+  client_service_name  = "nomad-client"
+  auto_advertise       = true
+  server_auto_join     = true
+  client_auto_join     = true
+  checks_use_advertise = true
+}
+
+advertise {
+
+  http = "{{ GetInterfaceIP \"eth0\" }}"
+  rpc  = "{{ GetInterfaceIP \"eth0\" }}"
+  serf = "{{ GetInterfaceIP \"eth0\" }}"
+}
diff --git a/infra/dev/nomad/config/nomad/consul.hcl b/infra/dev/nomad/config/nomad/consul.hcl
new file mode 100644
index 000000000..512ae3b71
--- /dev/null
+++ b/infra/dev/nomad/config/nomad/consul.hcl
@@ -0,0 +1,21 @@
+data_dir  = "/tmp/"
+log_level = "trace"
+
+server     = false
+datacenter = "dev-stat"
+
+bind_addr      = "0.0.0.0"
+advertise_addr = "{{GetInterfaceIP \"eth0\"}}"
+
+ports {
+  grpc     = 8502
+  grpc_tls = 8503
+}
+
+connect {
+  enabled = true
+}
+
+retry_join              = ["192.168.123.100"]
+skip_leave_on_interrupt = true
+leave_on_terminate      = false
diff --git a/infra/dev/nomad/nomad.hcl b/infra/dev/nomad/nomad.hcl
new file mode 100644
index 000000000..897d6d08d
--- /dev/null
+++ b/infra/dev/nomad/nomad.hcl
@@ -0,0 +1,105 @@
+resource "network" "station" {
+  subnet = "192.168.123.0/24"
+}
+
+resource "template" "consul_config" {
+
+  source = <<-EOF
+  data_dir  = "/consul/data/"
+  log_level = "trace"
+
+
+  datacenter = "dev-stat"
+
+  server = true
+
+  bootstrap_expect = 1
+  ui = true
+
+  bind_addr = "0.0.0.0"
+  client_addr = "127.0.0.1 {{GetInterfaceIP \"eth0\"}}"
+  advertise_addr = "{{GetInterfaceIP \"eth0\"}}"
+
+  ports {
+    grpc = 8502
+    grpc_tls = 8503
+    dns = 53
+  }
+  connect {
+    enabled = true
+  }
+
+  auto_encrypt {
+    allow_tls = true
+  }
+  skip_leave_on_interrupt = true
+  leave_on_terminate = false
+  recursors  = ["127.0.0.11"]
+  EOF
+
+  destination = "./tmp/consul.hcl"
+}
+
+
+resource "container" "consul" {
+  depends_on = ["resource.template.consul_config"]
+  network {
+    id         = resource.network.station.id
+    ip_address = "192.168.123.100"
+  }
+
+  image {
+    name = "hashicorp/consul:latest"
+  }
+
+  command = [
+    "consul",
+    "agent",
+    "-config-file=/consul/config/config.hcl"
+  ]
+
+  environment = {
+    CONSUL_HTTP_ADDR = "http://localhost:8500"
+  }
+
+  volume {
+    source      = resource.template.consul_config.destination
+    destination = "/consul/config/config.hcl"
+    type        = "bind"
+  }
+
+  port {
+    local  = 8500
+    remote = 8500
+    host   = 18500
+  }
+
+  port {
+    local    = 53
+    host     = 8600
+    remote   = 53
+    protocol = "udp"
+  }
+  privileged = false
+}
+
+resource "nomad_cluster" "station" {
+  depends_on    = ["resource.container.consul"]
+  client_nodes  = 1
+  client_config = "./config/nomad/client.hcl"
+  consul_config = "./config/nomad/consul.hcl"
+  datacenter    = "stat"
+
+  network {
+    id = resource.network.station.id
+  }
+  environment = {
+    NO_PROXY = "git.astron.nl:5000"
+  }
+
+  volume {
+    source      = "${variable.host_volume}"
+    destination = "/localdata"
+    type        = "volume"
+  }
+}
diff --git a/infra/dev/nomad/tmp/.keep b/infra/dev/nomad/tmp/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/infra/dev/nomad/variables.hcl b/infra/dev/nomad/variables.hcl
new file mode 100644
index 000000000..6deffdedc
--- /dev/null
+++ b/infra/dev/nomad/variables.hcl
@@ -0,0 +1,7 @@
+variable "host_volume" {
+  default = "dev_nomad_station"
+}
+
+output "nomad_cluster" {
+  value = resource.nomad_cluster.station
+}
diff --git a/infra/dev/services.hcl b/infra/dev/services.hcl
new file mode 100644
index 000000000..fc7d8e0b9
--- /dev/null
+++ b/infra/dev/services.hcl
@@ -0,0 +1,21 @@
+variable "host_volume" {
+  default = "dev_nomad_station"
+}
+
+module "nomad" {
+  source    = "./nomad"
+  variables = {
+    host_volume = "${variable.host_volume}"
+  }
+}
+
+resource "nomad_job" "monitoring" {
+  cluster = module.nomad.output.nomad_cluster
+
+  paths = ["./jobs/station/monitoring.nomad"]
+
+  health_check {
+    timeout = "300s"
+    jobs    = ["monitoring"]
+  }
+}
diff --git a/infra/env.yaml b/infra/env.yaml
new file mode 100644
index 000000000..5d7d6fcac
--- /dev/null
+++ b/infra/env.yaml
@@ -0,0 +1,72 @@
+tango:
+  db:
+    version: 11.0.2
+  databaseds:
+    version: 5.22.0
+  rest:
+    version: 1.14.8
+
+monitoring:
+  db:
+    version: 15.4
+
+object_storage:
+  minio:
+    version: RELEASE.2023-09-23T03-47-50Z
+  user:
+    name: minioadmin
+    pass: minioadmin
+
+sdptr:
+  lba:
+    port: 4840
+    first_gn: 0
+    fpgas: 16
+  hba0:
+    port: 4842
+    first_gn: 16
+    fpgas: 4
+  hba1:
+    port: 4844
+    first_gn: 20
+    fpgas: 4
+
+devices:
+  - class: AFH
+  - class: AFL
+  - class: APS
+  - class: APSCT
+  - class: APSPU
+  - class: Beamlet
+  - class: BST
+    ports:
+      lba: 5003
+      hba0: 5013
+      hba1: 5023
+  - class: Calibration
+  - class: CCD
+  - class: Configuration
+  - class: DigitalBeam
+  - class: Docker
+  - class: EC
+  - class: ObservationControl
+  - class: PCON
+  - class: PSOC
+  - class: RECVH
+  - class: RECVL
+  - class: SDP
+  - class: SDPFirmware
+  - class: SST
+    ports:
+      lba: 5001
+      hba0: 5011
+      hba1: 5021
+  - class: StationManager
+  - class: TemperatureManager
+  - class: TileBeam
+  - class: UNB2
+  - class: XST
+    ports:
+      lba: 5002
+      hba0: 5012
+      hba1: 5022
diff --git a/infra/imds/meta-data b/infra/imds/meta-data
deleted file mode 100644
index 88f01c274..000000000
--- a/infra/imds/meta-data
+++ /dev/null
@@ -1,3 +0,0 @@
-instance-id: omeid/somehostname
-local-hostname: ds001c
-region: ds001
diff --git a/infra/imds/user-data b/infra/imds/user-data
deleted file mode 100644
index 38aebe14e..000000000
--- a/infra/imds/user-data
+++ /dev/null
@@ -1,205 +0,0 @@
-## template: jinja
-#cloud-config
-system_info:
-  default_user:
-    name: ghost
-    home: /home/ghost
-
-password: password
-chpasswd:
-  expire: False
-
-write_files:
-- content: |
-    datacenter = "{{ds.meta_data.region}}"
-    data_dir  = "/localdata/consul"
-    node_name = "lcu"
-    server = true
-    bootstrap = true
-    bind_addr = "0.0.0.0"
-
-    # ui
-    ui_config{
-      enabled = true
-    }
-
-
-    ports {
-      grpc = 8502
-    }
-
-    connect {
-      enabled = true
-    }
-  owner: consul:consul
-  path: /etc/consul.d/consul.hcl
-  defer: true
-- content: |
-    bind_addr = "0.0.0.0"
-    name      = "lcu"
-    region    = "{{ds.meta_data.region}}"
-    data_dir  = "/localdata/nomad/data"
-    datacenter = "stat"
-
-    server {
-      # license_path is required for Nomad Enterprise as of Nomad v1.1.1+
-      #license_path = "/etc/nomad.d/license.hclic"
-      enabled          = true
-      bootstrap_expect = 1
-    }
-
-    client {
-      enabled = true
-      servers = ["127.0.0.1"]
-      cni_path = "/localdata/cni/bin"
-
-      host_volume "monitoring-postgresql-data" {
-        path = "/localdata/volumes/monitoring-postgresql-data"
-      }
-
-      host_volume "monitoring-loki-data" {
-        path = "/localdata/volumes/monitoring-loki-data"
-      }
-
-      host_volume "monitoring-prometheus-data" {
-        path = "/localdata/volumes/monitoring-prometheus-data"
-      }
-    }
-
-    consul {
-      address             = "127.0.0.1:8500"
-      server_service_name = "nomad"
-      client_service_name = "nomad-client"
-      auto_advertise      = true
-      server_auto_join    = true
-      client_auto_join    = true
-    }
-
-    plugin "docker" {
-      config {
-        volumes {
-          enabled = true
-        }
-        allow_caps = ["all"]
-      }
-    }
-  path: /etc/nomad.d/nomad.hcl
-  defer: true
-
-- content: |
-    net.bridge.bridge-nf-call-arptables = 1
-    net.bridge.bridge-nf-call-ip6tables = 1
-    net.bridge.bridge-nf-call-iptables = 1
-  path: /etc/sysctl.d/10-bridge.conf
-- content: |
-    deb [signed-by=/usr/share/keyrings/docker.asc] https://apt.releases.hashicorp.com $RELEASE main
-  path: /etc/apt/sources.list.d/hashicorp.list
-- path: /usr/share/keyrings/hashicorp.asc
-  owner: root:root
-  permissions: '0644'
-  content: |
-    -----BEGIN PGP PUBLIC KEY BLOCK-----
-
-    mQINBGO9u+MBEADmE9i8rpt8xhRqxbzlBG06z3qe+e1DI+SyjscyVVRcGDrEfo+J
-    W5UWw0+afey7HFkaKqKqOHVVGSjmh6HO3MskxcpRm/pxRzfni/OcBBuJU2DcGXnG
-    nuRZ+ltqBncOuONi6Wf00McTWviLKHRrP6oWwWww7sYF/RbZp5xGmMJ2vnsNhtp3
-    8LIMOmY2xv9LeKMh++WcxQDpIeRohmSJyknbjJ0MNlhnezTIPajrs1laLh/IVKVz
-    7/Z73UWX+rWI/5g+6yBSEtj368N7iyq+hUvQ/bL00eyg1Gs8nE1xiCmRHdNjMBLX
-    lHi0V9fYgg3KVGo6Hi/Is2gUtmip4ZPnThVmB5fD5LzS7Y5joYVjHpwUtMD0V3s1
-    HiHAUbTH+OY2JqxZDO9iW8Gl0rCLkfaFDBS2EVLPjo/kq9Sn7vfp2WHffWs1fzeB
-    HI6iUl2AjCCotK61nyMR33rNuNcbPbp+17NkDEy80YPDRbABdgb+hQe0o8htEB2t
-    CDA3Ev9t2g9IC3VD/jgncCRnPtKP3vhEhlhMo3fUCnJI7XETgbuGntLRHhmGJpTj
-    ydudopoMWZAU/H9KxJvwlVXiNoBYFvdoxhV7/N+OBQDLMevB8XtPXNQ8ZOEHl22G
-    hbL8I1c2SqjEPCa27OIccXwNY+s0A41BseBr44dmu9GoQVhI7TsetpR+qwARAQAB
-    tFFIYXNoaUNvcnAgU2VjdXJpdHkgKEhhc2hpQ29ycCBQYWNrYWdlIFNpZ25pbmcp
-    IDxzZWN1cml0eStwYWNrYWdpbmdAaGFzaGljb3JwLmNvbT6JAlQEEwEIAD4CGwMF
-    CwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQR5iuxlTlwVQoyOQu6qFvy8piHnAQUC
-    Y728PQUJCWYB2gAKCRCqFvy8piHnAd16EADeBtTgkdVEvct40TH/9HKkR/Lc/ohM
-    rer6FFHdKmceJ6Ma8/Qm4nCO5C7c4+EPjsUXdhK5w8DSdC5VbKLJDY1EnDlmU5B1
-    wSFkGoYKoB8lUn30E77E33MTu2kfrSuF605vetq269CyBwIJV7oNN6311dW8iQ6z
-    IytTtlJbVr4YZ7Vst40/uR4myumk9bVBGEd6JhFAPmr/um+BZFhRf9/8xtOryOyB
-    GF2d+bc9IoAugpxwv0IowHEqkI4RpK2U9hvxG80sTOcmerOuFbmNyPwnEgtJ6CM1
-    bc8WAmObJiQcRSLbcgF+a7+2wqrUbCqRE7QoS2wjd1HpUVPmSdJN925c2uaua2A4
-    QCbTEg8kV2HiP0HGXypVNhZJt5ouo0YgR6BSbMlsMHniDQaSIP1LgmEz5xD4UAxO
-    Y/GRR3LWojGzVzBb0T98jpDgPtOu/NpKx3jhSpE2U9h/VRDiL/Pf7gvEIxPUTKuV
-    5D8VqAiXovlk4wSH13Q05d9dIAjuinSlxb4DVr8IL0lmx9DyHehticmJVooHDyJl
-    HoA2q2tFnlBBAFbN92662q8Pqi9HbljVRTD1vUjof6ohaoM+5K1C043dmcwZZMTc
-    7gV1rbCuxh69rILpjwM1stqgI1ONUIkurKVGZHM6N2AatNKqtBRdGEroQo1aL4+4
-    u+DKFrMxOqa5b7kCDQRjvbwTARAA0ut7iKLj9sOcp5kRG/5V+T0Ak2k2GSus7w8e
-    kFh468SVCNUgLJpLzc5hBiXACQX6PEnyhLZa8RAG+ehBfPt03GbxW6cK9nx7HRFQ
-    GA79H5B4AP3XdEdT1gIL2eaHdQot0mpF2b07GNfADgj99MhpxMCtTdVbBqHY8YEQ
-    Uq7+E9UCNNs45w5ddq07EDk+o6C3xdJ42fvS2x44uNH6Z6sdApPXLrybeun74C1Z
-    Oo4Ypre4+xkcw2q2WIhy0Qzeuw+9tn4CYjrhw/+fvvPGUAhtYlFGF6bSebmyua8Q
-    MTKhwqHqwJxpjftM3ARdgFkhlH1H+PcmpnVutgTNKGcy+9b/lu/Rjq/47JZ+5VkK
-    ZtYT/zO1oW5zRklHvB6R/OcSlXGdC0mfReIBcNvuNlLhNcBA9frNdOk3hpJgYDzg
-    f8Ykkc+4z8SZ9gA3g0JmDHY1X3SnSadSPyMas3zH5W+16rq9E+MZztR0RWwmpDtg
-    Ff1XGMmvc+FVEB8dRLKFWSt/E1eIhsK2CRnaR8uotKW/A/gosao0E3mnIygcyLB4
-    fnOM3mnTF3CcRumxJvnTEmSDcoKSOpv0xbFgQkRAnVSn/gHkcbVw/ZnvZbXvvseh
-    7dstp2ljCs0queKU+Zo22TCzZqXX/AINs/j9Ll67NyIJev445l3+0TWB0kego5Fi
-    UVuSWkMAEQEAAYkEcgQYAQgAJhYhBHmK7GVOXBVCjI5C7qoW/LymIecBBQJjvbwT
-    AhsCBQkJZgGAAkAJEKoW/LymIecBwXQgBBkBCAAdFiEE6wr14plJaVlvmYc+cG5m
-    g2nAhekFAmO9vBMACgkQcG5mg2nAhenPURAAimI0EBZbqpyHpwpbeYq3Pygg1bdo
-    IlBQUVoutaN1lR7kqGXwYH+BP6G40x79LwVy/fWV8gO7cDX6D1yeKLNbhnJHPBus
-    FJDmzDPbjTlyWlDqJoWMiPqfAOc1A1cHodsUJDUlA01j1rPTho0S9iALX5R50Wa9
-    sIenpfe7RVunDwW5gw6y8me7ncl5trD0LM2HURw6nYnLrxePiTAF1MF90jrAhJDV
-    +krYqd6IFq5RHKveRtCuTvpL7DlgVCtntmbXLbVC/Fbv6w1xY3A7rXko/03nswAi
-    AXHKMP14UutVEcLYDBXbDrvgpb2p2ZUJnujs6cNyx9cOPeuxnke8+ACWvpnWxwjL
-    M5u8OckiqzRRobNxQZ1vLxzdovYTwTlUAG7QjIXVvOk9VNp/ERhh0eviZK+1/ezk
-    Z8nnPjx+elThQ+r16EM7hD0RDXtOR1VZ0R3OL64AlZYDZz1jEA3lrGhvbjSIfBQk
-    T6mxKUsCy3YbElcOyuohmPRgT1iVDIZ/1iPL0Q0HGm4+EsWCdH6fAPB7TlHD8z2D
-    7JCFLihFDWs5lrZyuWMO9nryZiVjJrOLPcStgJYVd/MhRHR4hC6g09bgo25RMJ6f
-    gyzL4vlEB7aSUih7yjgL9s5DKXP2J71dAhIlF8nnM403R2xEeHyivnyeR/9Ifn7M
-    PJvUMUuoG+ZANSMkrw//XA31o//TVk9WsLD1Edxt5XZCoR+fS+Vz8ScLwP1d/vQE
-    OW/EWzeMRG15C0td1lfHvwPKvf2MN+WLenp9TGZ7A1kEHIpjKvY51AIkX2kW5QLu
-    Y3LBb+HGiZ6j7AaU4uYR3kS1+L79v4kyvhhBOgx/8V+b3+2pQIsVOp79ySGvVwpL
-    FJ2QUgO15hnlQJrFLRYa0PISKrSWf35KXAy04mjqCYqIGkLsz2qQCY2lGcD5k05z
-    bBC4TvxwVxv0ftl2C5Bd0ydl/2YM7GfLrmZmTijK067t4OO+2SROT2oYPDsMtZ6S
-    E8vUXvoGpQ8tf5Nkrn2t0zDG3UDtgZY5UVYnZI+xT7WHsCz//8fY3QMvPXAuc33T
-    vVdiSfP0aBnZXj6oGs/4Vl1Dmm62XLr13+SMoepMWg2Vt7C8jqKOmhFmSOWyOmRH
-    UZJR7nKvTpFnL8atSyFDa4o1bk2U3alOscWS8u8xJ/iMcoONEBhItft6olpMVdzP
-    CTrnCAqMjTSPlQU/9EGtp21KQBed2KdAsJBYuPgwaQeyNIvQEOXmINavl58VD72Y
-    2T4TFEY8dUiExAYpSodbwBL2fr8DJxOX68WH6e3fF7HwX8LRBjZq0XUwh0KxgHN+
-    b9gGXBvgWnJr4NSQGGPiSQVNNHt2ZcBAClYhm+9eC5/VwB+Etg4+1wDmggztiqE=
-    =FdUF
-    -----END PGP PUBLIC KEY BLOCK-----
-
-
-yum_repos:
-  hashicorp:
-    name=Hashicorp Stable - $basearch
-    baseurl=https://rpm.releases.hashicorp.com/RHEL/$releasever/$basearch/stable
-    enabled=1
-    gpgcheck=1
-    gpgkey=https://rpm.releases.hashicorp.com/gpg
-
-apt:
-  preserve_sources_list: true
-  sources:
-    hashicorp:
-      source: deb [trusted=yes] https://apt.releases.hashicorp.com $RELEASE main
-
-package_update: true
-package_upgrade: true
-packages:
-  - docker
-  - nomad
-  - consul
-  - curl
-
-runcmd:
-  - mkdir -p /localdata/volumes/monitoring-postgresql-data
-  - mkdir -p /localdata/volumes/monitoring-prometheus-data
-  - mkdir -p /localdata/volumes/monitoring-loki-data
-  - chmod 0777 /localdata/volumes/monitoring-*
-  - [apt-get, update]
-  - [apt-get, -y, upgrade]
-  - [mkdir, -p, /localdata/consul]
-  - [chown, consul:consul, /localdata/consul]
-  - [mkdir, -p, /localdata/nomad/data]
-  - curl -L -o cni-plugins.tgz "https://github.com/containernetworking/plugins/releases/download/v1.0.0/cni-plugins-linux-$( [ $(uname -m) = aarch64 ] && echo arm64 || echo amd64)"-v1.0.0.tgz
-  - mkdir -p /localdata/cni/bin
-  - tar -C /localdata/cni/bin -xzf cni-plugins.tgz
-  - [systemctl, enable, consul.service]
-  - [systemctl, enable, nomad.service]
-  - [systemctl, start, consul.service]
-  - [systemctl, start, nomad.service]
diff --git a/infra/install-station.sh b/infra/install-station.sh
deleted file mode 100644
index 49ef0b658..000000000
--- a/infra/install-station.sh
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-#
-
-
-REGION="nl-cs-001"
-
-sudo yum install -y yum-utils
-sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
-
-### CONSUL ###
-sudo yum -y install consul
-
-cat << EOF > /etc/consul.d/consul.hcl
-datacenter = "$REGION"
-data_dir = "/localdata/consul"
-node_name = "lcu"
-server = true
-bootstrap = true
-bind_addr = "0.0.0.0"
-client_addr = "127.0.0.1"
-advertise_addr = "$IP"
-
-# ui
-ui_config{
-  enabled = true
-}
-
-tls {
-  defaults {
-    ca_path = "/etc/consul.d"
-    ca_file = "/etc/consul.d/consul-agent-ca.pem"
-    cert_file = "/etc/consul.d/$REGION-server-consul-0.pem"
-    key_file = "/etc/consul.d/$REGION-server-consul-0-key.pem"
-
-    verify_incoming = true
-    verify_outgoing = true
-  }
-  internal_rpc {
-    verify_server_hostname = true
-  }
-}
-
-ports {
-  grpc = 8502
-  grpc_tls = 8503
-}
-
-connect {
-  enabled = true
-}
-
-auto_encrypt {
-  allow_tls = true
-}
-
-performance {
-  raft_multiplier = 1
-}
-
-encrypt = "..."
-
-EOF
-sudo mkdir -p /localdata/consul
-sudo chown consul:consul /localdata/consul
-
-# Create on the machine where the CA file is located and copy them afterwards
-#sudo consul tls cert create -server -dc "$REGION"
-sudo chown -R consul:consul /etc/consul.d
-
-systemctrl enable consul.service
-systemctrl start consul.service
-
-consul join -wan cs001c.control.lofar
-
-
-### NOMAD ###
-sudo yum -y install nomad
-
-cat << EOF > /etc/nomad.d/nomad.hcl
-# Full configuration options can be found at https://www.nomadproject.io/docs/configuration
-
-data_dir  = "/localdata/nomad/data"
-bind_addr = "0.0.0.0"
-name      = "lcu"
-region    = "$REGION"
-datacenter = "stat"
-
-server {
-  # license_path is required for Nomad Enterprise as of Nomad v1.1.1+
-  #license_path = "/etc/nomad.d/license.hclic"
-  enabled          = true
-  bootstrap_expect = 1
-  encrypt          = "..."
-}
-
-client {
-  enabled = true
-  servers = ["127.0.0.1"]
-  cni_path = "/localdata/cni/bin"
-
-  host_volume "monitoring-postgresql-data" {
-    path = "/localdata/volumes/monitoring-postgresql-data"
-  }
-
-  host_volume "monitoring-loki-data" {
-    path = "/localdata/volumes/monitoring-loki-data"
-  }
-
-  host_volume "monitoring-prometheus-data" {
-    path = "/localdata/volumes/monitoring-prometheus-data"
-  }
-
-}
-
-consul {
-  address             = "127.0.0.1:8500"
-  server_service_name = "nomad"
-  client_service_name = "nomad-client"
-  auto_advertise      = true
-  server_auto_join    = true
-  client_auto_join    = true
-}
-
-plugin "docker" {
-  config {
-    volumes {
-      enabled = true
-    }
-    allow_caps = ["all"]
-  }
-}
-EOF
-
-mkdir -p /localdata/volumes/monitoring-postgresql-data
-mkdir -p /localdata/volumes/monitoring-prometheus-data
-mkdir -p /localdata/volumes/monitoring-loki-data
-chmod 0777 /localdata/volumes/monitoring-*
-
-curl -L -o cni-plugins.tgz "https://github.com/containernetworking/plugins/releases/download/v1.0.0/cni-plugins-linux-$( [ "$(uname -m)" = aarch64 ] && echo arm64 || echo amd64)"-v1.0.0.tgz
-mkdir -p /localdata/cni/bin && \
-tar -C /localdata/cni/bin -xzf cni-plugins.tgz
-
-echo 1 | tee /proc/sys/net/bridge/bridge-nf-call-arptables
-echo 1 | tee /proc/sys/net/bridge/bridge-nf-call-ip6tables
-echo 1 | tee /proc/sys/net/bridge/bridge-nf-call-iptables
-
-cat << "EOF" > /etc/sysctl.d/10-bridge.conf
-net.bridge.bridge-nf-call-arptables = 1
-net.bridge.bridge-nf-call-ip6tables = 1
-net.bridge.bridge-nf-call-iptables = 1
-EOF
-
-systemctrl enable nomad.service
-systemctrl start nomad.service
-
-nomad server join cs001c.control.lofar:4648
diff --git a/infra/jobs/Makefile b/infra/jobs/Makefile
new file mode 100644
index 000000000..9300046e1
--- /dev/null
+++ b/infra/jobs/Makefile
@@ -0,0 +1,12 @@
+
+
+DIR_OUT ?= .
+
+SUBDIRS := $(wildcard */.)
+
+.PHONY: render $(SUBDIRS)
+
+render: $(SUBDIRS)
+
+$(SUBDIRS):
+	$(MAKE) -C $@
diff --git a/infra/jobs/station/Makefile b/infra/jobs/station/Makefile
new file mode 100644
index 000000000..15bcff97b
--- /dev/null
+++ b/infra/jobs/station/Makefile
@@ -0,0 +1,18 @@
+
+TAG ?= latest
+STATION ?= dev
+DIR_OUT ?= .
+DIR_SRC += .
+SRC_JOBS += $(wildcard $(addsuffix /*.levant.nomad, $(DIR_SRC)))
+JOBS := $(patsubst %.levant.nomad,%.nomad, $(SRC_JOBS))
+ENV ?= ../../env.yaml
+
+.PHONY: render
+
+render: pull $(JOBS) $(DEVICES)
+
+pull:
+	docker pull -q hashicorp/levant
+
+%.nomad: %.levant.nomad
+	docker run --rm -v $(realpath $(ENV)):/env.yaml -v $(realpath $(DIR_SRC)):/in -v $(realpath $(DIR_OUT)):/out hashicorp/levant render -var-file=/env.yaml -var image_tag="$(TAG)" -var station="$(STATION)" -out=/out/$@ /in/$<
diff --git a/infra/jobs/station/device-server.levant.nomad b/infra/jobs/station/device-server.levant.nomad
new file mode 100644
index 000000000..4b313fa58
--- /dev/null
+++ b/infra/jobs/station/device-server.levant.nomad
@@ -0,0 +1,89 @@
+job "device-servers" {
+    datacenters = ["stat"]
+    type        = "service"
+
+    reschedule {
+      unlimited = true
+      delay = "30s"
+      delay_function = "constant"
+    }
+
+    [[ range $device := $.devices ]]
+    [[ with $device ]]
+    [[ $class := .class ]]
+    [[ $name := .class | toLower ]]
+    group "device-[[ $name ]]" {
+        count = 1
+
+        network {
+            mode = "cni/station-control"
+            port "device_server" {}
+            port "zmq_event" {}
+            port "zmq_heartbeat" {}
+            port "metrics" {
+                to = "8000"
+            }
+
+            [[ range $port_name, $port := .ports]]
+              port "[[ $port_name ]]" {
+                  static = "[[ $port ]]"
+              }
+            [[ end ]]
+        }
+
+        service {
+            tags = ["scrape"]
+            name = "device-[[ $name ]]"
+            port = "device_server"
+            meta {
+                metrics_address = "${NOMAD_ADDR_metrics}"
+                metrics_path = "/"
+            }
+        }
+
+        task "device-[[ $name ]]" {
+            driver = "docker"
+
+            config {
+                image = "git.astron.nl:5000/lofar2.0/tango/lofar-device-base:[[ $.image_tag ]]"
+                ports = [
+                    [[ range $port_name, $port := .ports]]
+                      "[[ $port_name ]]",
+                    [[ end ]]
+
+                    "device_server",
+                    "zmq_event",
+                    "zmq_heartbeat",
+                    "metrics"
+                ]
+
+                entrypoint = [
+                    "l2ss-ds",
+                    "[[ $class ]]",
+                    "STAT",
+                    "-v",
+                    "-ORBendPoint",
+                    "giop:tcp:0:${NOMAD_PORT_device_server}",
+                    "-ORBendPointPublish",
+                    "giop:tcp:device-[[ $name ]].service.consul:${NOMAD_PORT_device_server}"
+                ]
+            }
+
+            env {
+                TANGO_HOST               = "tango.service.consul:10000"
+                TANGO_ZMQ_EVENT_PORT     = "${NOMAD_PORT_zmq_event}"
+                TANGO_ZMQ_HEARTBEAT_PORT = "${NOMAD_PORT_zmq_heartbeat}"
+                MINIO_ROOT_USER = "minioadmin"
+                MINIO_ROOT_PASSWORD = "minioadmin"
+            }
+
+
+            resources {
+                cpu    = 250
+                memory = 512
+            }
+        }
+    }
+    [[ end ]]
+    [[ end ]]
+}
diff --git a/infra/jobs/station/dsconfig.levant.nomad b/infra/jobs/station/dsconfig.levant.nomad
new file mode 100644
index 000000000..cd5eac570
--- /dev/null
+++ b/infra/jobs/station/dsconfig.levant.nomad
@@ -0,0 +1,52 @@
+job "dsconfig" {
+  parameterized {
+    payload = "required"
+  }
+
+  datacenters = ["stat"]
+  type        = "batch"
+
+  group "dsconfig" {
+    count = 1
+
+    task "dsconfig" {
+      driver = "docker"
+
+      config {
+        image = "git.astron.nl:5000/lofar2.0/tango/dsconfig:[[ .image_tag ]]"
+        mount {
+          type   = "bind"
+          source = "local/dsconfig-update-settings.json"
+          target = "/tmp/dsconfig-update-settings.json"
+        }
+        mount {
+          type   = "bind"
+          source = "local/run.sh"
+          target = "/run.sh"
+        }
+        entrypoint = ["bash", "/run.sh"]
+
+      }
+
+      dispatch_payload {
+        file = "dsconfig-update-settings.json"
+      }
+      template {
+        data        = <<EOH
+        #!/bin/bash
+        /manage_object_properties.py --write < /tmp/dsconfig-update-settings.json
+        json2tango --write --update /tmp/dsconfig-update-settings.json
+        EOH
+        destination = "local/run.sh"
+      }
+      resources {
+        cpu    = 100
+        memory = 100
+      }
+      env {
+        TANGO_HOST = "tango.service.consul:10000"
+      }
+    }
+
+  }
+}
diff --git a/infra/jobs/station/ec-sim.levant.nomad b/infra/jobs/station/ec-sim.levant.nomad
new file mode 100644
index 000000000..1880d16d2
--- /dev/null
+++ b/infra/jobs/station/ec-sim.levant.nomad
@@ -0,0 +1,35 @@
+job "simulators" {
+  datacenters = ["stat"]
+  type        = "service"
+
+  group "ec-sim" {
+    count = 1
+
+    network {
+      mode = "bridge"
+      port "opcua" {
+        static = 4850
+        to     = 4840
+      }
+    }
+
+    service {
+      name = "ec-sim"
+      port = "opcua"
+    }
+
+    task "sim" {
+      driver = "docker"
+
+      config {
+        image = "git.astron.nl:5000/lofar2.0/tango/ec-sim:latest"
+        ports = ["opcua"]
+      }
+      resources {
+        cpu    = 100
+        memory = 100
+      }
+    }
+
+  }
+}
diff --git a/infra/jobs/station/jupyter.levant.nomad b/infra/jobs/station/jupyter.levant.nomad
new file mode 100644
index 000000000..3475f0dbb
--- /dev/null
+++ b/infra/jobs/station/jupyter.levant.nomad
@@ -0,0 +1,69 @@
+job "jupyter" {
+  datacenters = ["stat"]
+  type        = "service"
+
+  group "jupyter-lab" {
+    count = 1
+
+    network {
+      mode = "bridge"
+      port "jupyter" {
+        to = 8888
+      }
+    }
+
+    volume "notebooks" {
+      type      = "host"
+      read_only = false
+      source    = "jupyter-notebooks"
+    }
+
+    service {
+      name = "jupyter"
+      port = "jupyter"
+      task = "jupyter"
+    }
+
+    task "jupyter-lab" {
+      driver = "docker"
+
+      volume_mount {
+        volume      = "notebooks"
+        destination = "/home/jovyan/notebooks"
+        read_only   = false
+      }
+
+      config {
+        image = "git.astron.nl:5000/lofar2.0/tango/jupyter-lab:[[.image_tag]]"
+        ports = ["jupyter"]
+        mount {
+          type   = "bind"
+          source = "local/jupyter_notebook_config.py"
+          target = "/home/jovyan/.jupyter/jupyter_notebook_config.py"
+        }
+      }
+
+      env {
+        TANGO_HOST = "tango.service.consul:10000"
+      }
+
+      resources {
+        cpu        = 1024
+        memory     = 1024
+        memory_max = 8192
+      }
+      template {
+        data = <<EOH
+c.NotebookApp.base_url = '/jupyter'
+c.NotebookApp.password_required = False
+c.NotebookApp.token = ''
+c.NotebookApp.trust_xheaders = True
+c.NotebookApp.allow_origin = '*'
+
+        EOH
+
+        destination = "local/jupyter_notebook_config.py"
+      }
+    }
+  }
+}
diff --git a/infra/jobs/station/logging.levant.nomad b/infra/jobs/station/logging.levant.nomad
new file mode 100644
index 000000000..a3aca67de
--- /dev/null
+++ b/infra/jobs/station/logging.levant.nomad
@@ -0,0 +1,155 @@
+job "log-scraping" {
+    datacenters = ["stat"]
+    type        = "system"
+
+    update {
+        min_healthy_time  = "10s"
+        healthy_deadline  = "5m"
+        progress_deadline = "10m"
+        auto_revert       = true
+    }
+
+    group "vector" {
+        count = 1
+        restart {
+            attempts = 3
+            interval = "10m"
+            delay    = "30s"
+            mode     = "fail"
+        }
+        network {
+            mode = "bridge"
+            port "api" {
+                to = 8686
+            }
+        }
+
+        volume "docker-sock" {
+            type      = "host"
+            source    = "docker-sock-ro"
+            read_only = true
+        }
+
+        ephemeral_disk {
+            size   = 500
+            sticky = true
+        }
+
+        service {
+            connect {
+                sidecar_service {
+                    proxy {
+                        upstreams {
+                            destination_name = "prometheus"
+                            local_bind_port  = 9090
+                        }
+                        upstreams {
+                            destination_name = "loki"
+                            local_bind_port  = 3100
+                        }
+                    }
+                }
+            }
+        }
+
+        task "vector" {
+            driver = "docker"
+            config {
+                image = "timberio/vector:0.32.1.custom.989ad14-distroless-static"
+                ports = ["api"]
+            }
+            # docker socket volume mount
+            volume_mount {
+                volume      = "docker-sock"
+                destination = "/var/run/docker.sock"
+                read_only   = true
+            }
+            # Vector won't start unless the sinks(backends) configured are healthy
+            env {
+                VECTOR_CONFIG          = "local/vector.toml"
+                VECTOR_REQUIRE_HEALTHY = "true"
+            }
+            # resource limits are a good idea because you don't want your log collection to consume all resources available
+            resources {
+                cpu    = 50 # 500 MHz
+                memory = 100 # 256MB
+            }
+            # template with Vector's configuration
+            template {
+                destination     = "local/vector.toml"
+                change_mode     = "signal"
+                change_signal   = "SIGHUP"
+                left_delimiter  = "(("
+                right_delimiter = "))"
+                data            = <<EOF
+data_dir                     = "alloc/data/vector/"
+healthchecks.require_healthy = true
+
+[api]
+  enabled              = true
+  address              = "0.0.0.0:8686"
+  playground           = false
+
+[sources.docker-local]
+  type                 = "docker_logs"
+  docker_host          = "/var/run/docker.sock"
+  exclude_containers   = ["vector-"]
+[transforms.nomad-flags]
+  inputs = ["docker-local"]
+  type   = "remap"
+  source = '''
+    structured =
+      parse_syslog(.message) ??
+      parse_json(.message) ??
+      parse_common_log(.message) ??
+      parse_key_value!(.message)
+    .                = merge!(., structured)
+    .nomad.job       = .label."com.hashicorp.nomad.job_name"
+    .nomad.task      = .label."com.hashicorp.nomad.task_name"
+    .nomad.group     = .label."com.hashicorp.nomad.task_group_name"
+    .nomad.namespace = .label."com.hashicorp.nomad.namespace"
+    .nomad.node      = .label."com.hashicorp.nomad.node_name"
+    .nomad.job_id    = .label."com.hashicorp.nomad.job_id"
+    .nomad.node_id   = .label."com.hashicorp.nomad.node_id"
+  '''
+
+[sinks.loki]
+  type                 = "loki"
+  inputs               = [ "nomad-flags" ]
+  endpoint             = "http://localhost:3100"
+  encoding.codec       = "json"
+  healthcheck.enabled  = true
+  remove_label_fields  = true
+[sinks.loki.labels]
+  "nomad_*"            = '{{ nomad }}'
+
+[sources.host_metrics]
+  type                 = "host_metrics"
+  scrape_interval_secs = 10
+[sources.nomad_metrics]
+  type                 = "prometheus_scrape"
+  scrape_interval_secs = 10
+  endpoints            = [ "http://(( env "attr.unique.network.ip-address" )):4646/v1/metrics?format=prometheus" ]
+[sources.vector_metrics]
+  type                 = "internal_metrics"
+  scrape_interval_secs = 10
+[sinks.prometheus_remote_write]
+  type                 = "prometheus_remote_write"
+  inputs               = [ "host_metrics", "nomad_metrics", "internal_metrics" ]
+  endpoint             = "http://localhost:9090/api/v1/write"
+  healthcheck.enabled  = false
+EOF
+            }
+            service {
+                check {
+                    port     = "api"
+                    type     = "http"
+                    path     = "/health"
+                    interval = "30s"
+                    timeout  = "5s"
+                }
+            }
+            kill_timeout = "30s"
+        }
+    }
+}
diff --git a/infra/jobs/station/monitoring.levant.nomad b/infra/jobs/station/monitoring.levant.nomad
new file mode 100644
index 000000000..a9010f747
--- /dev/null
+++ b/infra/jobs/station/monitoring.levant.nomad
@@ -0,0 +1,395 @@
+job "monitoring" {
+  datacenters = ["stat"]
+  type        = "service"
+
+  group "postgres" {
+    count = 1
+
+    network {
+      mode = "bridge"
+      port "postgres" {
+        to = 5432
+      }
+    }
+
+    volume "postgresql" {
+      type      = "host"
+      read_only = false
+      source    = "monitoring-postgresql-data"
+    }
+
+    service {
+      name         = "postgres"
+      port         = "postgres"
+      task         = "postgres"
+      address_mode = "alloc"
+
+      connect {
+        sidecar_service {}
+      }
+    }
+
+    task "postgres" {
+      driver = "docker"
+
+      volume_mount {
+        volume      = "postgresql"
+        destination = "/var/lib/postgresql/data"
+        read_only   = false
+      }
+
+      config {
+        image = "postgres:[[.monitoring.db.version]]"
+        ports = ["postgres"]
+      }
+
+      env {
+        POSTGRES_DB       = "grafana"
+        POSTGRES_USER     = "postgres"
+        POSTGRES_PASSWORD = "password"
+      }
+
+      resources {
+        cpu    = 250
+        memory = 512
+      }
+    }
+  }
+
+  group "grafana" {
+    network {
+      mode = "bridge"
+      port "http" {
+        to = 3000
+      }
+    }
+
+    service {
+      tags = ["haproxy", "scrape"]
+      name = "grafana"
+      port = "http"
+
+      connect {
+        sidecar_service {
+          proxy {
+            upstreams {
+              destination_name = "postgres"
+              local_bind_port  = 5432
+            }
+            upstreams {
+              destination_name = "prometheus"
+              local_bind_port  = 9090
+            }
+            upstreams {
+              destination_name = "loki"
+              local_bind_port  = 3100
+            }
+          }
+        }
+      }
+    }
+
+    task "wait-for-db" {
+      lifecycle {
+        hook    = "prestart"
+        sidecar = false
+      }
+      driver = "docker"
+
+      config {
+        image   = "postgres:[[.monitoring.db.version]]"
+        command = "sh"
+        args    = ["-c", "while ! pg_isready -h localhost; do sleep 1; done"]
+      }
+      env {
+        PGPASSFILE = "local/pgpass.txt"
+      }
+
+      template {
+        data        = <<EOH
+{{ range service "postgres" }}
+localhost:5432:grafana:postgres:password
+{{ end }}
+                EOH
+        destination = "local/pgpass.txt"
+      }
+    }
+
+    task "grafana" {
+      driver = "docker"
+      config {
+        image = "git.astron.nl:5000/lofar2.0/tango/grafana:[[.image_tag]]"
+        ports = ["http"]
+        mount {
+          type   = "bind"
+          source = "local/datasource-prometheus.yaml"
+          target = "/etc/grafana/provisioning/datasources/prometheus.yaml"
+        }
+        mount {
+          type   = "bind"
+          source = "local/datasource-loki.yaml"
+          target = "/etc/grafana/provisioning/datasources/loki.yaml"
+        }
+      }
+
+      env {
+        GF_SERVER_DOMAIN              = "[[.station]]c.control.lofar"
+        GF_SERVER_ROOT_URL            = "%(protocol)s://%(domain)s:%(http_port)s/grafana/"
+        GF_SERVER_SERVE_FROM_SUB_PATH = "true"
+        GF_DATABASE_TYPE              = "postgres"
+        GF_DATABASE_HOST              = "localhost:5432"
+        GF_DATABASE_NAME              = "grafana"
+        GF_DATABASE_USER              = "postgres"
+        GF_DATABASE_PASSWORD          = "password"
+        GF_AUTH_ANONYMOUS_ENABLED     = "true"
+        GF_AUTH_ANONYMOUS_ORG_ROLE    = "Admin"
+        GF_AUTH_DISABLE_LOGIN_FORM    = "true"
+      }
+
+      template {
+        data        = <<EOH
+        apiVersion: 1
+
+        datasources:
+          - name: Prometheus
+            type: prometheus
+            access: proxy
+            orgId: 1
+            uid: prometheus
+            url: http://localhost:9090
+            isDefault: true
+
+        EOH
+        destination = "local/datasource-prometheus.yaml"
+      }
+      template {
+        data        = <<EOH
+        apiVersion: 1
+
+        datasources:
+          - name: Loki
+            type: loki
+            access: proxy
+            orgId: 1
+            uid: loki
+            url: http://localhost:3100
+            jsonData:
+              esVersion:  7.10.0
+              includeFrozen: false
+              logLevelField:
+              logMessageField:
+              maxConcurrentShardRequests: 5
+              timeField: "@timestamp"
+
+        EOH
+        destination = "local/datasource-loki.yaml"
+      }
+      resources {
+        cpu        = 250
+        memory     = 1024
+        memory_max = 8192
+      }
+    }
+  }
+  group "prometheus" {
+
+    network {
+      mode = "bridge"
+
+      port "prometheus" {
+        to = 9090
+      }
+    }
+
+    volume "prometheus" {
+      type      = "host"
+      read_only = false
+      source    = "monitoring-prometheus-data"
+    }
+
+    service {
+      tags         = ["haproxy", "scrape"]
+      name         = "prometheus"
+      port         = "prometheus"
+      address_mode = "alloc"
+
+      connect {
+        sidecar_service {}
+      }
+
+      check {
+        type     = "http"
+        name     = "prometheus_health"
+        port     = "prometheus"
+        path     = "/-/healthy"
+        interval = "20s"
+        timeout  = "30s"
+      }
+    }
+
+    task "prometheus" {
+      driver = "docker"
+
+      volume_mount {
+        volume      = "prometheus"
+        destination = "/prometheus"
+        read_only   = false
+      }
+
+      config {
+        image = "git.astron.nl:5000/lofar2.0/tango/prometheus:[[.image_tag]]"
+        ports = ["prometheus"]
+        args  = [
+          "--config.file=/etc/prometheus/prometheus.yml",
+          "--web.enable-remote-write-receiver"
+        ]
+        mount {
+          type   = "bind"
+          source = "local/prometheus.yaml"
+          target = "/etc/prometheus/prometheus.yml"
+        }
+      }
+
+      template {
+        data          = <<EOH
+        global:
+          evaluation_interval: 10s
+          scrape_interval: 10s
+          scrape_timeout: 10s
+
+        scrape_configs:
+          - job_name: tango
+            metrics_path: /
+            consul_sd_configs:
+            - server: '{{ with node "cs001c@lofar-cs-001" }}{{ .Node.Address }}:8500{{ end }}'
+              services: ['tango-prometheus-exporter']
+            relabel_configs:
+              - target_label: host
+                replacement: localhost
+
+          - job_name: tango-fast
+            scrape_interval: 1s
+            consul_sd_configs:
+            - server: '{{ with node "cs001c@lofar-cs-001" }}{{ .Node.Address }}:8500{{ end }}'
+              services: ['tango-prometheus-fast-exporter']
+            relabel_configs:
+              - target_label: host
+                replacement: localhost
+
+          - job_name: tango-slow
+            scrape_interval: 60s
+            scrape_timeout: 30s
+            consul_sd_configs:
+            - server: '{{ with node "cs001c@lofar-cs-001" }}{{ .Node.Address }}:8500{{ end }}'
+              services: ['tango-prometheus-slow-exporter']
+            relabel_configs:
+              - target_label: host
+                replacement: localhost
+
+          - job_name: 'consul'
+            metrics_path: '/v1/agent/metrics'
+            params:
+              format: ['prometheus']
+            consul_sd_configs:
+            - server: '{{ with node "cs001c@lofar-cs-001" }}{{ .Node.Address }}:8500{{ end }}'
+              services: ['consul']
+            relabel_configs:
+              - target_label: host
+
+          - job_name: 'nomad_metrics'
+            consul_sd_configs:
+            - server: '{{ with node "cs001c@lofar-cs-001" }}{{ .Node.Address }}:8500{{ end }}'
+              services: ['nomad-client', 'nomad']
+            relabel_configs:
+            - source_labels: ['__meta_consul_tags']
+              regex: '(.*)http(.*)'
+              action: keep
+            scrape_interval: 5s
+            metrics_path: /v1/metrics
+            params:
+              format: ['prometheus']
+
+          - job_name: "consul_services"
+            consul_sd_configs:
+              - server: '{{ with node "cs001c@lofar-cs-001" }}{{ .Node.Address }}:8500{{ end }}'
+                services:
+          {{range services}}{{if in .Tags "scrape"}}{{ if .Name | regexMatch "(.+)-sidecar-proxy$" }}{{ else }}
+                  - '{{.Name}}'
+          {{end}}{{end}}{{end}}
+            relabel_configs:
+              - target_label: host
+                replacement: localhost
+              - source_labels: [__meta_consul_service_metadata_metrics_path]
+                regex:  '(/.*)'            # capture '/...' part
+                target_label: __metrics_path__  # change metrics path
+              - source_labels: [__meta_consul_service_metadata_metrics_address]
+                regex:  '(.+)'
+                target_label: __address__  # change address
+        EOH
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+        destination   = "local/prometheus.yaml"
+      }
+
+      resources {
+        cpu        = 500
+        memory     = 2048
+        memory_max = 8192
+      }
+    }
+  }
+
+  group "loki" {
+
+    ephemeral_disk {
+      size = 101
+    }
+    network {
+      mode = "bridge"
+
+      port "loki" {
+        to = 3100
+        # should be activated when fully replaces docker-compose setup
+        #static = 3100
+      }
+    }
+
+    volume "loki" {
+      type      = "host"
+      read_only = false
+      source    = "monitoring-loki-data"
+    }
+
+    service {
+      tags         = ["haproxy", "scrape"]
+      name         = "loki"
+      port         = "loki"
+      address_mode = "alloc"
+
+      connect {
+        sidecar_service {}
+      }
+    }
+
+    task "loki" {
+      driver = "docker"
+
+      volume_mount {
+        volume      = "loki"
+        destination = "/loki"
+        read_only   = false
+      }
+
+      config {
+        image = "git.astron.nl:5000/lofar2.0/tango/loki:[[.image_tag]]"
+        ports = ["prometheus"]
+      }
+
+      resources {
+        cpu        = 250
+        memory     = 768
+        memory_max = 8192
+      }
+    }
+  }
+}
diff --git a/infra/jobs/station/nomad-client.nomad b/infra/jobs/station/nomad-client.nomad
new file mode 100644
index 000000000..80869738b
--- /dev/null
+++ b/infra/jobs/station/nomad-client.nomad
@@ -0,0 +1,391 @@
+job "nomad-client" {
+  datacenters = ["stat"]
+  type        = "service"
+
+  group "qemu-vm" {
+    count = 1
+
+    volume "images" {
+      type      = "host"
+      read_only = false
+      source    = "images"
+    }
+
+    network {
+      port "http" {}
+    }
+
+    task "imds" {
+      lifecycle {
+        hook    = "prestart"
+        sidecar = true
+      }
+
+      driver = "exec"
+
+      config {
+        command = "python3"
+        args    = [
+          "-m", "http.server", "${NOMAD_PORT_http}",
+          "--directory", "local/"
+        ]
+      }
+      template {
+        data = <<EOH
+        instance-id: ${NOMAD_SHORT_ALLOC_ID}
+        local-hostname: ${NOMAD_SHORT_ALLOC_ID}-client
+        EOH
+
+        destination = "local/meta-data"
+      }
+      template {
+        data = <<EOH
+        #cloud-config
+        password: password
+        chpasswd:
+          expire: False
+        ssh_authorized_keys:
+          - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPuqXIY4ATAkSfk3+nKIoyvFh/AUbhASgKTqUM0SfXvg root@cs001c
+
+        bootcmd:
+          - mkdir /localdata
+          - DEBIAN_FRONTEND=noninteractive apt-get -yq update
+          - DEBIAN_FRONTEND=noninteractive apt-get -yq install gnupg
+
+        mounts:
+          - [ vdb, /localdata ]
+          - [ vdc, /var/lib/docker ]
+
+        runcmd:
+          - ip link set dev ens3 mtu 9000
+          - [systemctl, restart, systemd-resolved]
+          - [sysctl ,-w ,fs.inotify.max_user_instances=256]
+          - curl -L -o cni-plugins.tgz "https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-$( [ $(uname -m) = aarch64 ] && echo arm64 || echo amd64)"-v1.3.0.tgz
+          - mkdir -p /opt/cni/bin
+          - tar -C /opt/cni/bin -xzf cni-plugins.tgz
+          - mkdir -p /localdata/nomad/data
+          - mkdir -p /localdata/volumes/monitoring-postgresql-data
+          - mkdir -p /localdata/volumes/monitoring-prometheus-data
+          - mkdir -p /localdata/volumes/monitoring-loki-data
+          - mkdir -p /localdata/volumes/tango-database
+          - mkdir -p /localdata/volumes/object-storage-data
+          - chmod 0777 /localdata/volumes/*
+          - [systemctl, enable, consul.service]
+          - [systemctl, start, consul.service]
+          - [systemctl, enable, nomad.service]
+          - [systemctl, start, nomad.service]
+          - [systemctl, enable, docker.service]
+          - [systemctl, start, docker.service]
+          - iptables -A CNI-BR9000 -d 172.31.0.0/16 -j ACCEPT
+
+        package_update: true
+        package_upgrade: true
+        packages:
+          - docker.io
+          - nomad
+          - curl
+          - consul
+
+        write_files:
+        - content: |
+            {
+                "cniVersion": "0.4.0",
+                "name": "station-control",
+                "plugins": [
+                {
+                  "type": "bridge",
+                  "bridge": "br9000",
+                  "isDefaultGateway": true,
+                  "forceAddress": false,
+                  "ipMasq": true,
+                  "hairpinMode": true,
+                  "mtu": 9000,
+                  "ipam": {
+                    "type": "host-local",
+                    "subnet": "172.31.0.0/16"
+                  }
+                },
+                {
+                  "type": "firewall",
+                  "backend": "iptables",
+                  "iptablesAdminChainName": "CNI-BR9000"
+                },
+                {
+                  "type": "portmap",
+                  "capabilities": { "portMappings": true },
+                  "snat": true
+                }
+                ]
+            }
+          path: /opt/cni/config/station-control.conflist
+          defer: true
+
+        - content: |
+            bind_addr = "0.0.0.0"
+            name      = "{{ env "NOMAD_SHORT_ALLOC_ID" }}-client"
+            region    = "{{ env "node.region" }}"
+            data_dir  = "/localdata/nomad/data"
+            datacenter = "stat"
+
+            client {
+              enabled = true
+              servers = ["{{ env "attr.unique.network.ip-address" }}"]
+              cni_path = "/opt/cni/bin"
+
+              host_volume "docker-sock-ro" {
+                path      = "/var/run/docker.sock"
+                read_only = true
+              }
+
+              host_volume "monitoring-postgresql-data" {
+                path = "/localdata/volumes/monitoring-postgresql-data"
+              }
+
+              host_volume "monitoring-loki-data" {
+                path = "/localdata/volumes/monitoring-loki-data"
+              }
+
+              host_volume "monitoring-prometheus-data" {
+                path = "/localdata/volumes/monitoring-prometheus-data"
+              }
+
+              host_volume "tango-database" {
+                path = "/localdata/volumes/tango-database"
+              }
+
+              host_volume "object-storage-data" {
+                path = "/localdata/volumes/object-storage-data"
+              }
+
+              host_volume "jupyter-notebooks" {
+                path = "/localdata/volumes/jupyter-notebooks"
+              }
+
+              options = {
+                "driver.allowlist" = "docker,exec"
+              }
+            }
+
+            consul {
+              address             = "localhost:8500"
+              server_service_name = "nomad"
+              client_service_name = "nomad-client"
+              auto_advertise      = true
+              server_auto_join    = true
+              client_auto_join    = true
+            }
+
+            telemetry {
+              collection_interval        = "1s"
+              disable_hostname           = true
+              prometheus_metrics         = true
+              publish_allocation_metrics = true
+              publish_node_metrics       = true
+            }
+
+            plugin "docker" {
+              config {
+                volumes {
+                  enabled = true
+                }
+                allow_caps = ["all"]
+                extra_labels = ["job_name", "job_id", "task_group_name", "task_name", "namespace", "node_name", "node_id"]
+              }
+            }
+          path: /etc/nomad.d/nomad.hcl
+          defer: true
+        - content: |
+            datacenter = "{{ env "attr.consul.datacenter" }}"
+            data_dir = "/opt/consul"
+            bind_addr = "{{"{{"}} GetInterfaceIP \"ens3\" {{"}}"}}"
+            encrypt = "{{ with nomadVar "nomad/jobs/nomad-client/qemu-vm/imds" }}{{ .consul_encrypt }}{{ end }}"
+            retry_join = ["10.99.250.250"]
+            server = false
+            ports {
+              grpc = 8502
+            }
+          path: /etc/consul.d/consul.hcl
+          defer: true
+
+        apt:
+          preserve_source_list: true
+          sources:
+            hashicorp:
+              source: 'deb https://apt.releases.hashicorp.com $RELEASE main'
+              key: |
+                -----BEGIN PGP PUBLIC KEY BLOCK-----
+
+                mQINBGO9u+MBEADmE9i8rpt8xhRqxbzlBG06z3qe+e1DI+SyjscyVVRcGDrEfo+J
+                W5UWw0+afey7HFkaKqKqOHVVGSjmh6HO3MskxcpRm/pxRzfni/OcBBuJU2DcGXnG
+                nuRZ+ltqBncOuONi6Wf00McTWviLKHRrP6oWwWww7sYF/RbZp5xGmMJ2vnsNhtp3
+                8LIMOmY2xv9LeKMh++WcxQDpIeRohmSJyknbjJ0MNlhnezTIPajrs1laLh/IVKVz
+                7/Z73UWX+rWI/5g+6yBSEtj368N7iyq+hUvQ/bL00eyg1Gs8nE1xiCmRHdNjMBLX
+                lHi0V9fYgg3KVGo6Hi/Is2gUtmip4ZPnThVmB5fD5LzS7Y5joYVjHpwUtMD0V3s1
+                HiHAUbTH+OY2JqxZDO9iW8Gl0rCLkfaFDBS2EVLPjo/kq9Sn7vfp2WHffWs1fzeB
+                HI6iUl2AjCCotK61nyMR33rNuNcbPbp+17NkDEy80YPDRbABdgb+hQe0o8htEB2t
+                CDA3Ev9t2g9IC3VD/jgncCRnPtKP3vhEhlhMo3fUCnJI7XETgbuGntLRHhmGJpTj
+                ydudopoMWZAU/H9KxJvwlVXiNoBYFvdoxhV7/N+OBQDLMevB8XtPXNQ8ZOEHl22G
+                hbL8I1c2SqjEPCa27OIccXwNY+s0A41BseBr44dmu9GoQVhI7TsetpR+qwARAQAB
+                tFFIYXNoaUNvcnAgU2VjdXJpdHkgKEhhc2hpQ29ycCBQYWNrYWdlIFNpZ25pbmcp
+                IDxzZWN1cml0eStwYWNrYWdpbmdAaGFzaGljb3JwLmNvbT6JAlQEEwEIAD4CGwMF
+                CwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQR5iuxlTlwVQoyOQu6qFvy8piHnAQUC
+                Y728PQUJCWYB2gAKCRCqFvy8piHnAd16EADeBtTgkdVEvct40TH/9HKkR/Lc/ohM
+                rer6FFHdKmceJ6Ma8/Qm4nCO5C7c4+EPjsUXdhK5w8DSdC5VbKLJDY1EnDlmU5B1
+                wSFkGoYKoB8lUn30E77E33MTu2kfrSuF605vetq269CyBwIJV7oNN6311dW8iQ6z
+                IytTtlJbVr4YZ7Vst40/uR4myumk9bVBGEd6JhFAPmr/um+BZFhRf9/8xtOryOyB
+                GF2d+bc9IoAugpxwv0IowHEqkI4RpK2U9hvxG80sTOcmerOuFbmNyPwnEgtJ6CM1
+                bc8WAmObJiQcRSLbcgF+a7+2wqrUbCqRE7QoS2wjd1HpUVPmSdJN925c2uaua2A4
+                QCbTEg8kV2HiP0HGXypVNhZJt5ouo0YgR6BSbMlsMHniDQaSIP1LgmEz5xD4UAxO
+                Y/GRR3LWojGzVzBb0T98jpDgPtOu/NpKx3jhSpE2U9h/VRDiL/Pf7gvEIxPUTKuV
+                5D8VqAiXovlk4wSH13Q05d9dIAjuinSlxb4DVr8IL0lmx9DyHehticmJVooHDyJl
+                HoA2q2tFnlBBAFbN92662q8Pqi9HbljVRTD1vUjof6ohaoM+5K1C043dmcwZZMTc
+                7gV1rbCuxh69rILpjwM1stqgI1ONUIkurKVGZHM6N2AatNKqtBRdGEroQo1aL4+4
+                u+DKFrMxOqa5b7kCDQRjvbwTARAA0ut7iKLj9sOcp5kRG/5V+T0Ak2k2GSus7w8e
+                kFh468SVCNUgLJpLzc5hBiXACQX6PEnyhLZa8RAG+ehBfPt03GbxW6cK9nx7HRFQ
+                GA79H5B4AP3XdEdT1gIL2eaHdQot0mpF2b07GNfADgj99MhpxMCtTdVbBqHY8YEQ
+                Uq7+E9UCNNs45w5ddq07EDk+o6C3xdJ42fvS2x44uNH6Z6sdApPXLrybeun74C1Z
+                Oo4Ypre4+xkcw2q2WIhy0Qzeuw+9tn4CYjrhw/+fvvPGUAhtYlFGF6bSebmyua8Q
+                MTKhwqHqwJxpjftM3ARdgFkhlH1H+PcmpnVutgTNKGcy+9b/lu/Rjq/47JZ+5VkK
+                ZtYT/zO1oW5zRklHvB6R/OcSlXGdC0mfReIBcNvuNlLhNcBA9frNdOk3hpJgYDzg
+                f8Ykkc+4z8SZ9gA3g0JmDHY1X3SnSadSPyMas3zH5W+16rq9E+MZztR0RWwmpDtg
+                Ff1XGMmvc+FVEB8dRLKFWSt/E1eIhsK2CRnaR8uotKW/A/gosao0E3mnIygcyLB4
+                fnOM3mnTF3CcRumxJvnTEmSDcoKSOpv0xbFgQkRAnVSn/gHkcbVw/ZnvZbXvvseh
+                7dstp2ljCs0queKU+Zo22TCzZqXX/AINs/j9Ll67NyIJev445l3+0TWB0kego5Fi
+                UVuSWkMAEQEAAYkEcgQYAQgAJhYhBHmK7GVOXBVCjI5C7qoW/LymIecBBQJjvbwT
+                AhsCBQkJZgGAAkAJEKoW/LymIecBwXQgBBkBCAAdFiEE6wr14plJaVlvmYc+cG5m
+                g2nAhekFAmO9vBMACgkQcG5mg2nAhenPURAAimI0EBZbqpyHpwpbeYq3Pygg1bdo
+                IlBQUVoutaN1lR7kqGXwYH+BP6G40x79LwVy/fWV8gO7cDX6D1yeKLNbhnJHPBus
+                FJDmzDPbjTlyWlDqJoWMiPqfAOc1A1cHodsUJDUlA01j1rPTho0S9iALX5R50Wa9
+                sIenpfe7RVunDwW5gw6y8me7ncl5trD0LM2HURw6nYnLrxePiTAF1MF90jrAhJDV
+                +krYqd6IFq5RHKveRtCuTvpL7DlgVCtntmbXLbVC/Fbv6w1xY3A7rXko/03nswAi
+                AXHKMP14UutVEcLYDBXbDrvgpb2p2ZUJnujs6cNyx9cOPeuxnke8+ACWvpnWxwjL
+                M5u8OckiqzRRobNxQZ1vLxzdovYTwTlUAG7QjIXVvOk9VNp/ERhh0eviZK+1/ezk
+                Z8nnPjx+elThQ+r16EM7hD0RDXtOR1VZ0R3OL64AlZYDZz1jEA3lrGhvbjSIfBQk
+                T6mxKUsCy3YbElcOyuohmPRgT1iVDIZ/1iPL0Q0HGm4+EsWCdH6fAPB7TlHD8z2D
+                7JCFLihFDWs5lrZyuWMO9nryZiVjJrOLPcStgJYVd/MhRHR4hC6g09bgo25RMJ6f
+                gyzL4vlEB7aSUih7yjgL9s5DKXP2J71dAhIlF8nnM403R2xEeHyivnyeR/9Ifn7M
+                PJvUMUuoG+ZANSMkrw//XA31o//TVk9WsLD1Edxt5XZCoR+fS+Vz8ScLwP1d/vQE
+                OW/EWzeMRG15C0td1lfHvwPKvf2MN+WLenp9TGZ7A1kEHIpjKvY51AIkX2kW5QLu
+                Y3LBb+HGiZ6j7AaU4uYR3kS1+L79v4kyvhhBOgx/8V+b3+2pQIsVOp79ySGvVwpL
+                FJ2QUgO15hnlQJrFLRYa0PISKrSWf35KXAy04mjqCYqIGkLsz2qQCY2lGcD5k05z
+                bBC4TvxwVxv0ftl2C5Bd0ydl/2YM7GfLrmZmTijK067t4OO+2SROT2oYPDsMtZ6S
+                E8vUXvoGpQ8tf5Nkrn2t0zDG3UDtgZY5UVYnZI+xT7WHsCz//8fY3QMvPXAuc33T
+                vVdiSfP0aBnZXj6oGs/4Vl1Dmm62XLr13+SMoepMWg2Vt7C8jqKOmhFmSOWyOmRH
+                UZJR7nKvTpFnL8atSyFDa4o1bk2U3alOscWS8u8xJ/iMcoONEBhItft6olpMVdzP
+                CTrnCAqMjTSPlQU/9EGtp21KQBed2KdAsJBYuPgwaQeyNIvQEOXmINavl58VD72Y
+                2T4TFEY8dUiExAYpSodbwBL2fr8DJxOX68WH6e3fF7HwX8LRBjZq0XUwh0KxgHN+
+                b9gGXBvgWnJr4NSQGGPiSQVNNHt2ZcBAClYhm+9eC5/VwB+Etg4+1wDmggztiqE=
+                =FdUF
+                -----END PGP PUBLIC KEY BLOCK-----
+        EOH
+
+        destination = "local/user-data"
+      }
+      template {
+        data = <<EOH
+
+        EOH
+
+        destination = "local/vendor-data"
+      }
+
+
+      #  artifact {
+      #    source      = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"
+      #    destination = "alloc/data"
+      #  }
+    }
+
+    task "prepare-image" {
+
+      driver = "exec"
+      lifecycle {
+        hook = "prestart"
+      }
+
+      config {
+        command = "/bin/bash"
+        args    = ["local/run.sh"]
+      }
+
+      template {
+        data        = <<EOH
+        #!/bin/bash
+        qemu-img resize local/disk-{{ env "NOMAD_SHORT_ALLOC_ID" }}.qcow2 +10G
+        mv local/disk-{{ env "NOMAD_SHORT_ALLOC_ID" }}.qcow2 images/disk-{{ env "NOMAD_SHORT_ALLOC_ID" }}.qcow2
+        EOH
+        destination = "local/run.sh"
+      }
+
+      volume_mount {
+        volume           = "images"
+        destination      = "images"
+        propagation_mode = "host-to-task"
+      }
+
+      artifact {
+        source      = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"
+        mode        = "file"
+        destination = "local/disk-${NOMAD_SHORT_ALLOC_ID}.qcow2"
+      }
+    }
+
+    task "debian" {
+
+      driver = "qemu"
+
+      config {
+        image_path        = "/opt/nomad/images/disk-${NOMAD_SHORT_ALLOC_ID}.qcow2"
+        #image_path        = "/opt/images/debian-12-genericcloud-amd64.qcow2"
+        drive_interface   = "virtio"
+        graceful_shutdown = true
+        accelerator       = "kvm"
+        args              = [
+          "-nographic",
+          #"-snapshot",
+          #"-drive", "file=debian-12-genericcloud-amd64.qcow2,if=virtio",
+          "-net", "nic,model=virtio,macaddr=52:54:00:12:34:56",
+          "-net", "tap,script=/etc/ovs-ifup,downscript=/etc/ovs-ifdown",
+          "-drive", "file=/dev/vg0/station_data,format=raw,if=virtio",
+          "-drive", "file=/dev/vg0/docker,format=raw,if=virtio",
+          #"-hdb", "fat:./local/imds",
+          "-smbios",
+          "type=1,serial=ds=nocloud-net;i=${NOMAD_SHORT_ALLOC_ID};h=${NOMAD_SHORT_ALLOC_ID}-client;s=http://${NOMAD_ADDR_http}/",
+          "-cpu", "host",
+          "-smp", "4",
+          #"-serial", "telnet:localhost:4321,server,nowait"
+          "-overcommit", "mem-lock=on",
+          "-overcommit", "cpu-pm=on",
+          #"-chardev", "socket,id=char0,path=/tmp/vhostqemu",
+          #"-device", "vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=docker_volumes",
+          #"-object", "memory-backend-file,id=mem,size=60000,mem-path=/dev/shm,share=on",
+          #"-numa", "node,memdev=mem"
+
+        ]
+      }
+
+      resources {
+        memory = 60000
+        cpu    = 18000
+      }
+    }
+    task "cleanup-image" {
+      lifecycle {
+        hook = "poststop"
+      }
+
+      driver = "exec"
+
+      config {
+        command = "rm"
+        args    = ["images/disk-${NOMAD_SHORT_ALLOC_ID}.qcow2"]
+      }
+
+      volume_mount {
+        volume           = "images"
+        destination      = "images"
+        propagation_mode = "host-to-task"
+      }
+    }
+  }
+}
diff --git a/infra/jobs/station/object-storage.levant.nomad b/infra/jobs/station/object-storage.levant.nomad
new file mode 100644
index 000000000..00cd9701f
--- /dev/null
+++ b/infra/jobs/station/object-storage.levant.nomad
@@ -0,0 +1,115 @@
+job "object-storage" {
+  datacenters = ["stat"]
+  type        = "service"
+
+  group "minio" {
+    count = 1
+
+    network {
+      mode = "bridge"
+      port "s3" {
+        static = 9000
+        to     = 9000
+      }
+      port "console" {
+        static = 9001
+        to     = 9001
+      }
+      port "metrics" {
+        to = 9598
+      }
+    }
+
+    volume "minio" {
+      type      = "host"
+      read_only = false
+      source    = "object-storage-data"
+    }
+
+    service {
+      tags = ["scrape"]
+      name = "s3"
+      port = "s3"
+      task = "minio"
+
+      meta {
+        metrics_address = "${NOMAD_ADDR_metrics}"
+      }
+    }
+    service {
+      name = "s3-console"
+      port = "console"
+      task = "minio"
+    }
+
+    task "minio" {
+      driver = "docker"
+
+      volume_mount {
+        volume      = "minio"
+        destination = "/data"
+        read_only   = false
+      }
+
+      config {
+        image   = "minio/minio:[[.object_storage.minio.version]]"
+        ports   = ["s3", "console"]
+        command = "server"
+        args    = ["--console-address", ":9001", "/data"]
+      }
+
+      env {
+        MINIO_ROOT_USER            = "[[.object_storage.user.name]]"
+        MINIO_ROOT_PASSWORD        = "[[.object_storage.user.pass]]"
+        MINIO_PROMETHEUS_AUTH_TYPE = "public"
+        #MINIO_BROWSER_REDIRECT_URL = "http://[[.station]]c.control.lofar/minio"
+      }
+
+      resources {
+        cpu    = 250
+        memory = 512
+      }
+    }
+
+    task "vector" {
+      driver = "docker"
+      config {
+        image = "timberio/vector:0.32.1.custom.989ad14-distroless-static"
+        ports = ["metrics"]
+      }
+      # Vector won't start unless the sinks(backends) configured are healthy
+      env {
+        VECTOR_CONFIG          = "local/vector.toml"
+        VECTOR_REQUIRE_HEALTHY = "true"
+      }
+      # resource limits are a good idea because you don't want your log collection to consume all resources available
+      resources {
+        cpu    = 10 # 500 MHz
+        memory = 50 # 256MB
+      }
+      # template with Vector's configuration
+      template {
+        destination     = "local/vector.toml"
+        change_mode     = "signal"
+        change_signal   = "SIGHUP"
+        left_delimiter  = "(("
+        right_delimiter = "))"
+        data            = <<EOF
+data_dir                     = "alloc/data/vector/"
+healthchecks.require_healthy = true
+[sources.cluster_metrics]
+  type                 = "prometheus_scrape"
+  scrape_interval_secs = 60
+  endpoints            = [ "http://localhost:9000/minio/v2/metrics/cluster" ]
+[sources.bucket_metrics]
+  type                 = "prometheus_scrape"
+  scrape_interval_secs = 60
+  endpoints            = [ "http://localhost:9000/minio/v2/metrics/bucket" ]
+[sinks.prometheus_exporter]
+  type                 = "prometheus_exporter"
+  inputs               = [ "cluster_metrics", "bucket_metrics" ]
+EOF
+      }
+    }
+  }
+}
diff --git a/infra/jobs/station/sdptr.levant.nomad b/infra/jobs/station/sdptr.levant.nomad
new file mode 100644
index 000000000..127d390d4
--- /dev/null
+++ b/infra/jobs/station/sdptr.levant.nomad
@@ -0,0 +1,43 @@
+job "sdptr" {
+    datacenters = ["stat"]
+    type        = "service"
+
+    [[ range $name, $tr := $.sdptr ]]
+        group "[[ $name ]]" {
+            count = 1
+            network {
+                mode = "bridge"
+                port "sdptr" {
+                    static = [[ $tr.port ]]
+                    to     = [[ $tr.port ]]
+                }
+            }
+
+            service {
+                name = "sdptr-[[ $name ]]"
+                port = "sdptr"
+            }
+
+            task "translator" {
+                driver = "docker"
+
+
+                config {
+                    image   = "git.astron.nl:5000/lofar2.0/sdptr:latest"
+                    ports   = ["sdptr"]
+                    command = "/usr/local/bin/sdptr-[[ $name ]]"
+                    args    = [
+                        "--port", "[[ $tr.port ]]",
+                        "--first_gn", "[[ $tr.first_gn ]]",
+                        "--fpgas", "[[ $tr.fpgas ]]", "--nodaemon"
+                    ]
+                }
+            }
+            resources {
+                cpu    = 500
+                memory = 100
+            }
+
+        }
+    [[ end ]]
+}
diff --git a/infra/jobs/station/tango-prometheus-exporter.levant.nomad b/infra/jobs/station/tango-prometheus-exporter.levant.nomad
new file mode 100644
index 000000000..6f65141cb
--- /dev/null
+++ b/infra/jobs/station/tango-prometheus-exporter.levant.nomad
@@ -0,0 +1,103 @@
+job "tango-prometheus-exporter" {
+  datacenters = ["stat"]
+  type        = "service"
+
+  group "tango-prometheus-exporter" {
+    count = 1
+
+    network {
+      mode = "bridge"
+      port "http" {
+        to = 8000
+      }
+    }
+
+    service {
+      name = "tango-prometheus-exporter"
+      port = "http"
+    }
+
+    task "exporter" {
+      driver = "docker"
+
+      config {
+        image   = "git.astron.nl:5000/lofar2.0/tango/tango-prometheus-exporter:[[.image_tag]]"
+        ports   = ["http"]
+        command = "--config=/code/lofar2-policy.json"
+      }
+      resources {
+        cpu    = 100
+        memory = 100
+      }
+      env {
+        TANGO_HOST = "tango.service.consul:10000"
+      }
+    }
+  }
+
+  group "tango-prometheus-fast-exporter" {
+    count = 1
+
+    network {
+      mode = "bridge"
+      port "http" {
+        to = 8000
+      }
+    }
+
+    service {
+      name = "tango-prometheus-fast-exporter"
+      port = "http"
+    }
+
+    task "exporter" {
+      driver = "docker"
+
+      config {
+        image   = "git.astron.nl:5000/lofar2.0/tango/tango-prometheus-exporter:[[.image_tag]]"
+        ports   = ["http"]
+        command = "--config=/code/lofar2-fast-policy.json"
+      }
+      resources {
+        cpu    = 100
+        memory = 100
+      }
+      env {
+        TANGO_HOST = "tango.service.consul:10000"
+      }
+    }
+  }
+
+  group "tango-prometheus-slow-exporter" {
+    count = 1
+
+    network {
+      mode = "bridge"
+      port "http" {
+        to = 8000
+      }
+    }
+
+    service {
+      name = "tango-prometheus-slow-exporter"
+      port = "http"
+    }
+
+    task "exporter" {
+      driver = "docker"
+
+      config {
+        image   = "git.astron.nl:5000/lofar2.0/tango/tango-prometheus-exporter:[[.image_tag]]"
+        ports   = ["http"]
+        command = "--config=/code/lofar2-slow-policy.json"
+      }
+      resources {
+        cpu    = 100
+        memory = 100
+      }
+      env {
+        TANGO_HOST = "tango.service.consul:10000"
+      }
+    }
+  }
+}
diff --git a/infra/jobs/station/tango.levant.nomad b/infra/jobs/station/tango.levant.nomad
new file mode 100644
index 000000000..0003c4106
--- /dev/null
+++ b/infra/jobs/station/tango.levant.nomad
@@ -0,0 +1,164 @@
+job "tango" {
+  datacenters = ["stat"]
+  type        = "service"
+
+  group "database" {
+    count = 1
+
+    restart {
+      attempts = 10
+      interval = "5m"
+      delay    = "25s"
+      mode     = "delay"
+    }
+
+    network {
+      mode = "bridge"
+      port "mysql" {
+        # should be migrated to port 3000 when fully replaces docker-compose setup
+        static = 3306
+        to     = 3306
+      }
+    }
+
+    volume "database" {
+      type      = "host"
+      read_only = false
+      source    = "tango-database"
+    }
+
+    service {
+      name = "tangodb"
+      port = "mysql"
+
+      check {
+        type     = "tcp"
+        interval = "10s"
+        timeout  = "2s"
+      }
+    }
+
+    task "database" {
+      driver = "docker"
+
+      volume_mount {
+        volume      = "database"
+        destination = "/var/lib/mysql"
+        read_only   = false
+      }
+
+      config {
+        image = "git.astron.nl:5000/lofar2.0/tango/tango-db:[[.tango.db.version]]"
+        ports = ["mysql"]
+      }
+
+      env {
+        MYSQL_ROOT_PASSWORD = "secret"
+        MYSQL_DATABASE      = "tango"
+        MYSQL_USER          = "tango"
+        MYSQL_PASSWORD      = "tango"
+      }
+
+      resources {
+        cpu    = 1024
+        memory = 512
+      }
+    }
+  }
+
+  group "device-server" {
+    count = 1
+
+    restart {
+      attempts = 10
+      interval = "10m"
+      delay    = "60s"
+      mode     = "delay"
+    }
+
+    network {
+      mode = "bridge"
+      port "tango" {
+        static = 10000
+        to     = 10000
+      }
+    }
+
+    service {
+      name = "tango"
+      port = "tango"
+
+      check {
+        type     = "tcp"
+        interval = "10s"
+        timeout  = "20s"
+      }
+    }
+
+    task "wait-for-db" {
+      lifecycle {
+        hook    = "prestart"
+        sidecar = false
+      }
+      driver = "docker"
+
+      config {
+        image   = "busybox"
+        command = "sh"
+        args    = ["-c", "while ! nc -z $MYSQL_HOST $MYSQL_PORT; do sleep 1; done"]
+      }
+
+      template {
+        data        = <<EOH
+            {{ range service "tangodb" }}
+            MYSQL_HOST     = "{{ .Address }}"
+            MYSQL_PORT     = "{{ .Port }}"
+            {{ end }}
+            EOH
+        destination = "local/env.txt"
+        env         = true
+      }
+    }
+
+    task "database-ds" {
+      driver = "docker"
+
+
+      config {
+        image      = "git.astron.nl:5000/lofar2.0/tango/tango-databaseds:[[.tango.databaseds.version]]"
+        ports      = ["tango"]
+        entrypoint = [
+          "/usr/local/bin/DataBaseds",
+          "2",
+          "-ORBendPoint",
+          "giop:tcp:0.0.0.0:10000",
+          "-ORBendPointPublish",
+          "giop:tcp:tango.service.consul:10000"
+        ]
+      }
+
+      env {
+        MYSQL_DATABASE = "tango"
+        MYSQL_USER     = "tango"
+        MYSQL_PASSWORD = "tango"
+        TANGO_HOST     = "tango.service.consul:10000"
+      }
+
+      template {
+        data        = <<EOH
+        {{ range service "tangodb" }}
+        MYSQL_HOST     = "{{ .Address }}:{{ .Port }}"
+        {{ end }}
+        EOH
+        destination = "local/env.txt"
+        env         = true
+      }
+
+
+      resources {
+        cpu    = 1024
+        memory = 512
+      }
+    }
+  }
+}
diff --git a/infra/qemu-test.pkr.hcl b/infra/qemu-test.pkr.hcl
deleted file mode 100644
index 9ee48e24f..000000000
--- a/infra/qemu-test.pkr.hcl
+++ /dev/null
@@ -1,459 +0,0 @@
-packer {
-  required_version = ">= 1.7.0, < 2.0.0"
-
-  required_plugins {
-    qemu = {
-      source  = "github.com/hashicorp/qemu"
-      version = ">= 1.0.0, < 2.0.0"
-    }
-  }
-}
-
-variable "apt_cache_url" {
-  type    = string
-  default = "http://myserver:3142"
-}
-
-variable "boot_wait" {
-  type    = string
-  default = "3s"
-}
-
-variable "bundle_iso" {
-  type    = string
-  default = "false"
-}
-
-variable "communicator" {
-  type    = string
-  default = "ssh"
-}
-
-variable "country" {
-  type    = string
-  default = "CA"
-}
-
-variable "cpus" {
-  type    = string
-  default = "1"
-}
-
-variable "description" {
-  type    = string
-  default = "Base box for x86_64 Debian Bookworm 12.x"
-}
-
-variable "disk_size" {
-  type    = string
-  default = "7500"
-}
-
-variable "domain" {
-  type    = string
-  default = ""
-}
-
-variable "guest_os_type" {
-  type    = string
-  default = "Debian_64"
-}
-
-variable "headless" {
-  type    = string
-  default = "false"
-}
-
-variable "host_port_max" {
-  type    = string
-  default = "4444"
-}
-
-variable "host_port_min" {
-  type    = string
-  default = "2222"
-}
-
-variable "http_port_max" {
-  type    = string
-  default = "9000"
-}
-
-variable "http_port_min" {
-  type    = string
-  default = "8000"
-}
-
-variable "iso_checksum" {
-  type    = string
-  default = "sha512:b462643a7a1b51222cd4a569dad6051f897e815d10aa7e42b68adc8d340932d861744b5ea14794daa5cc0ccfa48c51d248eda63f150f8845e8055d0a5d7e58e6"
-  # default = "file:http://cdimage.debian.org/cdimage/release/current/amd64/iso-cd/SHA512SUMS"
-}
-
-variable "iso_file" {
-  type    = string
-  default = "debian-12.0.0-amd64-netinst.iso"
-}
-
-variable "iso_path_external" {
-  type    = string
-  default = "http://cdimage.debian.org/cdimage/release/current/amd64/iso-cd"
-}
-
-variable "iso_path_internal" {
-  type    = string
-  default = "http://myserver:8080/debian"
-}
-
-variable "keep_registered" {
-  type    = string
-  default = "false"
-}
-
-variable "keyboard" {
-  type    = string
-  default = "us"
-}
-
-variable "language" {
-  type    = string
-  default = "en"
-}
-
-variable "locale" {
-  type    = string
-  default = "en_CA.UTF-8"
-}
-
-variable "memory" {
-  type    = string
-  default = "1024"
-}
-
-variable "mirror" {
-  type    = string
-  default = "ftp.ca.debian.org"
-}
-
-variable "packer_cache_dir" {
-  type    = string
-  default = "${env("PACKER_CACHE_DIR")}"
-}
-
-variable "preseed_file" {
-  type    = string
-  default = "base.preseed"
-}
-
-variable "qemu_binary" {
-  type    = string
-  default = "qemu-system-x86_64"
-}
-
-variable "shutdown_timeout" {
-  type    = string
-  default = "5m"
-}
-
-variable "skip_export" {
-  type    = string
-  default = "false"
-}
-
-variable "ssh_agent_auth" {
-  type    = string
-  default = "false"
-}
-
-variable "ssh_clear_authorized_keys" {
-  type    = string
-  default = "false"
-}
-
-variable "ssh_disable_agent_forwarding" {
-  type    = string
-  default = "false"
-}
-
-variable "ssh_file_transfer_method" {
-  type    = string
-  default = "scp"
-}
-
-variable "ssh_fullname" {
-  type    = string
-  default = "Ghost Writer"
-}
-
-variable "ssh_handshake_attempts" {
-  type    = string
-  default = "10"
-}
-
-variable "ssh_keep_alive_interval" {
-  type    = string
-  default = "5s"
-}
-
-variable "ssh_password" {
-  type    = string
-  default = "1ma63b0rk3d"
-}
-
-variable "ssh_port" {
-  type    = string
-  default = "22"
-}
-
-variable "ssh_pty" {
-  type    = string
-  default = "false"
-}
-
-variable "ssh_timeout" {
-  type    = string
-  default = "60m"
-}
-
-variable "ssh_username" {
-  type    = string
-  default = "ghost"
-}
-
-variable "start_retry_timeout" {
-  type    = string
-  default = "5m"
-}
-
-variable "system_clock_in_utc" {
-  type    = string
-  default = "true"
-}
-
-variable "timezone" {
-  type    = string
-  default = "UTC"
-}
-
-variable "vm_name" {
-  type    = string
-  default = "base-bookworm"
-}
-
-variable "vnc_vrdp_bind_address" {
-  type    = string
-  default = "127.0.0.1"
-}
-
-variable "vnc_vrdp_port_max" {
-  type    = string
-  default = "6000"
-}
-
-variable "vnc_vrdp_port_min" {
-  type    = string
-  default = "5900"
-}
-
-# The "legacy_isotime" function has been provided for backwards compatability,
-# but we recommend switching to the timestamp and formatdate functions.
-
-locals {
-  #output_directory = "build/${legacy_isotime("2006-01-02-15-04-05")}"
-  output_directory = "build/station"
-}
-
-source "qemu" "iso" {
-  accelerator  = "kvm"
-  boot_command = [
-    "<wait><wait><wait><esc><wait><wait><wait>",
-    "/install.amd/vmlinuz ",
-    "initrd=/install.amd/initrd.gz ",
-    "auto=true ",
-    "url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/${var.preseed_file} ",
-    "hostname=${var.vm_name} ",
-    "domain=${var.domain} ",
-    "interface=auto ",
-    "vga=788 noprompt quiet --<enter>"
-  ]
-  boot_wait            = var.boot_wait
-  communicator         = var.communicator
-  cpus                 = var.cpus
-  disk_cache           = "writeback"
-  disk_compression     = false
-  disk_discard         = "ignore"
-  disk_image           = false
-  disk_interface       = "virtio-scsi"
-  disk_size            = var.disk_size
-  format               = "raw"
-  headless             = var.headless
-  host_port_max        = var.host_port_max
-  host_port_min        = var.host_port_min
-  http_content         = { "/${var.preseed_file}" = templatefile(var.preseed_file, { var = var }) }
-  http_port_max        = var.http_port_max
-  http_port_min        = var.http_port_min
-  iso_checksum         = var.iso_checksum
-  iso_skip_cache       = false
-  iso_target_extension = "iso"
-  iso_target_path      = "${regex_replace(var.packer_cache_dir, "^$", "/tmp")}/${var.iso_file}"
-  iso_urls             = [
-    "${var.iso_path_internal}/${var.iso_file}",
-    "${var.iso_path_external}/${var.iso_file}"
-  ]
-  machine_type                 = "pc"
-  memory                       = var.memory
-  net_device                   = "virtio-net"
-  output_directory             = "build/debian-bookworm-amd64-qemu-iso-output"
-  qemu_binary                  = var.qemu_binary
-  shutdown_command             = "echo '${var.ssh_password}' | sudo -E -S poweroff"
-  shutdown_timeout             = var.shutdown_timeout
-  skip_compaction              = true
-  skip_nat_mapping             = false
-  ssh_agent_auth               = var.ssh_agent_auth
-  ssh_clear_authorized_keys    = var.ssh_clear_authorized_keys
-  ssh_disable_agent_forwarding = var.ssh_disable_agent_forwarding
-  ssh_file_transfer_method     = var.ssh_file_transfer_method
-  ssh_handshake_attempts       = var.ssh_handshake_attempts
-  ssh_keep_alive_interval      = var.ssh_keep_alive_interval
-  ssh_password                 = var.ssh_password
-  ssh_port                     = var.ssh_port
-  ssh_pty                      = var.ssh_pty
-  ssh_timeout                  = var.ssh_timeout
-  ssh_username                 = var.ssh_username
-  use_default_display          = false
-  vm_name                      = var.vm_name
-  vnc_bind_address             = var.vnc_vrdp_bind_address
-  vnc_port_max                 = var.vnc_vrdp_port_max
-  vnc_port_min                 = var.vnc_vrdp_port_min
-}
-source "qemu" "img" {
-  accelerator      = "kvm"
-  boot_wait        = var.boot_wait
-  communicator     = var.communicator
-  cpus             = var.cpus
-  disk_cache       = "writeback"
-  disk_compression = true
-  disk_discard     = "ignore"
-  disk_image       = true
-  disk_interface   = "virtio-scsi"
-  disk_size        = var.disk_size
-  format           = "qcow2"
-  headless         = var.headless
-  host_port_max    = var.host_port_max
-  host_port_min    = var.host_port_min
-  http_content     = { "/${var.preseed_file}" = templatefile(var.preseed_file, { var = var }) }
-  http_port_max    = var.http_port_max
-  http_port_min    = var.http_port_min
-  iso_checksum     = "none"
-  iso_urls         = [
-    "./build/debian-bookworm-amd64-qemu-iso-output/base-bookworm"
-  ]
-  machine_type                 = "pc"
-  memory                       = var.memory
-  net_device                   = "virtio-net"
-  output_directory             = local.output_directory
-  qemu_binary                  = var.qemu_binary
-  shutdown_command             = "echo '${var.ssh_password}' | sudo -E -S poweroff"
-  shutdown_timeout             = var.shutdown_timeout
-  skip_compaction              = true
-  skip_nat_mapping             = false
-  ssh_agent_auth               = var.ssh_agent_auth
-  ssh_clear_authorized_keys    = var.ssh_clear_authorized_keys
-  ssh_disable_agent_forwarding = var.ssh_disable_agent_forwarding
-  ssh_file_transfer_method     = var.ssh_file_transfer_method
-  ssh_handshake_attempts       = var.ssh_handshake_attempts
-  ssh_keep_alive_interval      = var.ssh_keep_alive_interval
-  ssh_password                 = var.ssh_password
-  ssh_port                     = var.ssh_port
-  ssh_pty                      = var.ssh_pty
-  ssh_timeout                  = var.ssh_timeout
-  ssh_username                 = var.ssh_username
-  use_default_display          = false
-  vm_name                      = var.vm_name
-  vnc_bind_address             = var.vnc_vrdp_bind_address
-  vnc_port_max                 = var.vnc_vrdp_port_max
-  vnc_port_min                 = var.vnc_vrdp_port_min
-}
-
-build {
-  name = "iso"
-
-  sources = ["source.qemu.iso"]
-}
-
-build {
-  name        = "img"
-  description = "Can't use variables here yet!"
-
-  sources = ["source.qemu.img"]
-
-  provisioner "shell" {
-    execute_command   = "echo '${var.ssh_password}' | sudo -S -E bash '{{ .Path }}'"
-    expect_disconnect = true
-    inline            = [
-      "echo Adding file to Docker Container",
-      "echo '${var.ssh_username} ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/99${var.ssh_username}",
-      "chmod 0440 /etc/sudoers.d/99${var.ssh_username}"
-    ]
-  }
-
-  provisioner "shell" {
-    inline = [
-      "sudo apt-get update",
-      "sudo apt-get --yes dist-upgrade",
-      "sudo apt-get clean"
-    ]
-    inline_shebang = "/bin/sh -e"
-  }
-
-  provisioner "shell" {
-    inline = [
-      "sudo apt-get update && sudo apt-get install -y wget gpg coreutils lsb-release",
-      "wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg",
-      "echo \"deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main\" | sudo tee /etc/apt/sources.list.d/hashicorp.list",
-      "sudo apt-get update && sudo apt-get install -y nomad consul",
-      "sudo apt-get clean"
-    ]
-    inline_shebang = "/bin/sh -e"
-  }
-
-  provisioner "shell" {
-    inline = [
-      "sudo apt-get install -y cloud-init",
-      "sudo apt-get clean"
-    ]
-    inline_shebang = "/bin/sh -e"
-  }
-
-  provisioner "shell" {
-    inline = [
-      "echo \"policy: enabled\" | sudo tee /etc/cloud/ds-identity.cfg",
-      "echo \"datasource_list: [ ConfigDrive, NoCloud, None ]\ndatasource:\n  ConfigDrive:\n    dsmode: local\n\" | sudo tee /etc/cloud/cloud.cfg.d/00-sources.cfg"
-    ]
-    inline_shebang = "/bin/sh -e"
-  }
-
-  provisioner "shell" {
-    inline = [
-      "sudo systemctl enable cloud-init-local.service",
-      "sudo systemctl start cloud-init-local.service",
-      "sudo systemctl enable cloud-init.service",
-      "sudo systemctl start cloud-init.service",
-      "sudo systemctl enable cloud-config.service",
-      "sudo systemctl start cloud-config.service",
-      "sudo systemctl enable cloud-final.service",
-      "sudo systemctl start cloud-final.service",
-      "sudo cloud-init init --local",
-      "sudo rm -rf /var/lib/cloud"
-    ]
-    inline_shebang = "/bin/sh -e"
-  }
-
-  post-processor "compress" {
-    compression_level   = 6
-    format              = ".gz"
-    keep_input_artifact = true
-    only                = ["qemu"]
-    output              = "${local.output_directory}/${var.vm_name}.raw.gz"
-  }
-}
diff --git a/infra/start-cloud-init.sh b/infra/start-cloud-init.sh
deleted file mode 100755
index 56a81672e..000000000
--- a/infra/start-cloud-init.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-#
-# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-#
-
-(
-  cd imds || exit
-  cloud-localds my-seed.img user-data meta-data
-)
-
-qemu-img create -f qcow2 -b ./debian-12-genericcloud-amd64.qcow2 -F qcow2 boot-disk.img
-qemu-system-x86_64 -machine accel=kvm:tcg -cpu host -m 1G -drive file=boot-disk.img,if=virtio -netdev user,id=n0 -nic user,model=virtio-net-pci -drive file=./imds/my-seed.img,if=virtio,format=raw -smbios type=1,serial=ds='nocloud'
diff --git a/infra/station-config/consul.hcl b/infra/station-config/consul.hcl
deleted file mode 100644
index 60653b002..000000000
--- a/infra/station-config/consul.hcl
+++ /dev/null
@@ -1,57 +0,0 @@
-# Full configuration options can be found at https://www.consul.io/docs/agent/config
-
-datacenter = "nl-cs-001"
-data_dir   = "/localdata/consul"
-node_name  = "lcu"
-server     = true
-bootstrap  = true
-
-bind_addr = "0.0.0.0"
-
-client_addr = "127.0.0.1 10.151.255.1"
-
-advertise_addr = "10.151.255.1"
-
-# ui
-ui_config {
-  enabled = true
-}
-
-tls {
-  defaults {
-    ca_path   = "/etc/consul.d"
-    ca_file   = "/etc/consul.d/consul-agent-ca.pem"
-    cert_file = "/etc/consul.d/nl-cs-001-server-consul-0.pem"
-    key_file  = "/etc/consul.d/nl-cs-001-server-consul-0-key.pem"
-
-    verify_incoming = true
-    verify_outgoing = true
-  }
-  internal_rpc {
-    verify_server_hostname = true
-  }
-}
-
-telemetry {
-  prometheus_retention_time = "24h"
-}
-
-ports {
-  grpc     = 8502
-  grpc_tls = 8503
-}
-
-connect {
-  enabled = true
-}
-
-auto_encrypt {
-  allow_tls = true
-}
-performance {
-  raft_multiplier = 1
-}
-
-bootstrap_expect = 1
-
-encrypt = "..."
diff --git a/infra/station-config/nomad.hcl b/infra/station-config/nomad.hcl
deleted file mode 100644
index 709db4bf0..000000000
--- a/infra/station-config/nomad.hcl
+++ /dev/null
@@ -1,59 +0,0 @@
-# Full configuration options can be found at https://www.nomadproject.io/docs/configuration
-
-data_dir   = "/localdata/nomad/data"
-bind_addr  = "0.0.0.0"
-name       = "lcu"
-region     = "nl-cs-001"
-datacenter = "stat"
-
-server {
-  # license_path is required for Nomad Enterprise as of Nomad v1.1.1+
-  #license_path = "/etc/nomad.d/license.hclic"
-  enabled          = true
-  bootstrap_expect = 1
-  encrypt          = "..."
-}
-
-client {
-  enabled  = true
-  servers  = ["127.0.0.1"]
-  cni_path = "/localdata/cni/bin"
-
-  host_volume "monitoring-postgresql-data" {
-    path = "/localdata/volumes/monitoring-postgresql-data"
-  }
-
-  host_volume "monitoring-loki-data" {
-    path = "/localdata/volumes/monitoring-loki-data"
-  }
-
-  host_volume "monitoring-prometheus-data" {
-    path = "/localdata/volumes/monitoring-prometheus-data"
-  }
-}
-
-telemetry {
-  collection_interval        = "1s"
-  disable_hostname           = true
-  prometheus_metrics         = true
-  publish_allocation_metrics = true
-  publish_node_metrics       = true
-}
-
-consul {
-  address             = "127.0.0.1:8500"
-  server_service_name = "nomad"
-  client_service_name = "nomad-client"
-  auto_advertise      = true
-  server_auto_join    = true
-  client_auto_join    = true
-}
-
-plugin "docker" {
-  config {
-    volumes {
-      enabled = true
-    }
-    allow_caps = ["all"]
-  }
-}
diff --git a/sbin/dsconfig.sh b/sbin/dsconfig.sh
new file mode 100755
index 000000000..a51b6c613
--- /dev/null
+++ b/sbin/dsconfig.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# defaults
+network="station"
+
+# list of arguments expected in the input
+optstring_long="help,load,update,dump,file,network::"
+optstring="hludf:n::"
+
+options=$(getopt -l ${optstring_long} -o ${optstring} -- "$@")
+
+eval set -- "$options"
+
+
+while true; do
+  case ${1} in
+    -h|--help)
+      usage
+      exit 0
+      ;;
+    -l|--load)
+      echo "Load config"
+      action="load"
+      ;;
+    -u|--update)
+      echo "Update config"
+      action="update"
+      ;;
+    -d|--dump)
+      echo "Dump config"
+      action="dump"
+      ;;
+    -n|--network)
+      shift
+      network="$1"
+      ;;
+    --)
+    shift
+    break;;
+  esac
+  shift
+done
+
+if [ -z "$TAG" ]; then
+  TAG="latest"
+fi
+
+echo "Use docker network '$network'"
+docker_args=(run --rm  -e "TANGO_HOST=$TANGO_HOST" --network="$network" -i)
+docker_image="git.astron.nl:5000/lofar2.0/tango/dsconfig:$TAG"
+
+if [[ -n "$DNS" ]]; then
+  echo "Set docker dns to $DNS"
+  docker_args+=(--dns "$DNS")
+fi
+
+if [[ "$action" != "dump" ]]; then
+
+  if [ ${#} -eq 1 ]; then
+      file=$(realpath "${1}")
+  else
+      echo "A file name must be provided."
+      exit 1
+  fi
+
+  cmd_args=(--write)
+  docker_args+=(-v "${file}":/tmp/dsconfig-update-settings.json)
+
+  if [[ "$action" == "update" ]]; then
+    "${LOFAR20_DIR}/sbin/dsconfig.sh" --dump > "${LOFAR20_DIR}"/CDB/dump_"$(date "+%Y.%m.%d_%H.%M.%S")".json
+
+    # update settings, Do not change -i into -it this will break integration tests in gitlab ci!
+    docker "${docker_args[@]}" "$docker_image" \
+      sh -c '/manage_object_properties.py --write < /tmp/dsconfig-update-settings.json'
+    cmd_args+=(--update)
+  fi
+
+
+  # update settings, Do not change -i into -it this will break integration tests in gitlab ci!
+  docker "${docker_args[@]}" "$docker_image" \
+    json2tango "${cmd_args[@]}" /tmp/dsconfig-update-settings.json
+
+  # somehow json2tango does not return 0 on success
+  exit 0
+fi
+
+# writes the JSON dump to stdout, Do not change -i into -it incompatible with gitlab ci!
+docker "${docker_args[@]}" "$docker_image" \
+  bash -c '
+  python -m dsconfig.dump > /tmp/dsconfig-configdb-dump.json
+  /manage_object_properties.py -r > /tmp/dsconfig-objectdb-dump.json
+  /merge_json.py /tmp/dsconfig-objectdb-dump.json /tmp/dsconfig-configdb-dump.json'
diff --git a/sbin/load_ConfigDb.sh b/sbin/load_ConfigDb.sh
index 707eee508..cc7c8b254 100755
--- a/sbin/load_ConfigDb.sh
+++ b/sbin/load_ConfigDb.sh
@@ -1,20 +1,7 @@
 #!/bin/bash
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
+#
 
-if [ ${#} -eq 1 ]; then
-    file=${1}
-else
-    echo "A file name must be provided."
-    exit 1
-fi
-
-# copy file into container to read it from container, as the file's location
-# in the container won't be the same as on the host.
-docker cp "${file}" dsconfig:/tmp/dsconfig-update-settings.json || exit 1
-
-# update settings, Do not change -i into -it this will break integration tests in gitlab ci!
-docker exec -i dsconfig json2tango --write /tmp/dsconfig-update-settings.json
-
-# somehow json2tango does not return 0 on success
-exit 0
+"${LOFAR20_DIR}/sbin/dsconfig.sh" --load "${1}"
diff --git a/sbin/prepare_dev_env.sh b/sbin/prepare_dev_env.sh
new file mode 100755
index 000000000..080ceb472
--- /dev/null
+++ b/sbin/prepare_dev_env.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# Url to the jumppad download location
+jumppad_download="https://git.astron.nl/lofar2.0/tango/-/package_files/37347/download"
+
+if [ -z "$LOFAR20_DIR" ]; then
+    # We assume we aren't in the PATH, so we can derive our path.
+    # We need our parent directory.
+    LOFAR20_DIR_RELATIVE=$(dirname "$0")/..
+
+    # As an absolute path
+    LOFAR20_DIR=$(readlink -f "${LOFAR20_DIR_RELATIVE}")
+fi
+
+echo 'Check if jumppad is installed'
+mkdir -p ./.bin
+bin_dir=$(realpath ./.bin)
+export PATH="$PATH:$bin_dir"
+
+if ! [ -x "$(command -v jumppad)" ]; then
+  echo 'jumppad is not installed, installing'
+  mkdir -p ./.bin
+  wget "${jumppad_download}" -O ./.bin/jumppad
+  chmod +x ./.bin/jumppad
+fi
+
+make -C infra/jobs/station DIR_OUT="$( realpath "infra/dev/jobs/station")" render
+
+docker_volume="dev_nomad_station"
+
+# list of arguments expected in the input
+optstring_long="help,volume::"
+optstring="hv::"
+
+options=$(getopt -l ${optstring_long} -o ${optstring} -- "$@")
+
+eval set -- "$options"
+
+while true; do
+  case ${1} in
+    -h|--help)
+      usage
+      exit 0
+      ;;
+    -v|--volume)
+      shift
+      docker_volume="$1"
+      ;;
+    --)
+    shift
+    break;;
+  esac
+  shift
+done
+
+if [ "$(docker volume list | grep -c "$docker_volume")" = "0" ]; then
+  echo "Create volume $docker_volume"
+  docker volume create "$docker_volume"
+fi
+
+docker pull -q bash
+docker run --rm -i -v "$docker_volume":/mnt bash bash  <<- EOM
+  mkdir -p /mnt/volumes/tango-database
+  mkdir -p /mnt/volumes/monitoring-postgresql-data
+  mkdir -p /mnt/volumes/monitoring-loki-data
+  mkdir -p /mnt/volumes/monitoring-prometheus-data
+  mkdir -p /mnt/volumes/object-storage-data
+  mkdir -p /mnt/volumes/jupyter-notebooks
+  chmod 0777 -R /mnt/volumes
+EOM
diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh
index c6ea4a4d4..621d69b2f 100755
--- a/sbin/run_integration_test.sh
+++ b/sbin/run_integration_test.sh
@@ -4,6 +4,9 @@
 # SPDX-License-Identifier: Apache-2.0
 #
 
+export HOSTNAME=192.168.123.1
+export DNS=192.168.123.100
+
 # Usage function explains how parameters are parsed
 function usage {
     echo "./$(basename "$0")
@@ -26,24 +29,24 @@ function integration_test {
   IFS=" " read -r -a configs <<< "${3}"
   for config in "${configs[@]}"; do
     echo "Updating config ${config} ..."
-    bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${config}"
+    bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --update "${config}"
   done
   if [ -n "${2+x}" ]; then
     # shellcheck disable=SC2145
     echo "make restart ${restarts[@]} ..."
-    make restart "${restarts[@]}"
+    DNS="$DNS" make restart "${restarts[@]}"
     # shellcheck disable=SC2145
     echo "make await ${restarts[@]} ..."
     make await "${restarts[@]}"
   fi
 
   echo "make integration ${1} ..."
-  make integration "${1}"
+  DNS="$DNS" make integration "${1}"
 }
 
 # list of arguments expected in the input
-optstring_long="help,no-build,skip-tests"
-optstring="hnb"
+optstring_long="help,no-build,skip-tests,preserve,save-logs"
+optstring="h"
 
 options=$(getopt -l ${optstring_long} -o ${optstring} -- "$@")
 
@@ -64,6 +67,15 @@ while true; do
       echo "Only setup and configure environment don't run any tests"
       export no_tests=1
       ;;
+    --preserve)
+      echo "Preserve test environment"
+      export preserve=1
+      ;;
+    --save-logs)
+      echo "Save logs after execution"
+      export save_logs=1
+      ;;
+
     --)
     shift
     break;;
@@ -82,23 +94,73 @@ fi
 
 export TANGO_SKIP_BUILD=1
 
-cd "$LOFAR20_DIR/docker-compose" || exit 1
+if [ -z "$TAG" ]; then
+  export TAG="latest"
+fi
 
-# Start the database server first, `-z ${y+x}` is the inverse of `-n ${y}`
+# Build dsconfig first, `-z ${y+x}` is the inverse of `-n ${y}`
 if [ -z "${no_build+x}" ]; then
     rm -rf "${LOFAR20_DIR}/tangostationcontrol/dist"
     cd "${LOFAR20_DIR}/tangostationcontrol" || exit 1
     tox -e build
     cd "$LOFAR20_DIR/docker-compose" || exit 1
-    make build databaseds dsconfig
+    make build dsconfig
+    cd "$LOFAR20_DIR"
 fi
 
-make start databaseds dsconfig
+docker network rm station || true
+
+# prepare a docker volume for nomad
+tmp_volume="test_$(hexdump -n 16 -v -e '/1 "%02X"' /dev/urandom)"
+
+function cleanup {
+  cd "$LOFAR20_DIR"
+  if [ -n "${save_logs}" ]; then
+    mkdir -p log
+    for container in $(docker ps -a --format "{{.Names}}")
+    do
+      echo "Saving log for container $container"
+      docker logs "${container}" >& "log/${container}.log"
+    done
+    bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --dump >& log/dump_ConfigDb.log
+  fi
+  if [ -z "${preserve}" ]; then
+    make stop > /dev/null 2>&1
+    HOME="$JUMPPAD_HOME" jumppad down infra/dev
+    docker volume rm "$tmp_volume" || true
+  fi
+}
+
+trap cleanup EXIT
 
-# Give dsconfig and databaseds time to start
-sleep 5 # dsconfig container must be up and running...
-# shellcheck disable=SC2016
-echo '/usr/local/bin/wait-for-it.sh ${TANGO_HOST} --strict --timeout=300 -- true' | make run dsconfig bash -
+cd "$LOFAR20_DIR" || exit 1
+
+source "${LOFAR20_DIR}"/sbin/prepare_dev_env.sh --volume="$tmp_volume"
+
+if [ -z "$JUMPPAD_HOME" ]; then
+  JUMPPAD_HOME="$HOME"
+fi
+
+rm -rf "$JUMPPAD_HOME/.jumppad/"
+
+HOME="$JUMPPAD_HOME" jumppad up --var="host_volume=$tmp_volume" infra/dev/main.hcl || true
+
+echo -n "Waiting for tango service to become available .."
+until [[ $(dig @127.0.0.1 -p 8600 tango.service.consul +short) ]]; do
+  sleep 2
+  echo -n "."
+done
+echo ". [ok]"
+
+tango_port=$(dig @127.0.0.1 -p 8600 tango.service.consul SRV +short  | awk '{printf "%s",$3}')
+tango_host=$(dig @127.0.0.1 -p 8600 tango.service.consul +short)
+
+export TANGO_HOST="$tango_host:$tango_port"
+
+
+echo "Using tango host $TANGO_HOST"
+
+cd "$LOFAR20_DIR/docker-compose" || exit 1
 
 # Devices list is used to explitly word split when supplied to commands, must
 # disable shellcheck SC2086 for each case.
@@ -111,36 +173,38 @@ SIMULATORS=(sdptr-sim recvh-sim recvl-sim unb2-sim apsct-sim apspu-sim ccd-sim e
 # jupyter is physically large > 2.5gb and overlayfs is really slow.
 
 [ -n "${no_build}" ] || make build "${SIMULATORS[@]}"
-[ -n "${no_build}" ] || make build logstash integration-test http-json-schemas
+[ -n "${no_build}" ] || make build integration-test
 
 # Start and stop sequence
-make stop http-json-schemas
-make stop object-storage init-object-storage
+make stop init-object-storage
 make stop "${DEVICES[@]}" "${SIMULATORS[@]}"
 make stop device-docker # this one does not test well in docker-in-docker
-make stop logstash
 
-make start logstash http-json-schemas object-storage init-object-storage
+DNS="$DNS" make start init-object-storage
 
 # Update the dsconfig
-# Do not remove `bash`, otherwise statement ignored by gitlab ci shell!
-bash "${LOFAR20_DIR}"/sbin/load_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/common.json
-bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/l0.json
-bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/l1.json
-bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/lba.json
-bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/h0.json
-bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/hba_core.json
-bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/cs001.json
-bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/testenv_cs001.json
+docker pull -q "git.astron.nl:5000/lofar2.0/tango/dsconfig:$TAG" || docker pull -q "git.astron.nl:5000/lofar2.0/tango/dsconfig:latest" || true
+if [ -n "$(docker image inspect \""git.astron.nl:5000/lofar2.0/tango/dsconfig":"${TAG}"\")" ]; then
+  docker tag "git.astron.nl:5000/lofar2.0/tango/dsconfig:latest" "git.astron.nl:5000/lofar2.0/tango/dsconfig:$TAG"
+fi
+
+bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --load "${LOFAR20_DIR}"/CDB/stations/common.json
+bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --update "${LOFAR20_DIR}"/CDB/stations/l0.json
+bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --update "${LOFAR20_DIR}"/CDB/stations/l1.json
+bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --update "${LOFAR20_DIR}"/CDB/stations/lba.json
+bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --update "${LOFAR20_DIR}"/CDB/stations/h0.json
+bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --update "${LOFAR20_DIR}"/CDB/stations/hba_core.json
+bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --update "${LOFAR20_DIR}"/CDB/stations/cs001.json
+bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --update "${LOFAR20_DIR}"/CDB/stations/testenv_cs001.json
 
 cd "$LOFAR20_DIR/docker-compose" || exit 1
-make start "${SIMULATORS[@]}"
+DNS="$DNS" make start "${SIMULATORS[@]}"
 
 # Give the simulators time to start
 sleep 5
 
 # shellcheck disable=SC2086
-make start "${DEVICES[@]}"
+DNS="$DNS" make start "${DEVICES[@]}"
 
 # Wait for devices to restart
 make await "${DEVICES[@]}"
@@ -151,7 +215,7 @@ fi
 
 # Start the integration test
 cd "$LOFAR20_DIR/docker-compose" || exit 1
-make up integration-test
+DNS="$DNS" make up integration-test
 
 integration_test default
 
@@ -161,4 +225,4 @@ integration_test digitalbeam_performance "device-sdpfirmware device-sdp device-r
 
 integration_test configuration "device-configuration"
 
-make restart "${DEVICES[@]}"
+DNS="$DNS" make restart "${DEVICES[@]}"
diff --git a/sbin/run_service_test.sh b/sbin/run_service_test.sh
old mode 100644
new mode 100755
index 1a357205e..d0718a84b
--- a/sbin/run_service_test.sh
+++ b/sbin/run_service_test.sh
@@ -13,10 +13,39 @@ if [ -z "$LOFAR20_DIR" ]; then
     LOFAR20_DIR=$(readlink -f "${LOFAR20_DIR_RELATIVE}")
 fi
 
-export TANGO_SKIP_BUILD=1
-export NO_BASE=1
+docker network rm station || true
 
-cd "$LOFAR20_DIR/docker-compose" || exit 1
+# prepare a docker volume for nomad
+tmp_volume="test_$(hexdump -n 16 -v -e '/1 "%02X"' /dev/urandom)"
 
-make start grafana loki logstash prometheus
-make await grafana loki logstash prometheus
+function cleanup {
+  cd "$LOFAR20_DIR"
+  if [ -n "${save_logs}" ]; then
+    mkdir -p log
+    for container in $(docker ps -a --format "{{.Names}}")
+    do
+      echo "Saving log for container $container"
+      docker logs "${container}" >& "log/${container}.log"
+    done
+    bash "${LOFAR20_DIR}"/sbin/dsconfig.sh --dump >& log/dump_ConfigDb.log
+  fi
+  if [ -z "${preserve}" ]; then
+    make stop > /dev/null 2>&1
+    HOME="$JUMPPAD_HOME" jumppad down
+    docker volume rm "$tmp_volume" || true
+  fi
+}
+
+trap cleanup EXIT
+
+cd "$LOFAR20_DIR" || exit 1
+
+source "${LOFAR20_DIR}"/sbin/prepare_dev_env.sh --volume="$tmp_volume"
+
+if [ -z "$JUMPPAD_HOME" ]; then
+  JUMPPAD_HOME="$HOME"
+fi
+
+rm -rf "$JUMPPAD_HOME/.jumppad/"
+
+HOME="$JUMPPAD_HOME" jumppad up --var="host_volume=$tmp_volume" infra/dev/services.hcl || true
diff --git a/sbin/tag_and_push_docker_image.sh b/sbin/tag_and_push_docker_image.sh
index 63daae8ef..3fc5527eb 100755
--- a/sbin/tag_and_push_docker_image.sh
+++ b/sbin/tag_and_push_docker_image.sh
@@ -67,9 +67,9 @@ REMOTE_IMAGES=(
 # integration tests.
 # TODO(Corne): Have this list generated from the .yml files
 LOCAL_IMAGES=(
-  "logstash logstash y"
   "lofar-device-base lofar-device-base y"
-  "http-json-schemas http-json-schemas y"
+
+  "dsconfig dsconfig n"
 
   "ec-sim ec-sim y"
 
diff --git a/sbin/update_ConfigDb.sh b/sbin/update_ConfigDb.sh
index 4e3fc4b21..dd46a81eb 100755
--- a/sbin/update_ConfigDb.sh
+++ b/sbin/update_ConfigDb.sh
@@ -1,31 +1,7 @@
 #!/bin/bash
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
+#
 
-if [ ${#} -eq 1 ]; then
-    file=${1}
-else
-    echo "A file name must be provided."
-    exit 1
-fi
-
-# dump a copy of the database before updating
-# Do not change -i into -it this will break integration tests in gitlab ci!
-docker exec -i dsconfig bash -c '
-  python -m dsconfig.dump > /tmp/dsconfig-configdb-dump.json
-  /manage_object_properties.py -r > /tmp/dsconfig-objectdb-dump.json
-  /merge_json.py /tmp/dsconfig-objectdb-dump.json /tmp/dsconfig-configdb-dump.json' \
-  > "${LOFAR20_DIR}"/CDB/dump_"$(date "+%Y.%m.%d_%H.%M.%S")".json
-
-# copy file into container to read it from container, as the file's location
-# in the container won't be the same as on the host.
-docker cp "${file}" dsconfig:/tmp/dsconfig-update-settings.json || exit 1
-
-# update settings, Do not change -i into -it this will break integration tests in gitlab ci!
-docker exec -i dsconfig /manage_object_properties.py --write < "${file}"
-
-# update settings, Do not change -i into -it this will break integration tests in gitlab ci!
-docker exec -i dsconfig json2tango --write --update /tmp/dsconfig-update-settings.json
-
-# somehow json2tango does not return 0 on success
-exit 0
+"${LOFAR20_DIR}/sbin/dsconfig.sh" --update "${1}"
diff --git a/setup.sh b/setup.sh
index ee556bb84..ba9846ce9 100755
--- a/setup.sh
+++ b/setup.sh
@@ -1,11 +1,17 @@
 #! /usr/bin/env bash
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
+#
 
 # Set up station control development
 
 # This file's directory is used to determine the station control directory
 # location.
+if [ -z ${BASH_SOURCE} ]; then
+  BASH_SOURCE=${(%):-%x}
+fi
+
 ABSOLUTE_PATH=$(realpath $(dirname ${BASH_SOURCE}))
 export LOFAR20_DIR=${1:-${ABSOLUTE_PATH}}
 
@@ -15,15 +21,22 @@ if [ ! -f "${LOFAR20_DIR}/.git/hooks/post-checkout" ]; then
   alias git="cp ${LOFAR20_DIR}/bin/update_submodules.sh ${LOFAR20_DIR}/.git/hooks/post-checkout; cp ${LOFAR20_DIR}/bin/update_submodules.sh ${LOFAR20_DIR}/.git/hooks/post-merge; unalias git; git"
 fi
 
-# This needs to be modified for a development environment.
-# In case you run multiple Docker networks on the same host in parallel,
-# you need to specify a unique network name for each of them.
-export NETWORK_MODE=tangonet
+# This is the docker network used within the integration tests/development environment
+export NETWORK_MODE=station
 
-# It is assumed that the Tango host, the computer that runs the TangoDB,
+# It checks if Tango is running within nomad, by trying to query the service from consul.
+# If consul is not available it is assumed that the Tango host, the computer that runs the TangoDB,
 # is this host. If this is not true, then modify to the Tango host's FQDN and
 # port. Example:  export TANGO_HOST=station-xk25.astron.nl:10000
-export TANGO_HOST=$(hostname):10000
+if dig @127.0.0.1 -p 8600 tango.service.consul +short > /dev/null; then
+  TANGO_PORT=$(dig @127.0.0.1 -p 8600 tango.service.consul SRV +short  | awk '{printf "%s",$3}')
+  TANGO_HOST=$(dig @127.0.0.1 -p 8600 tango.service.consul +short)
+  export TANGO_HOST="$TANGO_HOST:$TANGO_PORT"
+else
+  export TANGO_HOST=$(hostname):10000
+fi
+
+echo "Using tango host $TANGO_HOST"
 
 # Configure to install station control in lofar-device-base image
 # TODO(L2SS-520): Extend to support debug and expose as property in devices
diff --git a/tangostationcontrol/MANIFEST.in b/tangostationcontrol/MANIFEST.in
index 4f8b2d03b..0f4ae53bb 100644
--- a/tangostationcontrol/MANIFEST.in
+++ b/tangostationcontrol/MANIFEST.in
@@ -2,6 +2,8 @@ include LICENSE
 include README.md
 include VERSION
 
+include tangostationcontrol/configuration/schemas/*.json
 recursive-include docs *
 recursive-exclude tangostationcontrol/test *
 recursive-exclude tangostationcontrol/integration_test *
+
diff --git a/tangostationcontrol/VERSION b/tangostationcontrol/VERSION
index 215740905..ca222b7cf 100644
--- a/tangostationcontrol/VERSION
+++ b/tangostationcontrol/VERSION
@@ -1 +1 @@
-0.22.0
+0.23.0
diff --git a/tangostationcontrol/docs/source/configure_station.rst b/tangostationcontrol/docs/source/configure_station.rst
index fa45b4c34..4c08ada9b 100644
--- a/tangostationcontrol/docs/source/configure_station.rst
+++ b/tangostationcontrol/docs/source/configure_station.rst
@@ -5,7 +5,7 @@ The software will need to be told various aspects of your station configuration,
 
 Stock configurations are provided for several stations, as well as using simulators to simulate the station's interface (which is the default after bootstrapping a station). These are provided in the ``CDB/stations/`` directory, and can be loaded using for example::
 
-    sbin/update_ConfigDb.sh CDB/stations/LTS_ConfigDb.json
+    sbin/dsconfig.sh --update CDB/stations/LTS_ConfigDb.json
 
 The following sections describe the settings that are station dependent, and thus must or can be set.
 
diff --git a/tangostationcontrol/docs/source/devices/configure.rst b/tangostationcontrol/docs/source/devices/configure.rst
index 0ed05a0fb..c8573f029 100644
--- a/tangostationcontrol/docs/source/devices/configure.rst
+++ b/tangostationcontrol/docs/source/devices/configure.rst
@@ -39,10 +39,10 @@ Command-line interaction
 
 The content of the TangoDB can be dumped from the command line using::
 
-  bin/dump_ConfigDb.sh > tangodb-dump.json
+  sbin/dsconfig.sh --dump > tangodb-dump.json
 
 and changes can be applied using::
 
-  bin/update_ConfigDb.sh changeset.json
+  sbin/dsconfig.sh --update changeset.json
 
 .. note:: The ``dsconfig`` docker container needs to be running for these commands to work.
diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt
index 93ff226d7..a875eb4b0 100644
--- a/tangostationcontrol/requirements.txt
+++ b/tangostationcontrol/requirements.txt
@@ -10,12 +10,14 @@ psycopg2-binary >= 2.9.2 # LGPL
 pyasn1 == 0.4.8 # BSD, pinned because https://github.com/pyasn1/pyasn1/issues/28
 pysnmp >= 0.1.7 # BSD
 h5py >= 3.1.0 # BSD
-jsonschema >= 4.18 # MIT
+jsonschema > 4.18 # MIT
+referencing > 0.30 # MIT
 docker >= 5.0.3 # Apache 2
-python-logstash-async >= 2.5.0, != 2.7.1 # MIT - https://github.com/eht16/python-logstash-async/issues/88
 python-casacore >= 3.3.1 # LGPLv3
 etrs-itrs@git+https://github.com/brentjens/etrs-itrs # Apache 2
 lofarantpos >= 0.5.0 # Apache 2
 python-geohash >= 0.8.5 # Apache 2 / MIT
 attributewrapper@git+https://git.astron.nl/lofar2.0/attributewrapper # Apache2
 minio >= 7.1.14 # Apache 2
+prometheus-client # Apache 2
+dnspython # ISC
diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg
index be133876d..90a78e2f9 100644
--- a/tangostationcontrol/setup.cfg
+++ b/tangostationcontrol/setup.cfg
@@ -31,34 +31,7 @@ where = .
 
 [options.entry_points]
 console_scripts =
-    l2ss-aps = tangostationcontrol.devices.aps:main
-    l2ss-apsct = tangostationcontrol.devices.apsct:main
-    l2ss-apspu = tangostationcontrol.devices.apspu:main
-    l2ss-ccd = tangostationcontrol.devices.ccd:main
-    l2ss-ec = tangostationcontrol.devices.ec:main
-    l2ss-psoc = tangostationcontrol.devices.psoc:main
-    l2ss-pcon = tangostationcontrol.devices.pcon:main
-    l2ss-tilebeam = tangostationcontrol.devices.tilebeam:main
-    l2ss-beamlet = tangostationcontrol.devices.sdp.beamlet:main
-    l2ss-digitalbeam = tangostationcontrol.devices.sdp.digitalbeam:main
-    l2ss-afh = tangostationcontrol.devices.antennafield.afh:main
-    l2ss-afl = tangostationcontrol.devices.antennafield.afl:main
-    l2ss-boot = tangostationcontrol.devices.boot:main
-    l2ss-station-manager = tangostationcontrol.devices.station_manager:main
-    l2ss-docker = tangostationcontrol.devices.docker:main
-    l2ss-observation = tangostationcontrol.devices.observation:main
-    l2ss-observationcontrol = tangostationcontrol.devices.observation_control:main
-    l2ss-recvh = tangostationcontrol.devices.recv.recvh:main
-    l2ss-recvl = tangostationcontrol.devices.recv.recvl:main
-    l2ss-sdpfirmware = tangostationcontrol.devices.sdp.firmware:main
-    l2ss-sdp = tangostationcontrol.devices.sdp.sdp:main
-    l2ss-bst = tangostationcontrol.devices.sdp.bst:main
-    l2ss-sst = tangostationcontrol.devices.sdp.sst:main
-    l2ss-unb2 = tangostationcontrol.devices.unb2:main
-    l2ss-xst = tangostationcontrol.devices.sdp.xst:main
-    l2ss-temperaturemanager = tangostationcontrol.devices.temperature_manager:main
-    l2ss-configuration = tangostationcontrol.devices.configuration:main
-    l2ss-calibration = tangostationcontrol.devices.calibration:main
+    l2ss-ds = tangostationcontrol.device_server:main
     l2ss-analyse-dsconfig-hierarchies = tangostationcontrol.toolkit.analyse_dsconfig_hierarchies:main
 
 # The following entry points should eventually be removed / replaced
diff --git a/tangostationcontrol/tangostationcontrol/beam/managers/_base.py b/tangostationcontrol/tangostationcontrol/beam/managers/_base.py
index b8ebe85ba..225f130a7 100644
--- a/tangostationcontrol/tangostationcontrol/beam/managers/_base.py
+++ b/tangostationcontrol/tangostationcontrol/beam/managers/_base.py
@@ -9,7 +9,7 @@ from statistics import median
 
 import numpy
 
-from tangostationcontrol.devices.device_decorators import (
+from tangostationcontrol.common.device_decorators import (
     TimeIt,
 )
 
diff --git a/tangostationcontrol/tangostationcontrol/beam/managers/_digitalbeam.py b/tangostationcontrol/tangostationcontrol/beam/managers/_digitalbeam.py
index 16e38a6f2..83fe11f3a 100644
--- a/tangostationcontrol/tangostationcontrol/beam/managers/_digitalbeam.py
+++ b/tangostationcontrol/tangostationcontrol/beam/managers/_digitalbeam.py
@@ -11,7 +11,7 @@ from tangostationcontrol.common.constants import (
     N_pn,
     A_pn,
 )
-from tangostationcontrol.devices.device_decorators import (
+from tangostationcontrol.common.device_decorators import (
     TimeIt,
 )
 from ._base import AbstractBeamManager
diff --git a/tangostationcontrol/tangostationcontrol/beam/managers/_tilebeam.py b/tangostationcontrol/tangostationcontrol/beam/managers/_tilebeam.py
index 22b15a2ba..0200c9cfa 100644
--- a/tangostationcontrol/tangostationcontrol/beam/managers/_tilebeam.py
+++ b/tangostationcontrol/tangostationcontrol/beam/managers/_tilebeam.py
@@ -11,7 +11,7 @@ from tangostationcontrol.common.constants import (
     MAX_ANTENNA,
 )
 from tangostationcontrol.common.proxy import create_device_proxy
-from tangostationcontrol.devices.device_decorators import (
+from tangostationcontrol.common.device_decorators import (
     TimeIt,
 )
 from ._base import AbstractBeamManager
diff --git a/tangostationcontrol/tangostationcontrol/common/calibration.py b/tangostationcontrol/tangostationcontrol/common/calibration.py
index 7128a36aa..99531481d 100644
--- a/tangostationcontrol/tangostationcontrol/common/calibration.py
+++ b/tangostationcontrol/tangostationcontrol/common/calibration.py
@@ -11,6 +11,7 @@ from lofar_station_client.file_access import member, attribute, read_hdf5
 from minio import Minio
 from tango import DeviceProxy
 
+from tangostationcontrol.common import consul
 from tangostationcontrol.common.constants import (
     N_subbands,
     N_pol,
@@ -70,8 +71,10 @@ class CalibrationManager:
 
         logger.info(f"Derived {self.prefix=} {self.bucket_name=} from {self._url=}")
 
+        service: consul.Service = next(consul.lookup_service(result.netloc))
+        logger.info(f"Use service {service.host}:{service.port}")
         self._storage = Minio(
-            result.netloc,
+            f"{service.addr}:{service.port}",
             access_key=os.getenv("MINIO_ROOT_USER"),
             secret_key=os.getenv("MINIO_ROOT_PASSWORD"),
             secure=result.scheme == "https",
diff --git a/tangostationcontrol/tangostationcontrol/common/configuration.py b/tangostationcontrol/tangostationcontrol/common/configuration.py
index 7682daff0..09be2024e 100644
--- a/tangostationcontrol/tangostationcontrol/common/configuration.py
+++ b/tangostationcontrol/tangostationcontrol/common/configuration.py
@@ -1,19 +1,15 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 import json
 import logging
 from itertools import islice
-from jsonschema import Draft7Validator, FormatChecker, ValidationError
 
+from jsonschema import Draft7Validator, FormatChecker, ValidationError
 from tango import DeviceProxy, Database, DevFailed, DbDevInfo
 
 from tangostationcontrol.common.proxy import create_device_proxy
-from tangostationcontrol.configuration.configuration_base import (
-    _ConfigurationBase,
-    RetryHttpRefResolver,
-)
-
+from tangostationcontrol.configuration import REGISTRY
 
 logger = logging.getLogger()
 
@@ -54,13 +50,10 @@ class StationConfiguration:
     def get_validator(cls):
         """Retrieve the JSON validator from Schemas container"""
         name = cls.__name__
-        url = f"{_ConfigurationBase.BASE_URL}{_ConfigurationBase._class_to_url(name)}.json"
-        resolver = RetryHttpRefResolver(
-            base_uri=_ConfigurationBase.BASE_URL, referrer=url
-        )
-        _, resolved = resolver.resolve(url)
         return Draft7Validator(
-            resolved, format_checker=FormatChecker(), resolver=resolver
+            REGISTRY["station-configuration"].contents,
+            format_checker=FormatChecker(),
+            registry=REGISTRY,
         )
 
     #
diff --git a/tangostationcontrol/tangostationcontrol/common/consul.py b/tangostationcontrol/tangostationcontrol/common/consul.py
new file mode 100644
index 000000000..7c2f9f980
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/common/consul.py
@@ -0,0 +1,42 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+import collections
+import logging
+from typing import Iterator
+
+from dns import resolver
+
+logger = logging.getLogger()
+
+Service = collections.namedtuple("Service", ["host", "addr", "port"])
+
+
+class ServiceLookupException(Exception):
+    """Exception that is raised when the service lookup failed."""
+
+    def __str__(self) -> str:
+        return "Service lookup failed for %s: %s" % self.args[0:2]
+
+
+def lookup_service(name: str) -> Iterator[Service]:
+    fqdn = f"{name}.service.consul"
+
+    try:
+        result: resolver.Answer = resolver.resolve(fqdn, "SRV")
+    except (
+        resolver.NoAnswer,
+        resolver.NoNameservers,
+        resolver.NotAbsolute,
+        resolver.NoRootSOA,
+        resolver.NXDOMAIN,
+    ) as error:
+        logger.error("Querying SRV %s failed: %r", fqdn, error)
+        raise ServiceLookupException(name, error)
+
+    for resource in result:
+        host = resource.target.to_text(omit_final_dot=True)
+        yield Service(host, host, resource.port)
+
+
+if __name__ == "__main__":
+    lookup_service("s3")
diff --git a/tangostationcontrol/tangostationcontrol/devices/device_decorators.py b/tangostationcontrol/tangostationcontrol/common/device_decorators.py
similarity index 100%
rename from tangostationcontrol/tangostationcontrol/devices/device_decorators.py
rename to tangostationcontrol/tangostationcontrol/common/device_decorators.py
diff --git a/tangostationcontrol/tangostationcontrol/common/lofar_logging.py b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py
index 3f405b6fe..7223b70ee 100644
--- a/tangostationcontrol/tangostationcontrol/common/lofar_logging.py
+++ b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 import logging
 import socket
@@ -7,8 +7,8 @@ import time
 import traceback
 from functools import wraps
 
-from tango.server import Device
 from tango import DevFailed
+from tango.server import Device
 
 from tangostationcontrol import __version__ as version
 
@@ -179,37 +179,6 @@ def configure_logger(logger: logging.Logger = None, log_extra=None, debug=False)
 
     logger.addHandler(handler)
 
-    # If configuring for debug; exit early
-    if debug:
-        return logger
-
-    # Log to Logstash-Loki
-    try:
-        from logstash_async.handler import (
-            AsynchronousLogstashHandler,
-            LogstashFormatter,
-        )
-
-        # log to the tcp_input of logstash in our logstash-loki container
-        handler = AsynchronousLogstashHandler(
-            "logstash", 5959, database_path="/tmp/lofar_pending_log_messages.db"
-        )
-
-        # configure log messages
-        formatter = LogstashFormatter(extra=log_extra, tags=[])
-        handler.setFormatter(formatter)
-        handler.addFilter(LogSuppressErrorSpam())
-        handler.addFilter(LogAnnotator())
-
-        # install the handler
-        logger.addHandler(handler)
-    except ImportError:
-        logger.exception(
-            "Cannot forward logs to Logstash-Loki: logstash_async module not found."
-        )
-    except Exception:
-        logger.exception("Cannot forward logs to Logstash-Loki.")
-
     # Don't log to Tango to reduce log spam
     """
     # Log to Tango
diff --git a/tangostationcontrol/tangostationcontrol/configuration/__init__.py b/tangostationcontrol/tangostationcontrol/configuration/__init__.py
index 735a5ee25..d5b9dc9bd 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/__init__.py
+++ b/tangostationcontrol/tangostationcontrol/configuration/__init__.py
@@ -1,16 +1,11 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
+from ._schemas import REGISTRY
+from .dithering import Dithering
+from .hba import HBA
 from .observation_settings import ObservationSettings
 from .pointing import Pointing
 from .sap import Sap
-from .hba import HBA
-from .dithering import Dithering
 
-__all__ = [
-    "ObservationSettings",
-    "Pointing",
-    "Sap",
-    "HBA",
-    "Dithering",
-]
+__all__ = ["ObservationSettings", "Pointing", "Sap", "HBA", "Dithering", "REGISTRY"]
diff --git a/tangostationcontrol/tangostationcontrol/configuration/_schemas.py b/tangostationcontrol/tangostationcontrol/configuration/_schemas.py
new file mode 100644
index 000000000..6a6678e08
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/configuration/_schemas.py
@@ -0,0 +1,24 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
+import json
+
+try:
+    from importlib.resources import files
+except ImportError:
+    from importlib_resources import files  # type: ignore
+
+from referencing import Resource
+from referencing import Registry as _Registry
+from referencing.jsonschema import SchemaRegistry as _SchemaRegistry
+
+
+def _schemas():
+    for schema in files(__package__).joinpath("schemas").iterdir():
+        contents = json.loads(schema.read_text(encoding="utf-8"))
+        yield Resource.from_contents(contents)
+
+
+#: A `referencing.jsonschema.SchemaRegistry` containing all of the official
+#: meta-schemas and vocabularies.
+REGISTRY: _SchemaRegistry = (_schemas() @ _Registry()).crawl()
diff --git a/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py b/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py
index 3b879ff2a..ea8769d55 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py
+++ b/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py
@@ -1,45 +1,20 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 import json
 import re
-import time
 from abc import ABC, abstractmethod
 from typing import TypeVar, Type
 
 import jsonschema
-import requests
 from jsonschema import Draft7Validator, FormatChecker, ValidationError
-from jsonschema.validators import RefResolver
+
+from tangostationcontrol.configuration import REGISTRY
 from tangostationcontrol.configuration._json_parser import _from_json_hook_t
 
 T = TypeVar("T")
 
 
-def _fetch_url(url):
-    attempt_nr = 1
-    while True:
-        try:
-            response = requests.get(url)
-            response.raise_for_status()
-            return response.json()
-        except requests.exceptions.RequestException as e:
-            time.sleep(2)  # retry after a little sleep
-            if attempt_nr >= 5:
-                raise e
-            else:
-                attempt_nr += 1
-
-
-class RetryHttpRefResolver(RefResolver):
-    def resolve_remote(self, uri):
-        result = _fetch_url(uri)
-
-        if self.cache_remote:
-            self.store[uri] = result
-        return result
-
-
 def _is_object(_, instance):
     return isinstance(instance, dict) or issubclass(type(instance), _ConfigurationBase)
 
@@ -53,8 +28,6 @@ jsonschema.validators.Draft7Validator.TYPE_CHECKER = (
 
 
 class _ConfigurationBase(ABC):
-    BASE_URL = "http://http-json-schemas/"
-
     @staticmethod
     def _class_to_url(cls_name):
         cls_name = cls_name.replace("HBA", "hba")
@@ -63,14 +36,12 @@ class _ConfigurationBase(ABC):
 
     @classmethod
     def get_validator(cls):
+        """Retrieve the JSON validator from Schemas container"""
         name = cls.__name__
-        url = f"{_ConfigurationBase.BASE_URL}{_ConfigurationBase._class_to_url(name)}.json"
-        resolver = RetryHttpRefResolver(
-            base_uri=_ConfigurationBase.BASE_URL, referrer=url
-        )
-        _, resolved = resolver.resolve(url)
         return Draft7Validator(
-            resolved, format_checker=FormatChecker(), resolver=resolver
+            REGISTRY[_ConfigurationBase._class_to_url(name)].contents,
+            format_checker=FormatChecker(),
+            registry=REGISTRY,
         )
 
     @abstractmethod
diff --git a/docker-compose/http-json-schemas/definitions/dithering.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/dithering.json
similarity index 96%
rename from docker-compose/http-json-schemas/definitions/dithering.json
rename to tangostationcontrol/tangostationcontrol/configuration/schemas/dithering.json
index 87a6077d2..05c327b6e 100644
--- a/docker-compose/http-json-schemas/definitions/dithering.json
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/dithering.json
@@ -1,5 +1,6 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema",
+  "$id": "dithering",
   "type": "object",
   "description": "Settings for adding dithering to the signal to increase its linearity",
   "required": [
diff --git a/docker-compose/http-json-schemas/definitions/hba.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/hba.json
similarity index 92%
rename from docker-compose/http-json-schemas/definitions/hba.json
rename to tangostationcontrol/tangostationcontrol/configuration/schemas/hba.json
index 5d1a319fc..fa0eb4f31 100644
--- a/docker-compose/http-json-schemas/definitions/hba.json
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/hba.json
@@ -1,12 +1,13 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema",
+  "$id": "hba",
   "type": "object",
   "required": [
     "tile_beam"
   ],
   "properties": {
     "tile_beam": {
-      "$ref": "pointing.json"
+      "$ref": "pointing"
     },
     "DAB_filter": {
       "type": "boolean",
diff --git a/docker-compose/http-json-schemas/definitions/observation-settings.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-settings.json
similarity index 94%
rename from docker-compose/http-json-schemas/definitions/observation-settings.json
rename to tangostationcontrol/tangostationcontrol/configuration/schemas/observation-settings.json
index e6bdf42a3..c23561dc7 100644
--- a/docker-compose/http-json-schemas/definitions/observation-settings.json
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-settings.json
@@ -1,5 +1,6 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema",
+  "$id": "observation-settings",
   "type": "object",
   "required": [
     "observation_id",
@@ -53,7 +54,7 @@
       ]
     },
     "dithering": {
-      "$ref": "dithering.json"
+      "$ref": "dithering"
     },
     "filter": {
       "type": "string",
@@ -71,7 +72,7 @@
       "type": "array",
       "minItems": 1,
       "items": {
-        "$ref": "sap.json"
+        "$ref": "sap"
       }
     },
     "first_beamlet": {
@@ -80,7 +81,7 @@
       "minimum": 0
     },
     "HBA": {
-      "$ref": "hba.json"
+      "$ref": "hba"
     }
   }
 }
diff --git a/docker-compose/http-json-schemas/definitions/pointing.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/pointing.json
similarity index 97%
rename from docker-compose/http-json-schemas/definitions/pointing.json
rename to tangostationcontrol/tangostationcontrol/configuration/schemas/pointing.json
index ac3ddc141..61e1fc80f 100644
--- a/docker-compose/http-json-schemas/definitions/pointing.json
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/pointing.json
@@ -1,5 +1,6 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema",
+  "$id": "pointing",
   "type": "object",
   "required": [
     "angle1",
@@ -41,4 +42,4 @@
       "type": "string"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/docker-compose/http-json-schemas/definitions/sap.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/sap.json
similarity index 87%
rename from docker-compose/http-json-schemas/definitions/sap.json
rename to tangostationcontrol/tangostationcontrol/configuration/schemas/sap.json
index 50e945e87..7dd382d9e 100644
--- a/docker-compose/http-json-schemas/definitions/sap.json
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/sap.json
@@ -1,5 +1,6 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema",
+  "$id": "sap",
   "type": "object",
   "required": [
     "subbands",
@@ -14,7 +15,7 @@
       }
     },
     "pointing": {
-      "$ref": "pointing.json"
+      "$ref": "pointing"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/docker-compose/http-json-schemas/definitions/station-configuration.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/station-configuration.json
similarity index 70%
rename from docker-compose/http-json-schemas/definitions/station-configuration.json
rename to tangostationcontrol/tangostationcontrol/configuration/schemas/station-configuration.json
index 66a11e129..4c8d110c0 100644
--- a/docker-compose/http-json-schemas/definitions/station-configuration.json
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/station-configuration.json
@@ -1,6 +1,6 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
-  "$id": "http://http-json-schemas/station-configuration.json",
+  "$id": "station-configuration",
   "title": "Tango device generator JSON canonical format",
   "type": "object",
   "additionalProperties": false,
@@ -27,18 +27,18 @@
       "additionalProperties": false,
       "patternProperties": {
         "^[\\-\\w]+$": {
-          "$ref": "station-configuration.json#/$defs/server"
+          "$ref": "#/$defs/server"
         }
       }
     },
     "classes": {
       "type": "object",
       "additionalProperties": {
-        "$ref": "station-configuration.json#/$defs/device"
+        "$ref": "#/$defs/device"
       },
       "properties": {
         "properties": {
-          "$ref": "station-configuration.json#/$defs/property"
+          "$ref": "#/$defs/property"
         }
       }
     }
@@ -48,7 +48,7 @@
       "type": "object",
       "patternProperties": {
         "^[\\-\\w]+$": {
-          "$ref": "station-configuration.json#/$defs/instance"
+          "$ref": "#/$defs/instance"
         }
       }
     },
@@ -56,7 +56,7 @@
       "type": "object",
       "patternProperties": {
         "^[\\-\\w]+$": {
-          "$ref": "station-configuration.json#/$defs/class"
+          "$ref": "#/$defs/class"
         }
       }
     },
@@ -65,7 +65,7 @@
       "additionalProperties": false,
       "patternProperties": {
         "^[\\-\\w.@]+/[\\-\\w.@]+/[\\-\\w.@]+$": {
-          "$ref": "station-configuration.json#/$defs/device"
+          "$ref": "#/$defs/device"
         }
       }
     },
@@ -74,10 +74,10 @@
       "additionalProperties": false,
       "properties": {
         "properties": {
-          "$ref": "station-configuration.json#/$defs/properties"
+          "$ref": "#/$defs/properties"
         },
         "attribute_properties": {
-          "$ref": "station-configuration.json#/$defs/attribute_properties"
+          "$ref": "#/$defs/attribute_properties"
         },
         "alias": {
           "type": "string"
@@ -87,7 +87,7 @@
     "properties": {
       "type": "object",
       "additionalProperties": {
-        "$ref": "station-configuration.json#/$defs/property"
+        "$ref": "#/$defs/property"
       }
     },
     "property": {
@@ -99,20 +99,20 @@
     "attribute_properties": {
       "type": "object",
       "additionalProperties": {
-        "$ref": "station-configuration.json#/$defs/attribute_property"
+        "$ref": "#/$defs/attribute_property"
       }
     },
     "attribute_property": {
       "type": "object",
       "additionalProperties": {
-        "$ref": "station-configuration.json#/$defs/property"
+        "$ref": "#/$defs/property"
       }
     },
     "class_property": {
       "type": "object",
       "additionalProperties": {
         "type": "object",
-        "$ref": "station-configuration.json#/$defs/property"
+        "$ref": "#/$defs/property"
       }
     }
   }
diff --git a/tangostationcontrol/tangostationcontrol/device_server/__init__.py b/tangostationcontrol/tangostationcontrol/device_server/__init__.py
new file mode 100644
index 000000000..04330a211
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/device_server/__init__.py
@@ -0,0 +1,72 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
+import logging
+import sys
+
+from prometheus_client import start_http_server, Info
+from tango import Util
+from tango.server import run
+
+from tangostationcontrol import __version__ as version
+from tangostationcontrol import devices  # noqa: F401
+from tangostationcontrol.common.lofar_logging import configure_logger
+from tangostationcontrol.metrics import labelnames
+
+logger = logging.getLogger()
+
+tango_ds_info = Info("tango_ds", "Info about the tango device server", labelnames)
+
+
+def post_init_cb():
+    """This callback runs after the device server was initialised. It sets the
+    station control version as the server version, as well as initialises common
+    prometheus metrics about the device server."""
+    util = Util.instance()
+    util.set_server_version(version)
+    tango_version = util.get_tango_lib_release()
+    tango_ds_info.labels(
+        domain="dserver",
+        family=util.get_ds_exec_name(),
+        member=util.get_ds_inst_name(),
+        device_class="DServer",
+    ).info(
+        dict(
+            idl_version=util.get_version_str(),
+            server_version=util.get_server_version(),
+            lib_version=".".join(
+                [
+                    str(x)
+                    for x in [
+                        tango_version // 100,
+                        tango_version % 100 // 10,
+                        tango_version % 10,
+                    ]
+                ]
+            ),
+        )
+    )
+
+
+# ----------
+# Run server
+# ----------
+def main(**kwargs):
+    device_class_str = sys.argv[1]
+    device_class = getattr(sys.modules["tangostationcontrol.devices"], device_class_str)
+
+    start_http_server(8000)
+
+    # Remove first argument which is filename
+    args = sys.argv[1:]
+
+    # Setup logging
+    configure_logger()
+
+    if device_class_str == "ObservationControl":
+        classes = (device_class, devices.Observation)
+    else:
+        classes = (device_class,)
+
+    # Start the device server
+    run(classes, args=args, post_init_callback=post_init_cb, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/__init__.py b/tangostationcontrol/tangostationcontrol/devices/__init__.py
index c92b61544..b6038faf0 100644
--- a/tangostationcontrol/tangostationcontrol/devices/__init__.py
+++ b/tangostationcontrol/tangostationcontrol/devices/__init__.py
@@ -1,2 +1,62 @@
 #  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 #  SPDX-License-Identifier: Apache-2.0
+
+from .antennafield.afh import AFH
+from .antennafield.afl import AFL
+from .aps import APS
+from .apsct import APSCT
+from .apspu import APSPU
+from .calibration import Calibration
+from .ccd import CCD
+from .configuration import Configuration
+from .docker import Docker
+from .ec import EC
+from .observation import Observation
+from .observation_control import ObservationControl
+from .pcon import PCON
+from .psoc import PSOC
+from .recv.recvh import RECVH
+from .recv.recvl import RECVL
+from .sdp.beamlet import Beamlet
+from .sdp.bst import BST
+from .sdp.digitalbeam import DigitalBeam
+from .sdp.firmware import SDPFirmware
+from .sdp.sdp import SDP
+from .sdp.sst import SST
+from .sdp.statistics import Statistics
+from .sdp.xst import XST
+from .station_manager import StationManager
+from .temperature_manager import TemperatureManager
+from .tilebeam import TileBeam
+from .unb2 import UNB2
+
+__all__ = [
+    "AFH",
+    "AFL",
+    "APS",
+    "APSCT",
+    "APSPU",
+    "Calibration",
+    "CCD",
+    "Configuration",
+    "Docker",
+    "EC",
+    "Observation",
+    "ObservationControl",
+    "PCON",
+    "PSOC",
+    "RECVL",
+    "RECVH",
+    "Beamlet",
+    "BST",
+    "DigitalBeam",
+    "SDPFirmware",
+    "SDP",
+    "SST",
+    "Statistics",
+    "XST",
+    "StationManager",
+    "TemperatureManager",
+    "TileBeam",
+    "UNB2",
+]
diff --git a/tangostationcontrol/tangostationcontrol/devices/aps.py b/tangostationcontrol/tangostationcontrol/devices/aps.py
index 5e4b21073..1abc1524a 100644
--- a/tangostationcontrol/tangostationcontrol/devices/aps.py
+++ b/tangostationcontrol/tangostationcontrol/devices/aps.py
@@ -1,18 +1,17 @@
-# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ APS Device Server for LOFAR2.0
 
 """
 
 # PyTango imports
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
 
 # Additional import
 
-__all__ = ["APS", "main"]
+__all__ = ["APS"]
 
 
 @device_logging_to_python()
@@ -33,11 +32,3 @@ class APS(LOFARDevice):
     # --------
     # Commands
     # --------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the APS module."""
-    return entry(APS, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/apsct.py b/tangostationcontrol/tangostationcontrol/devices/apsct.py
index 9b7023b1c..6048169b9 100644
--- a/tangostationcontrol/tangostationcontrol/devices/apsct.py
+++ b/tangostationcontrol/tangostationcontrol/devices/apsct.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ APSCT Device Server for LOFAR2.0
 
@@ -15,17 +15,16 @@ from tango import AttrWriteType
 from tango import DebugIt
 from tango.server import command, attribute, device_property
 from tangostationcontrol.common.constants import DEFAULT_POLLING_PERIOD
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
-from tangostationcontrol.devices.device_decorators import only_in_states
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
+from tangostationcontrol.common.device_decorators import only_in_states
 
 # Additional import
 
 logger = logging.getLogger()
 
-__all__ = ["APSCT", "main"]
+__all__ = ["APSCT"]
 
 
 @device_logging_to_python()
@@ -268,11 +267,3 @@ class APSCT(OPCUADevice):
         :return:None
         """
         self.opcua_connection.call_method(["APSCT_160MHz_on"])
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the APSCT module."""
-    return entry(APSCT, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/apspu.py b/tangostationcontrol/tangostationcontrol/devices/apspu.py
index fd89a2796..7d82bf256 100644
--- a/tangostationcontrol/tangostationcontrol/devices/apspu.py
+++ b/tangostationcontrol/tangostationcontrol/devices/apspu.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ APSPU Device Server for LOFAR2.0
 
@@ -11,14 +11,14 @@ from attribute_wrapper.attribute_wrapper import AttributeWrapper
 # PyTango imports
 from tango import AttrWriteType
 from tango.server import attribute, device_property
+
 from tangostationcontrol.common.constants import DEFAULT_POLLING_PERIOD
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
 
 # Additional import
 
-__all__ = ["APSPU", "main"]
+__all__ = ["APSPU"]
 
 
 @device_logging_to_python()
@@ -100,9 +100,8 @@ class APSPU(OPCUADevice):
     # ----------
     # Summarising Attributes
     # ----------
-    APSPU_error_R = attribute(dtype=bool, fisallowed="is_attribute_access_allowed")
-
-    def read_APSPU_error_R(self):
+    @attribute(dtype=bool, fisallowed="is_attribute_access_allowed")
+    def APSPU_error_R(self):
         return (
             (self.read_attribute("APSPUTR_I2C_error_R") > 0)
             or self.alarm_val("APSPU_PCB_ID_R")
@@ -147,11 +146,3 @@ class APSPU(OPCUADevice):
     # --------
     # Commands
     # --------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the ObservationControl module."""
-    return entry(APSPU, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/antennafield_device.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/antennafield_device.py
index 75433fd6d..b8dc08d68 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/antennafield_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/antennafield_device.py
@@ -1,12 +1,12 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ AntennaField Abstract Device Server for LOFAR2.0
 
 """
 import logging
-from typing import List, Dict
 from enum import IntEnum
+from typing import List, Dict
 
 import numpy
 
@@ -22,6 +22,7 @@ from tango.server import device_property, attribute, command
 from tangostationcontrol.beam.geo import ETRS_to_ITRF
 from tangostationcontrol.beam.geo import GEO_to_GEOHASH
 from tangostationcontrol.beam.geo import ITRF_to_GEO
+from tangostationcontrol.common.antennas import antenna_set_to_mask
 from tangostationcontrol.common.cables import cable_types
 from tangostationcontrol.common.constants import (
     MAX_ANTENNA,
@@ -32,7 +33,7 @@ from tangostationcontrol.common.constants import (
     A_pn,
     N_ANTENNA_SETS,
 )
-from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.common.device_decorators import only_in_states
 from tangostationcontrol.common.frequency_bands import bands, Band
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
@@ -40,10 +41,7 @@ from tangostationcontrol.common.lofar_logging import (
 )
 from tangostationcontrol.common.proxy import create_device_proxy
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
-from tangostationcontrol.common.antennas import antenna_set_to_mask
-from tangostationcontrol.devices.device_decorators import only_in_states
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
-from tangostationcontrol.devices.types import DeviceTypes
 from tangostationcontrol.devices.base_device_classes.mapper import (
     MappingKeys,
     MappedAttribute,
@@ -52,10 +50,11 @@ from tangostationcontrol.devices.base_device_classes.mapper import (
     HBAToRecvMapper,
     LBAToRecvMapper,
 )
+from tangostationcontrol.devices.types import DeviceTypes
 
 logger = logging.getLogger()
 
-__all__ = ["AF", "AntennaUse", "AntennaQuality", "main"]
+__all__ = ["AF", "AntennaUse", "AntennaQuality"]
 
 
 class AntennaUse(IntEnum):
@@ -964,11 +963,3 @@ class AF(LOFARDevice):
             self.read_attribute("Antenna_Usage_Mask_R"),
         )
         walker.walk_receivers(self.recv_proxies, lambda recv: recv.RCU_DTH_off())
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the AntennaField module."""
-    return entry(AF, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/beam_device.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/beam_device.py
index 0e50d0335..a6d3dab2a 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/beam_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/beam_device.py
@@ -29,9 +29,12 @@ from tango.server import attribute, command, device_property
 from tangostationcontrol.beam.delays import Delays, pointing_to_str
 from tangostationcontrol.beam.managers import AbstractBeamManager
 from tangostationcontrol.common.constants import MAX_POINTINGS, N_point_prop
+from tangostationcontrol.common.device_decorators import (
+    only_in_states,
+    fault_on_error,
+)
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import log_exceptions, exception_to_str
 from tangostationcontrol.common.measures import (
     download_measures,
@@ -41,14 +44,9 @@ from tangostationcontrol.common.measures import (
     use_measures_directory,
 )
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
-from tangostationcontrol.devices.device_decorators import (
-    only_in_states,
-    fault_on_error,
-)
-
 from tangostationcontrol.devices.base_device_classes.async_device import AsyncDevice
 
-__all__ = ["BeamDevice", "main", "BeamTracker"]
+__all__ = ["BeamDevice", "BeamTracker"]
 
 logger = logging.getLogger()
 
@@ -447,16 +445,6 @@ class BeamDevice(AsyncDevice):
         restart_python()
 
 
-# ----------
-# Run server
-# ----------
-
-
-def main(**kwargs):
-    """Main function of the Docker module."""
-    return entry(BeamDevice, **kwargs)
-
-
 # ----------
 # Beam Tracker
 # ----------
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/lofar_device.py
index 61ac4db56..cabc55e4c 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/lofar_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/lofar_device.py
@@ -1,16 +1,16 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """Hardware Device Server for LOFAR2.0
 
 """
-from functools import partial
-import pprint
-from typing import List
+import logging
 import math
+import pprint
 import textwrap
 import time
-import logging
+from functools import partial
+from typing import List
 
 import numpy
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
@@ -24,20 +24,21 @@ from tango import (
 
 # PyTango imports
 from tango.server import attribute, command, Device, device_property
+
+# Additional import
+from tangostationcontrol import __version__ as version
+from tangostationcontrol.common.device_decorators import only_in_states, fault_on_error
 from tangostationcontrol.common.lofar_logging import log_exceptions
+from tangostationcontrol.common.proxy import create_device_proxy
 from tangostationcontrol.common.states import (
     DEFAULT_COMMAND_STATES,
     INITIALISED_STATES,
 )
-from tangostationcontrol.common.proxy import create_device_proxy
 from tangostationcontrol.common.type_checking import sequence_not_str
-from tangostationcontrol.devices.device_decorators import only_in_states, fault_on_error
 from tangostationcontrol.devices.base_device_classes.control_hierarchy import (
     ControlHierarchyDevice,
 )
-
-# Additional import
-from tangostationcontrol import __version__ as version
+from tangostationcontrol.metrics import DeviceMetricCollector
 
 __all__ = ["LOFARDevice"]
 
@@ -94,7 +95,7 @@ class LOFARDevice(Device):
         doc="How long this software device has been running (wall-clock time, in seconds)",
         unit="s",
         dtype=numpy.int64,
-        fget=lambda self: numpy.int64(time.time() - self._device_start_time),
+        fget=lambda self: numpy.int64(time.time() - self.device_start_time),
     )
 
     access_count_R = attribute(
@@ -247,7 +248,8 @@ class LOFARDevice(Device):
         self._access_count = 0
 
         # record when this device was started
-        self._device_start_time = time.time()
+        self.device_start_time = time.time()
+        DeviceMetricCollector.add_device(self)
 
     def _init_device(self):
         logger.debug("[LOFARDevice] init_device")
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/opcua_device.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/opcua_device.py
index 1462c7a10..6799dbe87 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/opcua_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/opcua_device.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ Generic OPC-UA Device Server for LOFAR2.0
 
@@ -7,11 +7,13 @@
 
 import asyncio
 import logging
+from datetime import datetime
 
 import numpy
+import prometheus_client as pc
 
 # PyTango imports
-from tango.server import device_property, attribute
+from tango.server import device_property, attribute, Device
 from tangostationcontrol.clients.opcua_client import (
     OPCUAConnection,
     test_OPCUA_connection,
@@ -19,7 +21,7 @@ from tangostationcontrol.clients.opcua_client import (
 )
 from tangostationcontrol.common.lofar_logging import log_exceptions, exception_to_str
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
-
+from tangostationcontrol.metrics import device_metrics, labelnames
 
 # Additional import
 
@@ -28,6 +30,7 @@ logger = logging.getLogger()
 __all__ = ["OPCUADevice"]
 
 
+@device_metrics(exclude=["*_RW", "can_connect_R"])
 class OPCUADevice(LOFARDevice):
     """
 
@@ -42,8 +45,46 @@ class OPCUADevice(LOFARDevice):
             - Type:'DevDouble'
     """
 
-    #  Contains the connection status information
-    OPCUA_connection_status = OPCUAConnectionStatus()
+    class MonitoredOPCUAConnectionStatus(OPCUAConnectionStatus):
+        ds_opcua_nr_connection_losses = pc.Counter(
+            "ds_opcua_connection_losses", "Amount of connection losses", labelnames
+        )
+        ds_opcua_connected = pc.Gauge(
+            "ds_opcua_connected", "Is connected to OPCUA server", labelnames
+        )
+
+        def __init__(self, device: Device):
+            self.last_disconnect_exception = None
+            self.last_disconnect_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+            self._nr_connection_losses = 0
+            self._connected = False
+            self._device = device
+
+        @property
+        def nr_connection_losses(self):
+            return self._nr_connection_losses
+
+        @nr_connection_losses.setter
+        def nr_connection_losses(self, value):
+            try:
+                self.ds_opcua_nr_connection_losses.labels(
+                    self._device.metric_labels
+                ).inc(value - self._nr_connection_losses)
+            except ValueError:
+                pass
+            self._nr_connection_losses = value
+
+        @property
+        def connected(self):
+            return self._connected
+
+        @connected.setter
+        def connected(self, value):
+            self._connected = value
+            try:
+                self.ds_opcua_connected.labels(self._device.metric_labels).set(value)
+            except ValueError:
+                pass
 
     # -----------------
     # Device Properties
@@ -99,11 +140,8 @@ class OPCUADevice(LOFARDevice):
         fisallowed="is_attribute_access_allowed",
     )
 
-    can_connect_R = attribute(
-        dtype=bool,
-    )
-
-    def read_can_connect_R(self):
+    @attribute(dtype=bool)
+    def can_connect_R(self):
         """
         Test whether we can connect with the OPC-ua server.
         Sets up an OPC ua connection from scratch, as it cannot be assumed
@@ -132,7 +170,7 @@ class OPCUADevice(LOFARDevice):
             self.OPC_namespace,
             self.OPC_Time_Out,
             self.Fault,
-            self.OPCUA_connection_status,
+            self.MonitoredOPCUAConnectionStatus(self),
         )
         self.opcua_connection.node_path_prefix = self.OPC_Node_Path_Prefix
 
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/power_hierarchy.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/power_hierarchy.py
index 2fb996242..1e6626be9 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/power_hierarchy.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/power_hierarchy.py
@@ -17,7 +17,7 @@ from tangostationcontrol.common.states import (
 )
 from tangostationcontrol.common.type_checking import device_class_matches
 
-from tangostationcontrol.devices.device_decorators import suppress_exceptions
+from tangostationcontrol.common.device_decorators import suppress_exceptions
 
 logger = logging.getLogger()
 
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/recv_device.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/recv_device.py
index 63dcd36d6..c8c8dde1a 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/recv_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/recv_device.py
@@ -1,18 +1,19 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ RECV Abstract Device Server for LOFAR2.0
 
 """
 import logging
 from enum import IntEnum
+
 import numpy
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
+from tango import AttrWriteType, DevString, DevLong
 
 # PyTango imports
 from tango import DebugIt
 from tango.server import command, device_property, attribute
-from tango import AttrWriteType, DevString, DevLong
 
 # Additional import
 from tangostationcontrol.common.constants import (
@@ -24,12 +25,12 @@ from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.frequency_bands import bands
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
-from tangostationcontrol.devices.device_decorators import only_in_states
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
+from tangostationcontrol.common.device_decorators import only_in_states
 
 logger = logging.getLogger()
 
-__all__ = ["RECVDevice", "main"]
+__all__ = ["RECVDevice"]
 
 
 class RCUType(IntEnum):
@@ -449,9 +450,6 @@ class RECVDevice(OPCUADevice):
         return bands[filter_name].rcu_band
 
 
-# ----------
-# Run server
-# ----------
 def main(**kwargs):
     """Main function of the RECV module."""
     return entry(RECVDevice, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/calibration.py b/tangostationcontrol/tangostationcontrol/devices/calibration.py
index 238d09cfb..b99351c7b 100644
--- a/tangostationcontrol/tangostationcontrol/devices/calibration.py
+++ b/tangostationcontrol/tangostationcontrol/devices/calibration.py
@@ -6,11 +6,10 @@
 """
 import datetime
 import logging
-import numpy
 
+import numpy
 from tango import EventType, Database
 from tango.server import device_property, command, attribute
-
 from tangostationcontrol.common.calibration import (
     CalibrationManager,
     calibrate_RCU_attenuator_dB,
@@ -20,7 +19,7 @@ from tangostationcontrol.common.case_insensitive_dict import (
     CaseInsensitiveDict,
     CaseInsensitiveString,
 )
-from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.common.device_decorators import only_in_states
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
@@ -28,15 +27,14 @@ from tangostationcontrol.common.lofar_logging import (
 from tangostationcontrol.common.proxy import create_device_proxy
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
 from tangostationcontrol.common.type_checking import device_name_matches
-from tangostationcontrol.devices.antennafield.afl import AFL
 from tangostationcontrol.devices.antennafield.afh import AFH
-from tangostationcontrol.devices.device_decorators import only_in_states
+from tangostationcontrol.devices.antennafield.afl import AFL
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
-from tangostationcontrol.devices.sdp.sdp import SDP
 from tangostationcontrol.devices.sdp.firmware import SDPFirmware
+from tangostationcontrol.devices.sdp.sdp import SDP
 
 logger = logging.getLogger()
-__all__ = ["Calibration", "main"]
+__all__ = ["Calibration"]
 
 
 @device_logging_to_python()
@@ -140,7 +138,7 @@ class Calibration(LOFARDevice):
         dtype="DevString",
         mandatory=False,
         update_db=True,
-        default_value="http://object-storage:9000/caltables",
+        default_value="http://s3/caltables",
     )
 
     @attribute(dtype=(str,), max_dim_x=20)
@@ -333,15 +331,3 @@ class Calibration(LOFARDevice):
         self.event_subscriptions = []
         for prx, s in subscriptions:
             prx.unsubscribe_event(s)
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the Calibration module."""
-    return entry(Calibration, **kwargs)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tangostationcontrol/tangostationcontrol/devices/ccd.py b/tangostationcontrol/tangostationcontrol/devices/ccd.py
index 1b01452f5..830179e39 100644
--- a/tangostationcontrol/tangostationcontrol/devices/ccd.py
+++ b/tangostationcontrol/tangostationcontrol/devices/ccd.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ CCD Device Server for LOFAR2.0
 
@@ -15,17 +15,16 @@ from tango import AttrWriteType
 from tango import DebugIt
 from tango.server import command, attribute, device_property
 from tangostationcontrol.common.constants import DEFAULT_POLLING_PERIOD
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
-from tangostationcontrol.devices.device_decorators import only_in_states
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
+from tangostationcontrol.common.device_decorators import only_in_states
 
 # Additional import
 
 logger = logging.getLogger()
 
-__all__ = ["CCD", "main"]
+__all__ = ["CCD"]
 
 
 @device_logging_to_python()
@@ -253,11 +252,3 @@ class CCD(OPCUADevice):
         :return:None
         """
         self.opcua_connection.call_method(["CCD_on"])
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the ObservationControl module."""
-    return entry(CCD, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/configuration.py b/tangostationcontrol/tangostationcontrol/devices/configuration.py
index 3de99aaaa..703c89f84 100644
--- a/tangostationcontrol/tangostationcontrol/devices/configuration.py
+++ b/tangostationcontrol/tangostationcontrol/devices/configuration.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ Configuration Device Server for LOFAR2.0
 
@@ -15,18 +15,17 @@ from tango.server import attribute, command, device_property
 
 # Additional import
 from tangostationcontrol.common.configuration import StationConfiguration
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
 )
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
-from tangostationcontrol.devices.device_decorators import only_in_states
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
+from tangostationcontrol.common.device_decorators import only_in_states
 
 logger = logging.getLogger()
 
-__all__ = ["Configuration", "main"]
+__all__ = ["Configuration"]
 
 
 @device_logging_to_python()
@@ -155,11 +154,3 @@ class Configuration(LOFARDevice):
             sort_keys=True,
         )
         self._write_backup_station_configuration(tango_db_configuration)
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the Boot module."""
-    return entry(Configuration, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/docker.py b/tangostationcontrol/tangostationcontrol/devices/docker.py
index a308132a7..506397d07 100644
--- a/tangostationcontrol/tangostationcontrol/devices/docker.py
+++ b/tangostationcontrol/tangostationcontrol/devices/docker.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ Docker Device Server for LOFAR2.0
 
@@ -7,6 +7,7 @@
 
 import asyncio
 import logging
+
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
 from tango import AttrWriteType
 
@@ -15,7 +16,6 @@ from tango.server import device_property
 from tangostationcontrol.clients.docker_client import DockerClient
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
@@ -24,7 +24,7 @@ from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDe
 
 logger = logging.getLogger()
 
-__all__ = ["Docker", "main"]
+__all__ = ["Docker"]
 
 
 @device_logging_to_python()
@@ -394,11 +394,3 @@ class Docker(LOFARDevice):
     # --------
     # Commands
     # --------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the Docker module."""
-    return entry(Docker, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/ec.py b/tangostationcontrol/tangostationcontrol/devices/ec.py
index d466361e1..2998c3caa 100644
--- a/tangostationcontrol/tangostationcontrol/devices/ec.py
+++ b/tangostationcontrol/tangostationcontrol/devices/ec.py
@@ -1,18 +1,17 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ EC (Environmental Control) Device Server for LOFAR2.0
 
 """
 
 import logging
-import numpy
 
+import numpy
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
 from tango import AttrWriteType
 
 # PyTango imports
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
 
@@ -20,7 +19,7 @@ from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADe
 
 logger = logging.getLogger()
 
-__all__ = ["EC", "main"]
+__all__ = ["EC"]
 
 
 @device_logging_to_python()
@@ -121,13 +120,3 @@ class EC(OPCUADevice):
     # --------
     # Commands
     # --------
-
-    pass
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the EnvironmentalControl module."""
-    return entry(EC, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py
index 4962f4f54..9ccd2a821 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation.py
@@ -1,8 +1,8 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
-from itertools import chain
 import logging
+from itertools import chain
 from time import time
 from typing import Optional
 
@@ -20,19 +20,18 @@ from tangostationcontrol.common.constants import (
     N_point_prop,
     N_pol,
 )
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.common.lofar_logging import log_exceptions
 from tangostationcontrol.common.proxy import create_device_proxy
 from tangostationcontrol.common.antennas import device_member_to_full_device_name
 from tangostationcontrol.configuration import ObservationSettings
-from tangostationcontrol.devices.device_decorators import fault_on_error
-from tangostationcontrol.devices.device_decorators import only_in_states
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
+from tangostationcontrol.common.device_decorators import fault_on_error
+from tangostationcontrol.common.device_decorators import only_in_states
 
 logger = logging.getLogger()
 
-__all__ = ["Observation", "main"]
+__all__ = ["Observation"]
 
 
 @device_logging_to_python()
@@ -483,11 +482,3 @@ class Observation(LOFARDevice):
             raise ValueError(f"Unsupported element selection: {element_selection}")
 
         return selection.reshape(nr_antennas, N_elements * N_pol)
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the ObservationControl module."""
-    return entry(Observation, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/observation_control.py b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
index acf35a664..ef6eb5fdd 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation_control.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 import logging
 
@@ -13,19 +13,17 @@ from tango import (
 )
 from tango.server import Device, command, attribute
 from tangostationcontrol.common import ObservationController
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
 )
 from tangostationcontrol.configuration import ObservationSettings
-from tangostationcontrol.devices.device_decorators import only_when_on
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
-from tangostationcontrol.devices.observation import Observation
+from tangostationcontrol.common.device_decorators import only_when_on
 
 logger = logging.getLogger()
 
-__all__ = ["ObservationControl", "main"]
+__all__ = ["ObservationControl"]
 
 
 @device_logging_to_python()
@@ -196,14 +194,3 @@ class ObservationControl(LOFARDevice):
     @log_exceptions()
     def is_any_observation_running(self) -> DevBoolean:
         return len(self._observation_controller.running_observations) > 0
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the ObservationControl module."""
-
-    # The ObservationControl device spawns Observation devices, so we manage
-    # both in this DeviceServer.
-    return entry((ObservationControl, Observation), verbose=True, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/pcon.py b/tangostationcontrol/tangostationcontrol/devices/pcon.py
index f42339d6e..7bc383f9a 100644
--- a/tangostationcontrol/tangostationcontrol/devices/pcon.py
+++ b/tangostationcontrol/tangostationcontrol/devices/pcon.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ PCON Device Server for LOFAR2.0
 
@@ -8,23 +8,22 @@
 import logging
 
 import numpy
-from pysmi import debug
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
+from pysmi import debug
+from tangostationcontrol.clients.snmp.attribute_classes import PCON_sim
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
 )
 from tangostationcontrol.devices.base_device_classes.snmp_device import SNMPDevice
-from tangostationcontrol.clients.snmp.attribute_classes import PCON_sim
 
 debug.setLogger(debug.Debug("searcher", "compiler", "borrower", "reader"))
 
 logger = logging.getLogger()
 
-__all__ = ["PCON", "main"]
+__all__ = ["PCON"]
 
 
 @device_logging_to_python()
@@ -50,11 +49,3 @@ class PCON(SNMPDevice):
         """user code here. is called when the state is set to STANDBY"""
 
         super().configure_for_initialise(simulator_class=PCON_sim)
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the PCON module."""
-    return entry(PCON, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc.py b/tangostationcontrol/tangostationcontrol/devices/psoc.py
index 147e6d459..b671e7275 100644
--- a/tangostationcontrol/tangostationcontrol/devices/psoc.py
+++ b/tangostationcontrol/tangostationcontrol/devices/psoc.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ PSOC Device Server for LOFAR2.0
 
@@ -9,26 +9,25 @@ import logging
 from datetime import timedelta
 
 import numpy
-from pysmi import debug
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
+from pysmi import debug
 from tango.server import device_property, command
+from tangostationcontrol.clients.snmp.attribute_classes import PSOC_sim
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
 )
-from tangostationcontrol.devices.base_device_classes.snmp_device import SNMPDevice
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
-from tangostationcontrol.devices.device_decorators import only_in_states
-from tangostationcontrol.clients.snmp.attribute_classes import PSOC_sim
+from tangostationcontrol.devices.base_device_classes.snmp_device import SNMPDevice
+from tangostationcontrol.common.device_decorators import only_in_states
 
 debug.setLogger(debug.Debug("searcher", "compiler", "borrower", "reader"))
 
 logger = logging.getLogger()
 
-__all__ = ["PSOC", "main"]
+__all__ = ["PSOC"]
 
 
 @device_logging_to_python()
@@ -182,11 +181,3 @@ class PSOC(SNMPDevice):
     def power_ccd_off(self):
         """Turn off 230V to the CCD"""
         self.socket_off("socket_1")
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the PSOC module."""
-    return entry(PSOC, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/recv/recvh.py b/tangostationcontrol/tangostationcontrol/devices/recv/recvh.py
index 902f4552e..b4315744b 100644
--- a/tangostationcontrol/tangostationcontrol/devices/recv/recvh.py
+++ b/tangostationcontrol/tangostationcontrol/devices/recv/recvh.py
@@ -1,15 +1,15 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ Receiver Unit High Device Server for LOFAR2.0
 
 """
 import numpy
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
+from tango import AttrWriteType, DevVarFloatArray
 
 # PyTango imports
 from tango.server import command, device_property
-from tango import AttrWriteType, DevVarFloatArray
 
 # Additional import
 from tangostationcontrol.common.constants import (
@@ -18,12 +18,10 @@ from tangostationcontrol.common.constants import (
     N_pol,
     N_rcu_inp,
 )
-
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.devices.base_device_classes.recv_device import RECVDevice
 
-__all__ = ["RECVH", "main"]
+__all__ = ["RECVH"]
 
 
 @device_logging_to_python()
@@ -221,11 +219,3 @@ class RECVH(RECVDevice):
         HBAT_bf_delay_steps = self._calculate_HBAT_bf_delay_steps(delays)
 
         return HBAT_bf_delay_steps.flatten()
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the RECVH module."""
-    return entry(RECVH, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/recv/recvl.py b/tangostationcontrol/tangostationcontrol/devices/recv/recvl.py
index 4f1e0f34b..b6a31130a 100644
--- a/tangostationcontrol/tangostationcontrol/devices/recv/recvl.py
+++ b/tangostationcontrol/tangostationcontrol/devices/recv/recvl.py
@@ -1,15 +1,14 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ Receiver Unit Low Device Server for LOFAR2.0
 
 """
 
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.devices.base_device_classes.recv_device import RECVDevice
 
-__all__ = ["RECVL", "main"]
+__all__ = ["RECVL"]
 
 
 @device_logging_to_python()
@@ -26,11 +25,3 @@ class RECVL(RECVDevice):
     # ----------
     # Attributes
     # ----------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the RECVL module."""
-    return entry(RECVL, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py b/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py
index d6c412259..42938fd3c 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py
@@ -20,7 +20,6 @@ from tango import (
 
 # PyTango imports
 from tango.server import device_property, command, attribute
-
 from tangostationcontrol.common.constants import (
     N_pn,
     A_pn,
@@ -33,13 +32,12 @@ from tangostationcontrol.common.constants import (
 )
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import log_exceptions
 from tangostationcontrol.common.proxy import create_device_proxy
 from tangostationcontrol.common.sdp import phases_to_weights
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
 
-__all__ = ["Beamlet", "main"]
+__all__ = ["Beamlet"]
 
 logger = logging.getLogger()
 
@@ -630,11 +628,3 @@ class Beamlet(OPCUADevice):
         bf_weights = self._calculate_bf_weights(delays, beamlet_frequencies)
 
         return bf_weights.flatten()
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the SST Device module."""
-    return entry(Beamlet, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py
index 621c3ef6e..36dc2fd27 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py
@@ -1,16 +1,15 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ BST Device Server for LOFAR2.0
 
 """
 
 import numpy
-from lofar_station_client.statistics.collector import BSTCollector
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
+from lofar_station_client.statistics.collector import BSTCollector
 from tango import AttrWriteType
 from tango.server import device_property, attribute
-
 from tangostationcontrol.clients.opcua_client import OPCUAConnection
 from tangostationcontrol.clients.statistics.client import StatisticsClient
 from tangostationcontrol.common.constants import (
@@ -20,10 +19,9 @@ from tangostationcontrol.common.constants import (
 )
 
 # Own imports
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.devices.sdp.statistics import Statistics
 
-__all__ = ["BST", "main"]
+__all__ = ["BST"]
 
 
 class BST(Statistics):
@@ -190,11 +188,3 @@ class BST(Statistics):
     # --------
     # Commands
     # --------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the BST Device module."""
-    return entry(BST, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py b/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py
index 022263aab..53f2cbe76 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py
@@ -11,7 +11,6 @@ import numpy
 
 # PyTango imports
 from tango.server import attribute, AttrWriteType
-
 from tangostationcontrol.beam.delays import Delays
 from tangostationcontrol.beam.managers import DigitalBeamManager
 from tangostationcontrol.common.antennas import antenna_set_to_mask
@@ -22,14 +21,13 @@ from tangostationcontrol.common.constants import (
 )
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import log_exceptions
 from tangostationcontrol.devices.base_device_classes.beam_device import BeamDevice
 from tangostationcontrol.devices.types import DeviceTypes
 
 logger = logging.getLogger()
 
-__all__ = ["DigitalBeam", "main"]
+__all__ = ["DigitalBeam"]
 
 
 class DigitalBeam(BeamDevice):
@@ -195,13 +193,3 @@ class DigitalBeam(BeamDevice):
 
         # relative positions of each antenna
         self._beam_manager.relative_antenna_positions = antenna_itrf - reference_itrf
-
-
-# ----------
-# Run server
-# ----------
-
-
-def main(**kwargs):
-    """Main function of the SST Device module."""
-    return entry(DigitalBeam, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/firmware.py b/tangostationcontrol/tangostationcontrol/devices/sdp/firmware.py
index 37c87ccf3..13bac5ed8 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/firmware.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/firmware.py
@@ -1,11 +1,12 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ SDP Firmware Device Server for LOFAR2.0
 
 """
 
 import logging
+
 import numpy
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
 from tango import AttrWriteType
@@ -22,16 +23,13 @@ from tangostationcontrol.common.constants import (
     N_subbands,
     DEFAULT_POLLING_PERIOD,
 )
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
-
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
-
 from tangostationcontrol.devices.types import DeviceTypes
 
 logger = logging.getLogger()
 
-__all__ = ["SDPFirmware", "main"]
+__all__ = ["SDPFirmware"]
 
 
 @device_logging_to_python()
@@ -306,11 +304,3 @@ class SDPFirmware(OPCUADevice):
     # --------
     # Commands
     # --------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the SDP Firmware module."""
-    return entry(SDPFirmware, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
index 9b6edf416..11b128bd2 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
@@ -24,13 +24,12 @@ from tangostationcontrol.common.constants import (
     DEFAULT_POLLING_PERIOD,
     SDP_UNIT_WEIGHT,
 )
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.common.sdp import subband_frequencies
-from tangostationcontrol.devices.device_decorators import TimeIt
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
+from tangostationcontrol.common.device_decorators import TimeIt
 
-__all__ = ["SDP", "main"]
+__all__ = ["SDP"]
 
 
 @device_logging_to_python()
@@ -514,11 +513,3 @@ class SDP(OPCUADevice):
     # --------
     # Support functions
     # --------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the SDP module."""
-    return entry(SDP, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
index 33add52eb..91168f9b0 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
@@ -1,27 +1,25 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ SST Device Server for LOFAR2.0
 
 """
 
 import numpy
-from lofar_station_client.statistics.collector import SSTCollector
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
+from lofar_station_client.statistics.collector import SSTCollector
 
 # PyTango imports
 from tango import AttrWriteType
 from tango.server import device_property, attribute
-
 from tangostationcontrol.clients.opcua_client import OPCUAConnection
 from tangostationcontrol.clients.statistics.client import StatisticsClient
 from tangostationcontrol.common.constants import N_pn, MAX_INPUTS, N_subbands
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.devices.sdp.statistics import Statistics
 
-__all__ = ["SST", "main"]
+__all__ = ["SST"]
 
 
 class SST(Statistics):
@@ -216,11 +214,3 @@ class SST(Statistics):
     # --------
     # Commands
     # --------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the SST Device module."""
-    return entry(SST, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
index fcedd5c1b..1347339eb 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
@@ -24,10 +24,9 @@ from tangostationcontrol.common.constants import (
 )
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.devices.sdp.statistics import Statistics
 
-__all__ = ["XST", "main"]
+__all__ = ["XST"]
 
 
 class XST(Statistics):
@@ -716,11 +715,3 @@ class XST(Statistics):
     # --------
     # Commands
     # --------
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the XST Device module."""
-    return entry(XST, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/station_manager.py b/tangostationcontrol/tangostationcontrol/devices/station_manager.py
index dc5d6c3f2..c182c1273 100644
--- a/tangostationcontrol/tangostationcontrol/devices/station_manager.py
+++ b/tangostationcontrol/tangostationcontrol/devices/station_manager.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ StationManager Device Server for LOFAR2.0
 
@@ -7,29 +7,27 @@
 
 import logging
 
+from tango import DebugIt, DevState, DevFailed, Except
+
 # pytango imports
 from tango.server import attribute, command, device_property
-from tango import DebugIt, DevState, DevFailed, Except
 
 # Additional import
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.common.lofar_logging import exception_to_str
 from tangostationcontrol.common.lofar_logging import log_exceptions
-from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
-from tangostationcontrol.devices.base_device_classes.power_hierarchy import (
-    PowerHierarchyDevice,
-)
-
 from tangostationcontrol.common.states import (
     StationState,
     ALLOWED_STATION_STATE_TRANSITIONS,
 )
-
+from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
+from tangostationcontrol.devices.base_device_classes.power_hierarchy import (
+    PowerHierarchyDevice,
+)
 
 logger = logging.getLogger()
 
-__all__ = ["StationManager", "main"]
+__all__ = ["StationManager"]
 
 
 @device_logging_to_python()
@@ -305,11 +303,3 @@ class StationManager(LOFARDevice):
 
         # update the station_state variable when successful
         self.station_state = StationState.ON
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the Docker module."""
-    return entry(StationManager, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/temperature_manager.py b/tangostationcontrol/tangostationcontrol/devices/temperature_manager.py
index a19d090f5..68c8be644 100644
--- a/tangostationcontrol/tangostationcontrol/devices/temperature_manager.py
+++ b/tangostationcontrol/tangostationcontrol/devices/temperature_manager.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ overtemperature managing Device Server for LOFAR2.0
 
@@ -7,7 +7,6 @@
 import logging
 
 import numpy as np
-
 from tango import (
     Util,
     Database,
@@ -22,7 +21,6 @@ from tango.server import command
 
 # Additional import
 from tangostationcontrol.common.constants import DEFAULT_POLLING_PERIOD
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
@@ -32,7 +30,7 @@ from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDe
 
 logger = logging.getLogger()
 
-__all__ = ["TemperatureManager", "main"]
+__all__ = ["TemperatureManager"]
 
 
 class AttrInfo:
@@ -225,11 +223,3 @@ class TemperatureManager(LOFARDevice):
         logger.warning(
             "Temperature alarm triggered auto shutdown of all hardware devices"
         )
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the temperature manager module."""
-    return entry(TemperatureManager, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/tilebeam.py b/tangostationcontrol/tangostationcontrol/devices/tilebeam.py
index 80dad2763..f5538b24f 100644
--- a/tangostationcontrol/tangostationcontrol/devices/tilebeam.py
+++ b/tangostationcontrol/tangostationcontrol/devices/tilebeam.py
@@ -11,7 +11,6 @@ import logging
 from tangostationcontrol.beam.delays import Delays
 from tangostationcontrol.beam.managers import TileBeamManager
 from tangostationcontrol.common.constants import N_xyz, N_elements
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
@@ -20,7 +19,7 @@ from tangostationcontrol.devices.base_device_classes.beam_device import BeamDevi
 
 logger = logging.getLogger()
 
-__all__ = ["TileBeam", "main"]
+__all__ = ["TileBeam"]
 
 
 @device_logging_to_python()
@@ -67,11 +66,3 @@ class TileBeam(BeamDevice):
             antenna_reference_itrf[tile] + hbat_antenna_itrf_offsets[tile]
             for tile in range(self._beam_manager.nr_tiles)
         ]
-
-
-# ----------
-# Run server
-# ----------
-def main(**kwargs):
-    """Main function of the ObservationControl module."""
-    return entry(TileBeam, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/unb2.py b/tangostationcontrol/tangostationcontrol/devices/unb2.py
index c6f42aec3..bd930b5b3 100644
--- a/tangostationcontrol/tangostationcontrol/devices/unb2.py
+++ b/tangostationcontrol/tangostationcontrol/devices/unb2.py
@@ -1,5 +1,5 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
 """ UNB2 Device Server for LOFAR2.0
 
@@ -18,15 +18,14 @@ from tangostationcontrol.common.constants import (
     N_qsfp,
     DEFAULT_POLLING_PERIOD,
 )
-from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
-from tangostationcontrol.devices.device_decorators import only_in_states
 from tangostationcontrol.devices.base_device_classes.opcua_device import OPCUADevice
+from tangostationcontrol.common.device_decorators import only_in_states
 
 # Additional import
 
-__all__ = ["UNB2", "main"]
+__all__ = ["UNB2"]
 
 
 @device_logging_to_python()
@@ -455,11 +454,3 @@ class UNB2(OPCUADevice):
         :return:None
         """
         self.opcua_connection.call_method(["UNB2_on"])
-
-
-# ----------
-# Run server
-# ----------
-def main(args=None, **kwargs):
-    """Main function of the UNB2 module."""
-    return entry(UNB2, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/metrics/__init__.py b/tangostationcontrol/tangostationcontrol/metrics/__init__.py
new file mode 100644
index 000000000..b360c8faa
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/metrics/__init__.py
@@ -0,0 +1,9 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
+from ._collectors import DeviceMetricCollector
+from ._decorators import device_metrics, DeviceMetricsAttribute
+
+labelnames = DeviceMetricsAttribute.labelnames
+
+__all__ = ["device_metrics", "labelnames", "DeviceMetricCollector"]
diff --git a/tangostationcontrol/tangostationcontrol/metrics/_collectors.py b/tangostationcontrol/tangostationcontrol/metrics/_collectors.py
new file mode 100644
index 000000000..ea4dfa93d
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/metrics/_collectors.py
@@ -0,0 +1,43 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+import time
+
+from prometheus_client import REGISTRY
+from prometheus_client.core import GaugeMetricFamily
+from prometheus_client.metrics_core import InfoMetricFamily
+
+from tangostationcontrol import __version__ as version
+from ._decorators import DeviceMetricsAttribute
+
+
+class DeviceMetricCollector:
+    """
+    Collects general device metrics for the prometheus metrics endpoint.
+    """
+
+    _devices = []
+
+    @staticmethod
+    def add_device(device):
+        DeviceMetricCollector._devices.append(device)
+
+    def collect(self):
+        uptime_metric = GaugeMetricFamily(
+            "device_uptime_seconds",
+            "How long this software device has been running (wall-clock time, in seconds)",
+            labels=DeviceMetricsAttribute.labelnames,
+        )
+        version_metric = InfoMetricFamily(
+            "device", "Version of the device", labels=DeviceMetricsAttribute.labelnames
+        )
+        t = time.time()
+        for device in self._devices:
+            if not hasattr(device, "metric_labels"):
+                continue
+            uptime_metric.add_metric(device.metric_labels, t - device.device_start_time)
+            version_metric.add_metric(device.metric_labels, dict(version=version))
+        yield uptime_metric
+        yield version_metric
+
+
+REGISTRY.register(DeviceMetricCollector())
diff --git a/tangostationcontrol/tangostationcontrol/metrics/_decorators.py b/tangostationcontrol/tangostationcontrol/metrics/_decorators.py
new file mode 100644
index 000000000..c2b64134f
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/metrics/_decorators.py
@@ -0,0 +1,133 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+import fnmatch
+import functools
+import logging
+
+from prometheus_client import Gauge, Enum, Counter
+from tango import Attribute, DevState
+from tango.server import Device, attribute
+
+logger = logging.getLogger()
+
+
+class DeviceMetricsAttribute:
+    """
+    Injects code to provide prometheus metrics for devices annotated with device_metrics
+    """
+
+    def __init__(self, exclude=None):
+        self.exclude = [] if exclude is None else exclude
+
+    labelnames = ["domain", "family", "member", "device_class"]
+    ds_state_str = Enum(
+        "device_state_str",
+        "State of the device",
+        labelnames,
+        states=list(DevState.names),
+    )
+    ds_state = Gauge("device_state", "State of the device", labelnames)
+    dev_access_count = Counter(
+        "device_access",
+        "How often this software device was accessed for commands or attributes",
+        labelnames,
+    )
+
+    @staticmethod
+    def get_device_attributes(cls):
+        return [
+            v
+            for k, v in cls.__dict__.items()
+            if isinstance(
+                v,
+                (
+                    attribute,
+                    Attribute,
+                ),
+            )
+        ]
+
+    def exclude_attr(self, name):
+        for p in self.exclude:
+            yield fnmatch.fnmatch(name, p)
+
+    @staticmethod
+    def wrap_method(cls, func, wrapper, post_execute=True, double_wrap=False):
+        if not double_wrap and hasattr(func, "__wrapped__"):
+            return
+
+        def decorated_pre(instance, *args, **kwargs):
+            wrapper(instance)
+            func(instance, *args, **kwargs)
+
+        def decorated_post(instance, *args, **kwargs):
+            wrapper(instance, *args, **kwargs)
+            func(instance, *args, **kwargs)
+
+        setattr(
+            cls,
+            func.__name__,
+            functools.update_wrapper(
+                decorated_post if post_execute else decorated_pre, func
+            ),
+        )
+
+    @staticmethod
+    def init_device_wrapper():
+        def new_init_device(instance):
+            metric_values = instance.get_name().split("/")
+            metric_values.append(str(instance.get_device_class().get_name()))
+            instance.metric_labels = metric_values
+
+        return new_init_device
+
+    def set_state_wrapper(self):
+        def new_set_state(instance, new_state):
+            try:
+                self.ds_state_str.labels(*instance.metric_labels).state(new_state.name)
+                self.ds_state.labels(*instance.metric_labels).set(new_state)
+            except ValueError as e:
+                logger.warning(e)
+
+        return new_set_state
+
+    def __call__(self, cls):
+        # we'll be doing very weird things if this class isn't
+        if not issubclass(cls, Device):
+            raise ValueError(
+                "device_metrics decorator is to be used on Tango Device classes only."
+            )
+
+        for v in self.get_device_attributes(cls):
+            if any(self.exclude_attr(v.attr_name)):
+                # @todo: add code in cae the attribute should be excluded
+                pass
+            else:
+                # @todo: add code in cae the attribute should be included
+                pass
+
+        cls.metric_labelnames = self.labelnames
+
+        self.wrap_method(
+            cls,
+            cls.init_device,
+            self.init_device_wrapper(),
+            post_execute=False,
+            double_wrap=True,
+        )
+
+        self.wrap_method(
+            cls,
+            cls.always_executed_hook,
+            lambda instance: self.dev_access_count.labels(
+                *instance.metric_labels
+            ).inc(),
+        )
+
+        self.wrap_method(cls, cls.set_state, self.set_state_wrapper())
+
+        return cls
+
+
+def device_metrics(*args, **kwargs):
+    return DeviceMetricsAttribute(*args, **kwargs)
diff --git a/tangostationcontrol/test-requirements.txt b/tangostationcontrol/test-requirements.txt
index 9c0aa76bd..450da51ea 100644
--- a/tangostationcontrol/test-requirements.txt
+++ b/tangostationcontrol/test-requirements.txt
@@ -24,7 +24,6 @@ testtools>=2.4.0 # MIT
 timeout-decorator>=0.5.0  # MIT
 xenon>=0.8.0 # MIT
 prometheus_client # Apache-2.0
-python-logstash-async # MIT
 pytest>=7.3.0 # MIT
 pytest-forked>=1.6.0 # MIT
 pytest-cov >= 3.0.0 # MIT
diff --git a/tangostationcontrol/test/common/test_calibration.py b/tangostationcontrol/test/common/test_calibration.py
index a87b4dc65..1916e557d 100644
--- a/tangostationcontrol/test/common/test_calibration.py
+++ b/tangostationcontrol/test/common/test_calibration.py
@@ -8,6 +8,7 @@ from test import base
 import numpy
 from numpy.testing import assert_array_equal
 
+from tangostationcontrol.common import consul
 from tangostationcontrol.common.calibration import (
     delay_compensation,
     loss_compensation,
@@ -24,7 +25,12 @@ class MockMinio:
         self.args = kwargs
 
 
+def new_lookup_service(*args, **kwargs):
+    yield consul.Service(host="test", port=9000, addr="test")
+
+
 @patch("tangostationcontrol.common.calibration.Minio")
+@patch("tangostationcontrol.common.consul.lookup_service", new=new_lookup_service)
 @patch.dict(
     os.environ,
     {"MINIO_ROOT_USER": "my_user", "MINIO_ROOT_PASSWORD": "my_passwd"},
diff --git a/tangostationcontrol/test/configuration/_mock_requests.py b/tangostationcontrol/test/configuration/_mock_requests.py
deleted file mode 100644
index d89e75cb5..000000000
--- a/tangostationcontrol/test/configuration/_mock_requests.py
+++ /dev/null
@@ -1,246 +0,0 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-
-import json
-
-POINTING_SCHEMA = """
-{
-  "type": "object",
-  "required": [
-    "angle1",
-    "angle2",
-    "direction_type"
-  ],
-  "properties": {
-    "angle1": {
-      "default": 0.6624317181687094,
-      "description": "First angle (e.g. RA)",
-      "title": "Angle 1",
-      "type": "number"
-    },
-    "angle2": {
-      "default": 1.5579526427549426,
-      "description": "Second angle (e.g. DEC)",
-      "title": "Angle 2",
-      "type": "number"
-    },
-    "direction_type": {
-      "default": "J2000",
-      "description": "",
-      "enum": [
-        "J2000",
-        "AZELGEO",
-        "LMN",
-        "SUN",
-        "MOON",
-        "MERCURY",
-        "VENUS",
-        "MARS",
-        "JUPITER",
-        "SATURN",
-        "URANUS",
-        "NEPTUNE",
-        "PLUTO"
-      ],
-      "title": "Reference frame",
-      "type": "string"
-    }
-  }
-}
-"""
-
-SAP_SCHEMA = """
-{
-  "$schema": "http://json-schema.org/draft-07/schema",
-  "type": "object",
-  "required": [
-    "subbands",
-    "pointing"
-  ],
-  "properties": {
-    "subbands": {
-      "type": "array",
-      "minItems": 1,
-      "items": {
-        "type": "number"
-      }
-    },
-    "pointing": {
-      "$ref": "pointing.json"
-    }
-  }
-}
-"""
-
-HBA_SCHEMA = """
-{
-  "$schema": "http://json-schema.org/draft-07/schema",
-  "type": "object",
-  "required": [
-    "tile_beam"
-  ],
-  "properties": {
-    "tile_beam": {
-      "$ref": "pointing.json"
-    },
-    "dab_filter": {
-      "type": "boolean",
-      "default": false,
-      "description": "Enable hardware filter on DAB frequencies"
-    },
-    "element_selection": {
-      "type": "string",
-      "default": "ALL",
-      "description": "Which element(s) to enable in each tile",
-      "enum": [
-        "ALL",
-        "GENERIC_201512"
-      ]
-    }
-  }
-}
-"""
-
-DITHERING_SCHEMA = """
-{
-  "$schema": "http://json-schema.org/draft-07/schema",
-  "type": "object",
-  "description": "Settings for adding dithering to the signal to increase its linearity",
-  "required": [
-    "enabled"
-  ],
-  "properties": {
-    "enabled": {
-      "type": "boolean",
-      "default": false
-    },
-    "power": {
-      "type": "number",
-      "description": "Power of dithering signal, in dBm",
-      "minimum": -25.0,
-      "maximum": -4.0,
-      "default": -4.0
-    },
-    "frequency": {
-      "type": "number",
-      "description": "Frequency of dithering signal, in Hz",
-      "default": 102000000
-    }
-  }
-}
-"""
-
-OBSERVATION_SETTINGS_SCHEMA = """
-{
-  "$schema": "http://json-schema.org/draft-07/schema",
-  "type": "object",
-  "required": [
-    "observation_id",
-    "stop_time",
-    "antenna_field",
-    "antenna_set",
-    "filter",
-    "SAPs"
-  ],
-  "properties": {
-    "observation_id": {
-      "type": "number",
-      "minimum": 1
-    },
-    "start_time": {
-      "type": "string",
-      "format": "date-time",
-      "default": "1970-01-01T00:00:00"
-    },
-    "stop_time": {
-      "type": "string",
-      "format": "date-time"
-    },
-    "lead_time": {
-      "type": "number",
-      "description": "Number of seconds to start before the provided start time, to account for initialising the on-line signal chain, and for possibly negative geometrical delay compensation.",
-      "default": 2.0,
-      "minimum": 0
-    },
-    "antenna_field": {
-      "default": "HBA",
-      "description": "Antenna field to use",
-      "type": "string",
-      "enum": [
-        "LBA",
-        "HBA",
-        "HBA0",
-        "HBA1"
-      ]
-    },
-    "antenna_set": {
-      "default": "ALL",
-      "description": "Fields & antennas to use",
-      "type": "string",
-      "enum": [
-        "ALL",
-        "INNER",
-        "OUTER",
-        "SPARSE_EVEN",
-        "SPARSE_ODD"
-      ]
-    },
-    "dithering": {
-      "$ref": "dithering.json"
-    },
-    "filter": {
-      "type": "string",
-      "enum": [
-        "LBA_10_90",
-        "LBA_10_70",
-        "LBA_30_90",
-        "LBA_30_70",
-        "HBA_170_230",
-        "HBA_110_190",
-        "HBA_210_250"
-      ]
-    },
-    "SAPs": {
-      "type": "array",
-      "minItems": 1,
-      "items": {
-        "$ref": "sap.json"
-      }
-    },
-    "first_beamlet": {
-      "type": "number",
-      "default": 0,
-      "minimum": 0
-    },
-    "HBA": {
-      "$ref": "hba.json"
-    }
-  }
-}
-"""
-
-
-def mocked_requests_get(*args, **kwargs):
-    class MockResponse:
-        def __init__(self, text, status_code):
-            self.text = text
-            self.status_code = status_code
-
-        def raise_for_status(self):
-            pass
-
-        def json(self):
-            return json.loads(self.text)
-
-    if args[0] == "http://http-json-schemas/pointing.json":
-        return MockResponse(POINTING_SCHEMA, 200)
-    elif args[0] == "http://http-json-schemas/sap.json":
-        return MockResponse(SAP_SCHEMA, 200)
-    elif args[0] == "http://http-json-schemas/hba.json":
-        return MockResponse(HBA_SCHEMA, 200)
-    elif args[0] == "http://http-json-schemas/dithering.json":
-        return MockResponse(DITHERING_SCHEMA, 200)
-    elif args[0] == "http://http-json-schemas/observation-settings.json":
-        return MockResponse(OBSERVATION_SETTINGS_SCHEMA, 200)
-
-    return MockResponse(None, 404)
diff --git a/tangostationcontrol/test/configuration/test_observation_settings.py b/tangostationcontrol/test/configuration/test_observation_settings.py
index 4f8765631..6220062ee 100644
--- a/tangostationcontrol/test/configuration/test_observation_settings.py
+++ b/tangostationcontrol/test/configuration/test_observation_settings.py
@@ -1,22 +1,17 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
+import json
 from datetime import datetime
-from unittest import mock
 
-import json
-import requests
-from jsonschema.exceptions import ValidationError, RefResolutionError
+from jsonschema.exceptions import ValidationError
 
 from tangostationcontrol.configuration import Pointing, ObservationSettings, Sap
-
 from test import base
-from test.configuration._mock_requests import mocked_requests_get
 
 
-@mock.patch("requests.get", side_effect=mocked_requests_get)
 class TestObservationSettings(base.TestCase):
-    def test_from_json(self, _):
+    def test_from_json(self):
         sut = ObservationSettings.from_json(
             '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", '
             '"antenna_field": "HBA", '
@@ -52,7 +47,7 @@ class TestObservationSettings(base.TestCase):
 
         self.assertEqual(sut.first_beamlet, 2)
 
-    def test_from_json_type_missmatch(self, _):
+    def test_from_json_type_missmatch(self):
         for json_str in [
             # observation_id
             '{"observation_id": "3", "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
@@ -70,7 +65,7 @@ class TestObservationSettings(base.TestCase):
             with self.assertRaises((ValidationError, ValueError), msg=f"{json_str}"):
                 ObservationSettings.from_json(json_str)
 
-    def test_from_json_missing_fields(self, _):
+    def test_from_json_missing_fields(self):
         complete_json = '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "HBA": {"tile_beam": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}, "first_beamlet": 2}'
 
         for field in (
@@ -92,7 +87,7 @@ class TestObservationSettings(base.TestCase):
             ):
                 ObservationSettings.from_json(json_str)
 
-    def test_to_json(self, _):
+    def test_to_json(self):
         sut = ObservationSettings(
             5,
             datetime.fromisoformat("2022-10-26T11:35:54.704150"),
@@ -116,19 +111,7 @@ class TestObservationSettings(base.TestCase):
             '"angle2": 4.4, "direction_type": "MOON"}}], "first_beamlet": 0}',
         )
 
-    def test_throw_exception_if_schema_not_available(self, mock_get):
-        ObservationSettings.VALIDATOR = None
-        mock_get.side_effect = requests.exceptions.Timeout
-        with self.assertRaises(RefResolutionError):
-            ObservationSettings.from_json(
-                '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", '
-                '"antenna_field": "HBA", '
-                '"antenna_set": "ALL", "filter": "HBA_110_190",'
-                '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}]}'
-            )
-        self.assertEqual(5, mock_get.call_count)
-
-    def test_throw_wrong_instance(self, _):
+    def test_throw_wrong_instance(self):
         for json_str in [
             '{"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}',
             '{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}',
diff --git a/tangostationcontrol/test/configuration/test_pointing.py b/tangostationcontrol/test/configuration/test_pointing.py
index a5ca5955d..1c864b99f 100644
--- a/tangostationcontrol/test/configuration/test_pointing.py
+++ b/tangostationcontrol/test/configuration/test_pointing.py
@@ -1,27 +1,21 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
-from unittest import mock
-
-import requests
-from jsonschema.exceptions import ValidationError, RefResolutionError
+from jsonschema.exceptions import ValidationError
 
 from tangostationcontrol.configuration import Pointing
-
 from test import base
-from test.configuration._mock_requests import mocked_requests_get
 
 
-@mock.patch("requests.get", side_effect=mocked_requests_get)
 class TestPointing(base.TestCase):
-    def test_from_json(self, _):
+    def test_from_json(self):
         ps = Pointing.from_json('{"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}')
 
         self.assertEqual(1.2, ps.angle1)
         self.assertEqual(2.1, ps.angle2)
         self.assertEqual("LMN", ps.direction_type)
 
-    def test_from_json_type_missmatch(self, _):
+    def test_from_json_type_missmatch(self):
         for json in [
             '{"angle1":"1.2", "angle2": 2.1, "direction_type":"LMN"}',
             '{"angle1":1.2, "angle2": "2.1", "direction_type":"LMN"}',
@@ -30,7 +24,7 @@ class TestPointing(base.TestCase):
             with self.assertRaises(ValidationError):
                 Pointing.from_json(json)
 
-    def test_from_json_missing_fields(self, _):
+    def test_from_json_missing_fields(self):
         for json in [
             '{"angle2": 2.1, "direction_type":"LMN"}',
             '{"angle1":1.2, "direction_type":"LMN"}',
@@ -39,15 +33,8 @@ class TestPointing(base.TestCase):
             with self.assertRaises(ValidationError):
                 Pointing.from_json(json)
 
-    def test_to_json(self, _):
+    def test_to_json(self):
         ps = Pointing(1.3, 2.3, "URANUS")
         self.assertEqual(
             ps.to_json(), '{"angle1": 1.3, "angle2": 2.3, "direction_type": "URANUS"}'
         )
-
-    def test_throw_exception_if_schema_not_available(self, mock_get):
-        Pointing.VALIDATOR = None
-        mock_get.side_effect = requests.exceptions.Timeout
-        with self.assertRaises(RefResolutionError):
-            Pointing.from_json('{"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}')
-        self.assertEqual(5, mock_get.call_count)
diff --git a/tangostationcontrol/test/configuration/test_sap_settings.py b/tangostationcontrol/test/configuration/test_sap_settings.py
index a9afbfd9e..b013f1feb 100644
--- a/tangostationcontrol/test/configuration/test_sap_settings.py
+++ b/tangostationcontrol/test/configuration/test_sap_settings.py
@@ -1,20 +1,14 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
 
-from unittest import mock
-
-import requests
-from jsonschema.exceptions import ValidationError, RefResolutionError
+from jsonschema.exceptions import ValidationError
 
 from tangostationcontrol.configuration import Pointing, Sap
-
 from test import base
-from test.configuration._mock_requests import mocked_requests_get
 
 
-@mock.patch("requests.get", side_effect=mocked_requests_get)
 class TestSapSettings(base.TestCase):
-    def test_from_json(self, _):
+    def test_from_json(self):
         sap = Sap.from_json(
             '{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}'
         )
@@ -24,7 +18,7 @@ class TestSapSettings(base.TestCase):
         self.assertEqual(sap.pointing.angle2, 2.1)
         self.assertEqual(sap.pointing.direction_type, "LMN")
 
-    def test_from_json_type_missmatch(self, _):
+    def test_from_json_type_missmatch(self):
         for json in [
             '{"subbands": ["3", 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}',
             '{"subbands": "3", "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}',
@@ -36,7 +30,7 @@ class TestSapSettings(base.TestCase):
             with self.assertRaises(ValidationError):
                 Sap.from_json(json)
 
-    def test_from_json_missing_fields(self, _):
+    def test_from_json_missing_fields(self):
         for json in [
             '{"subbands": [], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}',
             '{"pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}',
@@ -46,22 +40,13 @@ class TestSapSettings(base.TestCase):
             with self.assertRaises(ValidationError):
                 Sap.from_json(json)
 
-    def test_to_json(self, _):
+    def test_to_json(self):
         sut = Sap([3, 2, 1], Pointing(1.3, 2.3, "URANUS"))
         self.assertEqual(
             sut.to_json(),
             '{"subbands": [3, 2, 1], "pointing": {"angle1": 1.3, "angle2": 2.3, "direction_type": "URANUS"}}',
         )
 
-    def test_throw_exception_if_schema_not_available(self, mock_get):
-        Sap.VALIDATOR = None
-        mock_get.side_effect = requests.exceptions.Timeout
-        with self.assertRaises(RefResolutionError):
-            Sap.from_json(
-                '{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}'
-            )
-        self.assertEqual(5, mock_get.call_count)
-
-    def test_throw_wrong_instance(self, _):
+    def test_throw_wrong_instance(self):
         with self.assertRaises(ValidationError):
             Sap.from_json('{"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}')
-- 
GitLab