-
Notifications
You must be signed in to change notification settings - Fork 0
dashboard
Bart Kodden edited this page Jul 19, 2026
·
1 revision
A complete Lovelace dashboard for monitoring all Docker servers.
| 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.
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).
- In Home Assistant open any dashboard
- Click ⋮ → Edit dashboard → ⋮ → Raw configuration editor
- Add a new view and paste the YAML below
- Replace all occurrences of
server1andserver2with your actual server names (lowercase)
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
| 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 |
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 nameTo add a third or fourth server, duplicate the entire Server 2 block and:
- Replace all
server2with your new server name (lowercase) - Add a matching chip in the chips bar at the top
- Create a matching
input_boolean.docker_{servername}_expandedhelper
Links