Wieviel RAM frisst dein Server? Baue dein eigenes Monitoring-Dashboard
Du hast einen VPS, einen Homeserver oder einen Raspberry Pi – aber wie viel Arbeitsspeicher ist gerade frei? Wie hoch ist die CPU-Last? Klar, du könntest htop in der Konsole nutzen, aber wäre ein schickes Web-Dashboard nicht viel besser?
In diesem Tutorial bauen wir ein Echtzeit-Monitoring-Dashboard, das dir CPU, RAM und Disk-Auslastung live im Browser anzeigt – mit automatischen Updates via WebSockets.
🛠️ Projekt aufsetzen
mkdir my-server-monitor && cd my-server-monitor
npm init -y
Damit der Docker-Container später weiß, wie er die App starten soll (und welche Pakete benötigt werden), legen wir die package.json im Projektordner an:
my-server-monitor/package.json:
{
"name": "my-server-monitor",
"version": "1.0.0",
"scripts": { "start": "node server.js" },
"dependencies": {
"express": "^4.18.2",
"socket.io": "^4.7.2",
"os-utils": "^0.0.14"
}
}
Was macht os-utils? Es ist eine winzige Library, die den Zugriff auf Systemdaten (CPU, RAM) vereinfacht, ohne dass wir /proc/meminfo selbst parsen müssen.
1. Der Server: Systemdaten sammeln & senden
my-server-monitor/server.js:
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const os = require('os');
const osUtils = require('os-utils');
const path = require('path');
const app = express();
const server = http.createServer(app);
const io = new Server(server);
app.use(express.static(
path.join(__dirname, 'public')
));
// Systemdaten sammeln
function getSystemData() {
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
return {
hostname: os.hostname(),
platform: os.platform(),
uptime: os.uptime(),
memory: {
total: totalMem,
used: usedMem,
free: freeMem,
percent: Math.round(
(usedMem / totalMem) * 100
)
},
cpus: os.cpus().length
};
}
io.on('connection', (socket) => {
console.log('Dashboard verbunden');
// Sofort Daten senden
const send = () => {
osUtils.cpuUsage((cpuPercent) => {
const data = getSystemData();
data.cpu = Math.round(cpuPercent * 100);
socket.emit('stats', data);
});
};
send();
const interval = setInterval(send, 2000);
socket.on('disconnect', () => {
clearInterval(interval);
});
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(
`Monitor läuft auf http://localhost:${PORT}`
);
});
2. Das Dashboard-Frontend
my-server-monitor/public/index.html:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Server Monitor</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Courier New', monospace;
background: #0a0e17;
color: #c8d6e5;
min-height: 100vh;
display: flex;
justify-content: center;
padding: 2rem 1rem;
}
#dashboard {
width: 100%;
max-width: 600px;
}
h1 {
text-align: center;
color: #10b981;
margin-bottom: 0.5rem;
}
.subtitle {
text-align: center;
color: #576574;
font-size: 0.8rem;
margin-bottom: 2rem;
}
.card {
background: #141a2a;
border: 1px solid #1e293b;
border-radius: 10px;
padding: 1.25rem;
margin-bottom: 1rem;
}
.card-title {
font-size: 0.8rem;
color: #576574;
margin-bottom: 0.75rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.metric {
font-size: 2rem;
font-weight: bold;
}
.cpu { color: #38bdf8; }
.ram { color: #f59e0b; }
.uptime-val { color: #10b981; }
.bar-bg {
width: 100%;
height: 12px;
background: #1e293b;
border-radius: 6px;
margin-top: 0.75rem;
overflow: hidden;
}
.bar-fill {
height: 100%;
border-radius: 6px;
transition: width 0.5s ease;
}
.bar-fill.cpu-bar { background: #38bdf8; }
.bar-fill.ram-bar { background: #f59e0b; }
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: #576574;
margin-top: 0.5rem;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
background: #10b981;
border-radius: 50%;
margin-right: 6px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
</style>
</head>
<body>
<div id="dashboard">
<h1>📡 Server Monitor</h1>
<p class="subtitle">
<span class="status-dot"></span>
Live-Daten alle 2 Sekunden
</p>
<div class="card">
<div class="card-title">CPU Auslastung</div>
<div class="metric cpu" id="cpu">0%</div>
<div class="bar-bg">
<div class="bar-fill cpu-bar"
id="cpuBar"
style="width: 0%"></div>
</div>
<div class="info-row">
<span id="cpuCores">– Kerne</span>
<span id="platform">–</span>
</div>
</div>
<div class="card">
<div class="card-title">
Arbeitsspeicher (RAM)
</div>
<div class="metric ram" id="ram">0%</div>
<div class="bar-bg">
<div class="bar-fill ram-bar"
id="ramBar"
style="width: 0%"></div>
</div>
<div class="info-row">
<span id="ramUsed">– / – GB</span>
<span id="ramFree">– GB frei</span>
</div>
</div>
<div class="grid">
<div class="card">
<div class="card-title">Uptime</div>
<div class="metric uptime-val"
id="uptime">–</div>
</div>
<div class="card">
<div class="card-title">Hostname</div>
<div class="metric"
style="font-size:1rem;color:#e2e8f0"
id="hostname">–</div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
function formatBytes(bytes) {
return (bytes / 1073741824).toFixed(1);
}
function formatUptime(seconds) {
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
const m = Math.floor((seconds % 3600) / 60);
if (d > 0) return d + 'd ' + h + 'h';
if (h > 0) return h + 'h ' + m + 'm';
return m + 'm';
}
socket.on('stats', (data) => {
// CPU
document.getElementById('cpu')
.textContent = data.cpu + '%';
document.getElementById('cpuBar')
.style.width = data.cpu + '%';
document.getElementById('cpuCores')
.textContent = data.cpus + ' Kerne';
document.getElementById('platform')
.textContent = data.platform;
// RAM
document.getElementById('ram')
.textContent = data.memory.percent + '%';
document.getElementById('ramBar')
.style.width = data.memory.percent + '%';
document.getElementById('ramUsed')
.textContent =
formatBytes(data.memory.used) + ' / ' +
formatBytes(data.memory.total) + ' GB';
document.getElementById('ramFree')
.textContent =
formatBytes(data.memory.free) + ' GB frei';
// Info
document.getElementById('uptime')
.textContent = formatUptime(data.uptime);
document.getElementById('hostname')
.textContent = data.hostname;
});
</script>
</body>
</html>
3. Docker Deployment
my-server-monitor/docker-compose.yml:
version: '3.8'
services:
app:
image: node:18-alpine
working_dir: /app
volumes:
- .:/app
ports:
- "3000:3000"
command: sh -c "npm install && npm start"
docker compose up -d
Öffne http://localhost:3000 – dein Dashboard ist live!
⚠️ 📸 SCREENSHOT ANFRAGE: Hier einen Screenshot vom Dashboard mit aktiven CPU- und RAM-Balken einfügen.
🧠 Wie funktioniert es?
| Schritt | Was passiert |
|---|---|
| 1. Verbindung | Browser öffnet WebSocket via Socket.io |
| 2. Intervall | Server sammelt alle 2 Sekunden Systemdaten |
| 3. Push | Daten werden sofort an den Client gesendet |
| 4. Rendering | Frontend aktualisiert Balken und Zahlen |
Der Clou: Kein Polling! Der Server pusht die Daten aktiv zum Client. Das ist effizienter und fühlt sich deutlich flüssiger an als ein setInterval mit fetch.
📈 Erweiterungs-Ideen
| Feature | Schwierigkeit | Beschreibung |
|---|---|---|
| Disk-Auslastung | ⭐⭐ | df-Befehl auslesen und anzeigen |
| Netzwerk-Traffic | ⭐⭐⭐ | Eingehende/ausgehende Bytes tracken |
| Historische Daten | ⭐⭐⭐ | Messwerte in SQLite speichern und Graphen zeigen |
| Multi-Server | ⭐⭐⭐⭐ | Mehrere Server auf einem Dashboard |
| Alerts | ⭐⭐ | E-Mail-Warnung bei CPU > 90% |
Fazit
In wenigen Minuten hast du ein eigenes Server-Monitoring-Dashboard gebaut, das dir in Echtzeit zeigt, was auf deiner Maschine passiert. Kein Grafana-Setup, kein Prometheus – nur Node.js und ein Browser.
Perfekt für Homeserver, Raspberry Pis oder als Basis für ein größeres Monitoring-System. Happy Monitoring! 📡
Login