Skip to content

dashboard

Bart Kodden edited this page Jul 19, 2026 · 1 revision

Dashboard

A complete Lovelace dashboard for monitoring all Docker servers.

Required HACS frontend cards

Card HACS name
Mushroom lovelace-mushroom
Auto Entities lovelace-auto-entities
Stack In Card lovelace-stack-in-card
Card Mod lovelace-card-mod
ApexCharts Card apexcharts-card

Install all of these via HACS → Frontend before adding the dashboard.

Required helpers

Create one toggle helper per server in Settings → Devices & Services → Helpers → Toggle:

Name Entity ID
Docker Server1 Expanded input_boolean.docker_server1_expanded
Docker Server2 Expanded input_boolean.docker_server2_expanded

Adjust names to match your server names exactly (lowercase, spaces → underscore).

How to add the dashboard

  1. In Home Assistant open any dashboard
  2. Click ⋮ → Edit dashboard → ⋮ → Raw configuration editor
  3. Add a new view and paste the YAML below
  4. Replace all occurrences of server1 and server2 with your actual server names (lowercase)

Entity ID pattern

Docker Manager creates entities following this pattern:

sensor.{servername}_containers_running
sensor.{servername}_cpu_usage
sensor.{servername}_memory_usage
sensor.{servername}_network_total_up
sensor.{servername}_network_total_down
sensor.{servername}_{containername}_state
sensor.{servername}_{containername}_cpu
sensor.{servername}_{containername}_memory
sensor.{servername}_{containername}_ports
sensor.{servername}_{containername}_compose_project
button.{servername}_{containername}_restart
button.{servername}_{containername}_fetch_logs

Cluster-wide entities (auto-created, no changes needed):

sensor.docker_manager_docker_servers_online
sensor.docker_manager_docker_total_running
sensor.docker_manager_docker_total_containers
binary_sensor.docker_manager_docker_cluster_healthy

Interactions

Action Result
Tap server header Expand / collapse container list
Tap container card More-info popup with full sensor history
Hold container card Restart the container immediately
Double-tap container card Fetch last 100 log lines → Bell notification

Dashboard YAML

title: Docker
path: docker-infra
icon: mdi:docker
cards:

  # ─────────────────────────────────────────────────────────────
  # CLUSTER CHIPS
  # Replace server1 / server2 with your actual server names
  # ─────────────────────────────────────────────────────────────
  - type: custom:mushroom-chips-card
    alignment: center
    chips:
      - type: template
        icon: >
          {{ 'mdi:shield-check'
             if is_state('binary_sensor.docker_manager_docker_cluster_healthy','on')
             else 'mdi:shield-alert-outline' }}
        icon_color: >
          {{ 'green'
             if is_state('binary_sensor.docker_manager_docker_cluster_healthy','on')
             else 'red' }}
        content: >
          {{ states('sensor.docker_manager_docker_servers_online') }}/{{
            state_attr('sensor.docker_manager_docker_servers_online','total_servers')
            | default(2) }} Online
        tap_action:
          action: none

      - type: template
        icon: mdi:docker
        icon_color: blue
        content: >
          {{ states('sensor.docker_manager_docker_total_running') }}/{{
            states('sensor.docker_manager_docker_total_containers') }} running
        tap_action:
          action: none

      # ── One chip per server ───────────────────────────────────
      # Duplicate this block for each additional server
      - type: template
        icon: mdi:server
        icon_color: >
          {{ 'green' if states('sensor.server1_containers_running')
             not in ['unavailable','unknown'] else 'red' }}
        content: >
          Server1 · {{ states('sensor.server1_containers_running') }}
        tap_action:
          action: none

      - type: template
        icon: mdi:server
        icon_color: >
          {{ 'green' if states('sensor.server2_containers_running')
             not in ['unavailable','unknown'] else 'red' }}
        content: >
          Server2 · {{ states('sensor.server2_containers_running') }}
        tap_action:
          action: none


  # ═════════════════════════════════════════════════════════════
  #  SERVER 1
  #  Replace every occurrence of "server1" with your server name
  # ═════════════════════════════════════════════════════════════
  - type: custom:stack-in-card
    mode: vertical
    cards:

      # ── Header — tap to expand/collapse ──────────────────────
      - type: custom:mushroom-template-card
        primary: "🖥  Server1"
        secondary: >
          Docker {{ states('sensor.server1_version') }} ·
          {{ states('sensor.server1_containers_running') }}/{{ states('sensor.server1_containers_total') }} containers
          · tap to {{ 'collapse' if is_state('input_boolean.docker_server1_expanded','on') else 'expand' }}
        icon: mdi:server-network
        icon_color: >
          {{ 'green' if states('sensor.server1_containers_running')
             not in ['unavailable','unknown'] else 'red' }}
        badge_icon: >
          {{ 'mdi:chevron-up' if is_state('input_boolean.docker_server1_expanded','on')
             else 'mdi:chevron-down' }}
        badge_color: grey
        fill_container: true
        tap_action:
          action: call-service
          service: input_boolean.toggle
          service_data:
            entity_id: input_boolean.docker_server1_expanded
        card_mod:
          style: |
            ha-card {
              background: linear-gradient(135deg,
                rgba(var(--rgb-primary-color,3,169,244),0.10) 0%,
                transparent 65%);
              border-left: 3px solid rgb(var(--rgb-primary-color,3,169,244));
              border-radius: 12px 12px 0 0;
              margin-bottom: 0;
              cursor: pointer;
            }

      # ── Graphs ───────────────────────────────────────────────
      - type: horizontal-stack
        cards:
          - type: custom:apexcharts-card
            header:
              show: true
              title: CPU & Memory
              show_states: true
              colorize_states: true
            graph_span: 1h
            yaxis:
              - id: cpu
                min: 0
                max: 100
                decimals: 1
                apex_config:
                  title:
                    text: "%"
              - id: mem
                opposite: true
                decimals: 0
                apex_config:
                  title:
                    text: MiB
            apex_config:
              chart:
                height: 150
                toolbar:
                  show: false
              stroke:
                curve: smooth
                width: 2
              grid:
                borderColor: rgba(255,255,255,0.05)
            series:
              - entity: sensor.server1_cpu_usage
                name: CPU
                color: "#03a9f4"
                yaxis_id: cpu
              - entity: sensor.server1_memory_usage
                name: RAM
                color: "#4caf50"
                yaxis_id: mem

          - type: custom:apexcharts-card
            header:
              show: true
              title: Network
              show_states: true
              colorize_states: true
            graph_span: 1h
            apex_config:
              chart:
                height: 150
                toolbar:
                  show: false
              stroke:
                curve: smooth
                width: 2
              grid:
                borderColor: rgba(255,255,255,0.05)
            series:
              - entity: sensor.server1_network_total_up
                name: "↑ Up"
                color: "#ff9800"
              - entity: sensor.server1_network_total_down
                name: "↓ Down"
                color: "#9c27b0"

      # ── Container list — shown when expanded ─────────────────
      - type: conditional
        conditions:
          - condition: state
            entity: input_boolean.docker_server1_expanded
            state: "on"
        card:
          type: custom:auto-entities
          show_empty: false
          card:
            type: grid
            columns: 1
            square: false
            padding: 4
          card_param: cards
          sort:
            method: state
            reverse: true
          filter:
            include:
              - entity_id: "sensor.server1_*_state"
                options:
                  type: custom:mushroom-template-card
                  multiline_secondary: false
                  icon: mdi:docker

                  # Container display name
                  primary: >
                    {% set base = config.entity
                       | replace('sensor.server1_','')
                       | replace('_state','') %}
                    {{ base | replace('_',' ') | title }}

                  # Stats line — all pulled from docker_manager sensors
                  secondary: >
                    {% set base = config.entity | replace('_state','') %}
                    {% set cpu     = states(base ~ '_cpu')             | float(0) | round(1) %}
                    {% set mem     = states(base ~ '_memory')          | float(0) | round(0) %}
                    {% set up      = states(base ~ '_network_up') %}
                    {% set down    = states(base ~ '_network_down') %}
                    {% set status  = states(base ~ '_status') %}
                    {% set compose = states(base ~ '_compose_project') %}
                    {% set ports   = states(base ~ '_ports') %}
                    🖥 {{ cpu }}%  💾 {{ mem }} MiB
                    {%- if up not in ['unavailable','unknown','none','0.0'] %} · ↑{{ up | float(0) | round(1) }}/↓{{ down | float(0) | round(1) }} KiB/s{% endif %}
                    {%- if compose not in ['unavailable','unknown','','none'] %} · 📦 {{ compose }}{% endif %}
                    {%- if ports not in ['unavailable','unknown','none',''] %} · 🔌 {{ ports }}{% endif %}
                    {%- if status not in ['unavailable','unknown',''] %} · {{ status }}{% endif %}

                  # Icon colour — orange if restarting or started <60s ago
                  icon_color: >
                    {% set base = config.entity | replace('_state','') %}
                    {% set s  = states(config.entity) %}
                    {% set ut = states(base ~ '_uptime') %}
                    {% set fresh = s == 'running'
                       and ut not in ['unknown','unavailable','none','']
                       and (now() - ut | as_datetime).total_seconds() < 60 %}
                    {% if fresh or s == 'restarting' %}orange
                    {% elif s == 'running' %}green
                    {% elif s == 'paused' %}amber
                    {% elif s in ['exited','dead'] %}red
                    {% else %}grey{% endif %}

                  badge_icon: >
                    {% set base = config.entity | replace('_state','') %}
                    {% set s  = states(config.entity) %}
                    {% set ut = states(base ~ '_uptime') %}
                    {% set fresh = s == 'running'
                       and ut not in ['unknown','unavailable','none','']
                       and (now() - ut | as_datetime).total_seconds() < 60 %}
                    {% if fresh %}mdi:alert-circle
                    {% elif s == 'running' %}mdi:play-circle
                    {% elif s == 'restarting' %}mdi:restart
                    {% elif s == 'paused' %}mdi:pause-circle
                    {% elif s in ['exited','dead'] %}mdi:stop-circle
                    {% else %}mdi:help-circle{% endif %}

                  badge_color: >
                    {% set base = config.entity | replace('_state','') %}
                    {% set s  = states(config.entity) %}
                    {% set ut = states(base ~ '_uptime') %}
                    {% set fresh = s == 'running'
                       and ut not in ['unknown','unavailable','none','']
                       and (now() - ut | as_datetime).total_seconds() < 60 %}
                    {% if fresh or s == 'restarting' %}orange
                    {% elif s == 'running' %}green
                    {% elif s in ['exited','dead'] %}red
                    {% else %}grey{% endif %}

                  # Tap → more-info popup
                  tap_action:
                    action: more-info

                  # Hold → restart container
                  hold_action:
                    action: call-service
                    service: button.press
                    service_data:
                      entity_id: >
                        button.server1_{{
                          config.entity
                          | replace('sensor.server1_','')
                          | replace('_state','')
                        }}_restart

                  # Double-tap → fetch logs → bell notification
                  double_tap_action:
                    action: call-service
                    service: button.press
                    service_data:
                      entity_id: >
                        button.server1_{{
                          config.entity
                          | replace('sensor.server1_','')
                          | replace('_state','')
                        }}_fetch_logs

            # Exclude host-level sensors that match *_state pattern
            exclude:
              - entity_id: "sensor.server1_containers_running"
              - entity_id: "sensor.server1_containers_total"
              - entity_id: "sensor.server1_cpu_usage"
              - entity_id: "sensor.server1_memory_usage"
              - entity_id: "sensor.server1_version"
              - entity_id: "sensor.server1_network_total_up"
              - entity_id: "sensor.server1_network_total_down"
              - state: "unknown"


  # ═════════════════════════════════════════════════════════════
  #  SERVER 2
  #  Duplicate this entire block for each additional server
  #  Replace every occurrence of "server2" with your server name
  # ═════════════════════════════════════════════════════════════
  - type: custom:stack-in-card
    mode: vertical
    cards:

      - type: custom:mushroom-template-card
        primary: "🖥  Server2"
        secondary: >
          Docker {{ states('sensor.server2_version') }} ·
          {{ states('sensor.server2_containers_running') }}/{{ states('sensor.server2_containers_total') }} containers
          · tap to {{ 'collapse' if is_state('input_boolean.docker_server2_expanded','on') else 'expand' }}
        icon: mdi:server-network
        icon_color: >
          {{ 'green' if states('sensor.server2_containers_running')
             not in ['unavailable','unknown'] else 'red' }}
        badge_icon: >
          {{ 'mdi:chevron-up' if is_state('input_boolean.docker_server2_expanded','on')
             else 'mdi:chevron-down' }}
        badge_color: grey
        fill_container: true
        tap_action:
          action: call-service
          service: input_boolean.toggle
          service_data:
            entity_id: input_boolean.docker_server2_expanded
        card_mod:
          style: |
            ha-card {
              background: linear-gradient(135deg,
                rgba(var(--rgb-primary-color,3,169,244),0.10) 0%,
                transparent 65%);
              border-left: 3px solid rgb(var(--rgb-primary-color,3,169,244));
              border-radius: 12px 12px 0 0;
              margin-bottom: 0;
              cursor: pointer;
            }

      - type: horizontal-stack
        cards:
          - type: custom:apexcharts-card
            header:
              show: true
              title: CPU & Memory
              show_states: true
              colorize_states: true
            graph_span: 1h
            yaxis:
              - id: cpu
                min: 0
                max: 100
                decimals: 1
                apex_config:
                  title:
                    text: "%"
              - id: mem
                opposite: true
                decimals: 0
                apex_config:
                  title:
                    text: MiB
            apex_config:
              chart:
                height: 150
                toolbar:
                  show: false
              stroke:
                curve: smooth
                width: 2
              grid:
                borderColor: rgba(255,255,255,0.05)
            series:
              - entity: sensor.server2_cpu_usage
                name: CPU
                color: "#03a9f4"
                yaxis_id: cpu
              - entity: sensor.server2_memory_usage
                name: RAM
                color: "#4caf50"
                yaxis_id: mem

          - type: custom:apexcharts-card
            header:
              show: true
              title: Network
              show_states: true
              colorize_states: true
            graph_span: 1h
            apex_config:
              chart:
                height: 150
                toolbar:
                  show: false
              stroke:
                curve: smooth
                width: 2
              grid:
                borderColor: rgba(255,255,255,0.05)
            series:
              - entity: sensor.server2_network_total_up
                name: "↑ Up"
                color: "#ff9800"
              - entity: sensor.server2_network_total_down
                name: "↓ Down"
                color: "#9c27b0"

      - type: conditional
        conditions:
          - condition: state
            entity: input_boolean.docker_server2_expanded
            state: "on"
        card:
          type: custom:auto-entities
          show_empty: false
          card:
            type: grid
            columns: 1
            square: false
            padding: 4
          card_param: cards
          sort:
            method: state
            reverse: true
          filter:
            include:
              - entity_id: "sensor.server2_*_state"
                options:
                  type: custom:mushroom-template-card
                  multiline_secondary: false
                  icon: mdi:docker
                  primary: >
                    {% set base = config.entity
                       | replace('sensor.server2_','')
                       | replace('_state','') %}
                    {{ base | replace('_',' ') | title }}
                  secondary: >
                    {% set base = config.entity | replace('_state','') %}
                    {% set cpu     = states(base ~ '_cpu')             | float(0) | round(1) %}
                    {% set mem     = states(base ~ '_memory')          | float(0) | round(0) %}
                    {% set up      = states(base ~ '_network_up') %}
                    {% set down    = states(base ~ '_network_down') %}
                    {% set status  = states(base ~ '_status') %}
                    {% set compose = states(base ~ '_compose_project') %}
                    {% set ports   = states(base ~ '_ports') %}
                    🖥 {{ cpu }}%  💾 {{ mem }} MiB
                    {%- if up not in ['unavailable','unknown','none','0.0'] %} · ↑{{ up | float(0) | round(1) }}/↓{{ down | float(0) | round(1) }} KiB/s{% endif %}
                    {%- if compose not in ['unavailable','unknown','','none'] %} · 📦 {{ compose }}{% endif %}
                    {%- if ports not in ['unavailable','unknown','none',''] %} · 🔌 {{ ports }}{% endif %}
                    {%- if status not in ['unavailable','unknown',''] %} · {{ status }}{% endif %}
                  icon_color: >
                    {% set base = config.entity | replace('_state','') %}
                    {% set s  = states(config.entity) %}
                    {% set ut = states(base ~ '_uptime') %}
                    {% set fresh = s == 'running'
                       and ut not in ['unknown','unavailable','none','']
                       and (now() - ut | as_datetime).total_seconds() < 60 %}
                    {% if fresh or s == 'restarting' %}orange
                    {% elif s == 'running' %}green
                    {% elif s == 'paused' %}amber
                    {% elif s in ['exited','dead'] %}red
                    {% else %}grey{% endif %}
                  badge_icon: >
                    {% set base = config.entity | replace('_state','') %}
                    {% set s  = states(config.entity) %}
                    {% set ut = states(base ~ '_uptime') %}
                    {% set fresh = s == 'running'
                       and ut not in ['unknown','unavailable','none','']
                       and (now() - ut | as_datetime).total_seconds() < 60 %}
                    {% if fresh %}mdi:alert-circle
                    {% elif s == 'running' %}mdi:play-circle
                    {% elif s == 'restarting' %}mdi:restart
                    {% elif s == 'paused' %}mdi:pause-circle
                    {% elif s in ['exited','dead'] %}mdi:stop-circle
                    {% else %}mdi:help-circle{% endif %}
                  badge_color: >
                    {% set base = config.entity | replace('_state','') %}
                    {% set s  = states(config.entity) %}
                    {% set ut = states(base ~ '_uptime') %}
                    {% set fresh = s == 'running'
                       and ut not in ['unknown','unavailable','none','']
                       and (now() - ut | as_datetime).total_seconds() < 60 %}
                    {% if fresh or s == 'restarting' %}orange
                    {% elif s == 'running' %}green
                    {% elif s in ['exited','dead'] %}red
                    {% else %}grey{% endif %}
                  tap_action:
                    action: more-info
                  hold_action:
                    action: call-service
                    service: button.press
                    service_data:
                      entity_id: >
                        button.server2_{{
                          config.entity
                          | replace('sensor.server2_','')
                          | replace('_state','')
                        }}_restart
                  double_tap_action:
                    action: call-service
                    service: button.press
                    service_data:
                      entity_id: >
                        button.server2_{{
                          config.entity
                          | replace('sensor.server2_','')
                          | replace('_state','')
                        }}_fetch_logs
            exclude:
              - entity_id: "sensor.server2_containers_running"
              - entity_id: "sensor.server2_containers_total"
              - entity_id: "sensor.server2_cpu_usage"
              - entity_id: "sensor.server2_memory_usage"
              - entity_id: "sensor.server2_version"
              - entity_id: "sensor.server2_network_total_up"
              - entity_id: "sensor.server2_network_total_down"
              - state: "unknown"

  # Add more server blocks here by duplicating the Server 2 block above
  # and replacing "server2" with your next server name

Adding more servers

To add a third or fourth server, duplicate the entire Server 2 block and:

  1. Replace all server2 with your new server name (lowercase)
  2. Add a matching chip in the chips bar at the top
  3. Create a matching input_boolean.docker_{servername}_expanded helper

Clone this wiki locally