diff --git a/building_manager.py b/building_manager.py
index f8ae6ee46f9bf96d0188139441d6bb7e02013947..32eb9c18275b8fd6d9f20a1dd7f47a4f852cc3e7 100755
--- a/building_manager.py
+++ b/building_manager.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 """ Python module to assist creating and maintaining docker openHab stacks."""
 import crypt
+from enum import Enum
 import logging
 import os
 from hashlib import md5
@@ -42,10 +43,11 @@ COMPOSE_NAME = 'docker-stack.yml'
 SKELETON_NAME = 'docker-skeleton.yml'
 TEMPLATES_NAME = 'docker-templates.yml'
 CONFIG_DIRS = ['mosquitto', 'nodered', 'ssh',
-               'traefik', 'volumerize', 'postgres']
+               'traefik', 'volumerize', 'postgres', 'pb-framr']
 TEMPLATE_FILES = [
     'mosquitto/mosquitto.conf', 'nodered/nodered_package.json',
-    'nodered/nodered_settings.js', 'ssh/sshd_config', 'traefik/traefik.toml'
+    'pb-framr/logo.svg', 'nodered/nodered_settings.js',
+    'ssh/sshd_config', 'traefik/traefik.toml'
 ]
 EDIT_FILES = {
     "mosquitto_passwords": "mosquitto/mosquitto_passwords",
@@ -56,7 +58,8 @@ EDIT_FILES = {
     "known_hosts": "ssh/known_hosts",
     "backup_config": "volumerize/backup_config.json",
     "postgres_user": "postgres/user",
-    "postgres_passwd": "postgres/passwd"
+    "postgres_passwd": "postgres/passwd",
+    "pb_framr_pages": "pb-framr/pages.json"
 }
 CONSTRAINTS = {"building": "node.labels.building"}
 SERVICES = {
@@ -66,6 +69,10 @@ SERVICES = {
     "postgres": "postgres_X",
     "mqtt": "mqtt_X"
 }
+FRONTEND_SERVICES = {
+    "openhab": "OpenHAB",
+    "nodered": "Node-RED"
+}
 
 # Default Swarm port
 SWARM_PORT = 2377
@@ -73,6 +80,22 @@ SWARM_PORT = 2377
 UID = 9001
 # Username for admin
 ADMIN_USER = 'ohadmin'
+
+
+class Service(Enum):
+    SFTP = ("SFTP", "sftp_X", False)
+    OPENHAB = ("OpenHAB", "openhab_X", True, '/', 'dashboard')
+    NODERED = ("Node-RED", "nodered_X", True, 'nodered', 'ballot')
+    POSTGRES = ("Postgre SQL", "postgres_X", False)
+    MQTT = ("Mosquitto MQTT Broker", "mqtt_X", False)
+
+    def __init__(self, fullname, compose_entry, frontend,
+                 prefix=None, icon=None):
+        self.fullname = fullname
+        self.compose_entry = compose_entry
+        self.frontend = frontend
+        self.prefix = prefix
+        self.icon = icon
 # >>>
 
 
@@ -399,6 +422,25 @@ def generate_traefik_user_line(username, password):
     return line
 
 
+def generate_pb_framr_entry(host, service):
+    """Generates a single entry of the framr file
+
+    :host: host this entry is intended for
+    :service: entry from service enum
+    :returns: a dict fitting the asked entry
+
+    """
+    entry = {}
+    entry['title'] = service.fullname
+    if service.prefix == "/":
+        entry['url'] = f'http://{host}/'
+        pass
+    else:
+        entry['url'] = f'/{service.prefix}_{host}'
+    entry['icon'] = service.icon
+    return entry
+
+
 def generate_mosquitto_file(base_dir, username, password):
     """Generates a mosquitto password file using mosquitto_passwd system tool
 
@@ -531,6 +573,26 @@ def generate_volumerize_file(base_dir, hosts):
         base_dir, EDIT_FILES['backup_config'], configs, json=True)
 
 
+def generate_pb_framr_file(base_dir, frames):
+    """Generates config for pb framr landing page
+
+    :base_dir: path that contains custom config folder
+    :frames: a dict that contains hosts with matching name and services
+    """
+    configs = []
+
+    for f in frames:
+        building = {
+            'instance': f['building'],
+            'entries': [generate_pb_framr_entry(f['host'], s)
+                        for s in f['services'] if s.frontend]
+        }
+        configs.append(building)
+
+    create_or_replace_config_file(
+        base_dir, EDIT_FILES['pb_framr_pages'], configs, json=True)
+
+
 def create_or_replace_config_file(base_dir, config_path, content, json=False):
     """Creates or replaces a config file with new content
 
@@ -545,8 +607,6 @@ def create_or_replace_config_file(base_dir, config_path, content, json=False):
             json.dump(content, file, indent=2)
         else:
             file.write(content)
-
-
 # >>>
 
 
@@ -929,8 +989,15 @@ def init_menu(args):
     generate_id_rsa_files(base_dir)
     generate_host_key_files(base_dir, hosts)
 
+    frames = []
     for i, host in enumerate(hosts):
-        init_machine_menu(base_dir, host, i)
+        building, services = init_machine_menu(base_dir, host, i)
+        frames.append({'host': host,
+                       'building': building, 'services': services})
+
+    # When frames is not empty generate frame config
+    if frames:
+        generate_pb_framr_file(base_dir, frames)
 
     # print(answers)
     print(f"Configuration files for {stack_name} generated in {base_dir}")
@@ -949,25 +1016,25 @@ def init_machine_menu(base_dir, host, increment):
     :base_dir: Directory of config files
     :host: docker-machine host
     :increment: incrementing number to ensure ports are unique
+    :return: choosen building name and services
     """
     # Prompt for services
     building = qust.text(f'Choose a name for building on server {host}',
                          default=f'{host}', style=st).ask()
     services = qust.checkbox(f'What services shall {host} provide?',
-                             choices=generate_cb_choices(SERVICES.keys(),
-                                                         checked=True),
+                             choices=generate_cb_service_choices(checked=True),
                              style=st).ask()
-    if 'sftp' in services:
+    if Service.SFTP in services:
         add_sftp_service(base_dir, host, increment)
-    if 'openhab' in services:
+    if Service.OPENHAB in services:
         add_openhab_service(base_dir, host)
-    if 'nodered' in services:
+    if Service.NODERED in services:
         add_nodered_service(base_dir, host)
-    if 'mqtt' in services:
+    if Service.MQTT in services:
         add_mqtt_service(base_dir, host, increment)
-    if 'postgres' in services:
+    if Service.POSTGRES in services:
         add_postgres_service(base_dir, host)
-    print(building)
+    return building, services
 
 
 # *** Exec Menu Entries ***
@@ -1008,6 +1075,17 @@ def generate_cb_choices(list, checked=False):
     return [{'name': m, 'checked': checked} for m in list]
 
 
+def generate_cb_service_choices(checked=False):
+    """Generates checkbox entries for the sevice enum
+
+    :checked: if true, selections will be checked by default
+    :returns: A list of dicts with name keys
+    """
+    return [
+        {'name': s.fullname, 'value': s, 'checked': checked} for s in Service
+    ]
+
+
 def docker_client_prompt(message_details=''):
     """Show list of docker machines and return selection
 
diff --git a/template_configs/docker-skeleton.yml b/template_configs/docker-skeleton.yml
index 8e771c0c10ab5918d6fdf1ccef863d6c1b936b2e..55eeec9b1e202c5fae20866c36462ae058dc1e24 100644
--- a/template_configs/docker-skeleton.yml
+++ b/template_configs/docker-skeleton.yml
@@ -37,6 +37,10 @@ configs:
         file: ./postgres/user
     postgres_passwd:
         file: ./postgres/passwd
+    landing_logo:
+        file: ./pb-framr/logo.svg
+    landing_pages:
+        file: ./pb-framr/pages.json
 
 volumes:
     openhab_addons:
@@ -71,3 +75,25 @@ services:
             placement:
                 constraints:
                     - node.role == manager
+    landing:
+        image: doblix/pb-framr
+        networks:
+          - habnet
+        configs:
+          - source: landing_logo
+            target: /usr/share/nginx/html/logo.svg
+          - source: landing_pages
+            target: /usr/share/nginx/html/pages.json
+        deploy:
+            labels:
+              - traefik.docker.network=habnet
+              - traefik.port=80
+              - traefik.backend=landing
+              - traefik.main.frontend.priority=2
+              - traefik.main.frontend.redirect.regex=^(.*)/landing$$
+              - traefik.main.frontend.redirect.replacement=$$1/landing/
+              - traefik.main.frontend.rule=PathPrefix:/landing;ReplacePathRegex:^/landing/(.*)
+                /$$1
+            placement:
+                constraints:
+                    - node.role == manager
diff --git a/template_configs/docker-templates.yml b/template_configs/docker-templates.yml
index cea84f8f9162727f60ff9e53c9df6d123a42207b..e27a44d41c000d628a854f8d62a96094531c7945 100644
--- a/template_configs/docker-templates.yml
+++ b/template_configs/docker-templates.yml
@@ -37,6 +37,10 @@ configs:
         file: ./postgres/user
     postgres_passwd:
         file: ./postgres/passwd
+    landing_logo:
+        file: ./pb-framr/logo.svg
+    landing_pages:
+        file: ./pb-framr/pages.json
 
 volumes:
     openhab_addons:
diff --git a/template_configs/pb-framr/logo.svg b/template_configs/pb-framr/logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6a04fd05a804594625910d1289859cf4c5b022d7
--- /dev/null
+++ b/template_configs/pb-framr/logo.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   width="105.83334mm"
+   height="105.83334mm"
+   viewBox="0 0 105.83334 105.83334"
+   version="1.1"
+   id="svg8">
+  <defs
+     id="defs2" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     transform="translate(-28.600533,-86.069031)">
+    <circle
+       style="opacity:1;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.12197291;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path881"
+       cx="81.517204"
+       cy="138.9857"
+       r="52.916672" />
+    <g
+       transform="matrix(1.4629556,0,0,1.4629556,-15.525955,-77.21686)"
+       id="g891"
+       style="fill:#ffffff">
+      <text
+         id="text859"
+         y="143.47997"
+         x="52.784073"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:OpenSans;-inkscape-font-specification:OpenSans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+         xml:space="preserve"><tspan
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.69999981px;font-family:OpenSans;-inkscape-font-specification:OpenSans;fill:#ffffff;stroke-width:0.26458332"
+           y="143.47997"
+           x="52.784073"
+           id="tspan857">Your</tspan></text>
+      <text
+         id="text859-8"
+         y="158.60701"
+         x="48.7006"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:Z003;-inkscape-font-specification:Z003;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+         xml:space="preserve"><tspan
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:OpenSans;-inkscape-font-specification:'OpenSans Bold';fill:#ffffff;stroke-width:0.26458332"
+           y="158.60701"
+           x="48.7006"
+           id="tspan857-9">Logo</tspan></text>
+    </g>
+  </g>
+</svg>