A real-time web dashboard for monitoring your Minecraft server with RCON integration, SSH-based performance metrics, and SQLite persistence for player session tracking.
Disclaimer: Portions of this dashboard have been created using Claude. This project serves as an experiment for Claude integration with my knowledge of Python and containerized applications.
- Live Server Status - Online/offline detection via RCON
- Player Tracking - Real-time player list and join/leave detection
- Performance Metrics - TPS, Memory, CPU, and Disk usage via SSH
- Efficient Polling - Polls RCON and SSH every 5 seconds
- Instant API Responses - Sub-millisecond response times from in-memory cache
- Resilient - Shows last known good data if RCON/SSH temporarily fails
- Scalable - Multiple dashboards share the same cache (minimal server load)
- SQLite Persistence - Stores player join/leave events with session durations
- Today's Activity - View all players who joined today with total playtime
- Historical Data - Session data persists across dashboard restarts
- Real-time Status - Shows which players are currently online
- Auto-refresh - Dashboard updates every 5 seconds automatically
- Responsive Design - Clean, modern interface with gradient theme
- Activity Table - Sortable player activity with online status indicators
┌────────────────────────────────────────────────┐
│ FastAPI App │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Background Polling Task │ │
│ │ (every 5 seconds) │ │
│ └───┬──────────────────────────────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ RCON │ │ SSH │ │──SSH──> Minecraft Server Host
│ │ Service │ │ Service │ │ (192.168.x.x:22)
│ └────┬────┘ └────┬────┘ │ - CPU usage
│ │ │ │ - Memory usage
│ │ ┌────────────────┐ │ │ - Disk usage
│ └───>│ Cache Service │<──────┘ │ - TPS (if available)
│ └────────┬───────┘ │
│ │ │ │
│ ┌──────────┘ └────────────┐ │
│ ▼ ▼ │
│ ┌─────────┐ ┌────────────┐ │
│ │Database │ │ API │◄────┼─── Dashboard (Browser)
│ │ Service │ │ Endpoints │ │ - Server Status
│ │(SQLite) │ └────────────┘ │ - Player Activity
│ └─────────┘ │ - Performance Metrics
│ │ │
│ ▼ │
│ data/minecraft_dashboard.db │
│ (Player Sessions) │
└────────────────────────────────────────────────┘
│
│──RCON──> Minecraft Server
│ (192.168.x.x:25575)
│ - Online status
│ - Player list
│ - Max players
Benefits:
- Only 12 RCON calls/min regardless of how many users view the dashboard
- API responses return instantly from memory (<5ms) instead of waiting for RCON/SSH (50-200ms)
- Graceful degradation if Minecraft server goes offline
- Player session data persists across restarts
- Real performance metrics from the host system (not mocked)
- Python 3.11 or higher
- Minecraft server with RCON enabled (see RCON Setup Guide)
- Clone the repository
git clone https://github.com/anthonyi7/minecraft-dashboard.git
cd minecraft-dashboard- Install dependencies
pip install -r requirements.txt- Configure environment variables
Copy the example environment file:
cp .env.example .envEdit .env with your server details:
# Minecraft RCON Configuration
MC_SERVER_HOST=192.168.1.209 # Your server IP or hostname
MC_RCON_PORT=25575 # RCON port (default 25575)
MC_RCON_PASSWORD=your_password # RCON password from server.properties
# Database Configuration
DB_PATH=data/minecraft_dashboard.db # SQLite database path
# SSH Configuration (for performance metrics)
SSH_HOST=192.168.1.209 # SSH host (usually same as MC_SERVER_HOST)
SSH_PORT=22 # SSH port (default 22)
SSH_USER=ubuntu # SSH username
SSH_KEY_PATH=~/.ssh/mc_dashboard_key # Path to SSH private key
MC_SERVER_DIR=/home/ubuntu/minecraft # Minecraft server directory on remote hostNote: For SSH metrics to work, you need to set up SSH key authentication:
# Generate SSH key (if you don't have one)
ssh-keygen -t rsa -b 4096 -f ~/.ssh/mc_dashboard_key -N ""
# Copy public key to Minecraft server
ssh-copy-id -i ~/.ssh/mc_dashboard_key.pub ubuntu@192.168.1.209
# Test the connection
ssh -i ~/.ssh/mc_dashboard_key ubuntu@192.168.1.209 "uptime"- Start the dashboard
python -m uvicorn app:app --reload --host 0.0.0.0 --port 8000- Open in browser
http://localhost:8000
You should see:
Database initialized
No orphaned sessions found
Background polling task started - polling RCON every 5 seconds
Cache updated: Server online, 0/20 players
The dashboard is containerized and available as a Docker image for easy deployment.
- Clone the repository
git clone https://github.com/anthonyi7/minecraft-dashboard.git
cd minecraft-dashboard- Build the Docker image
docker build -t YOUR_DOCKERHUB_USERNAME/minecraft-dashboard:latest .- Push to DockerHub (optional)
docker login
docker push YOUR_DOCKERHUB_USERNAME/minecraft-dashboard:latestFor production deployments, Kubernetes provides orchestration, auto-restart, and persistent storage.
- Kubernetes cluster (GKE, EKS, AKS, or local k3s/minikube)
kubectlconfigured to access your cluster- SSH private key for accessing Minecraft server
- Clone the repository
git clone https://github.com/anthonyi7/minecraft-dashboard.git
cd minecraft-dashboard- Edit configuration
Update k8s/configmap.yaml with your server details:
data:
MC_SERVER_HOST: "192.168.1.209" # Your Minecraft server IP
MC_RCON_PORT: "25575"
SSH_HOST: "192.168.1.209"
SSH_USER: "ubuntu"
MC_SERVER_DIR: "/home/ubuntu/minecraft"Update k8s/deployment.yaml with your DockerHub image:
image: YOUR_DOCKERHUB_USERNAME/minecraft-dashboard:latest- Create Kubernetes Secrets
# Create SSH key secret from your private key file
kubectl create secret generic minecraft-dashboard-ssh-key \
--from-file=mc_dashboard_key=$HOME/.ssh/mc_dashboard_key
# Create RCON password secret
kubectl create secret generic minecraft-dashboard-secret \
--from-literal=MC_RCON_PASSWORD='your_rcon_password_here'- Deploy to Kubernetes
# Using kubectl apply
kubectl apply -f k8s/
# Or using Kustomize
kubectl apply -k k8s/- Access the dashboard
# Get the external IP (for LoadBalancer service)
kubectl get service minecraft-dashboard
# Or use port-forward for testing
kubectl port-forward service/minecraft-dashboard 8000:80Then open http://localhost:8000 in your browser.
┌─────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Service (LoadBalancer) │ │
│ │ External IP: x.x.x.x:80 │ │
│ └──────────────┬────────────────────────┘ │
│ │ │
│ ┌──────────────▼────────────────────────┐ │
│ │ Deployment (1 replica) │ │
│ │ - FastAPI container │ │
│ │ - Resource limits: 512Mi / 500m CPU │ │
│ │ - Health checks: /api/healthz │ │
│ └──┬────────────────────────────────┬───┘ │
│ │ │ │
│ ┌──▼──────────────┐ ┌───────────▼──┐ │
│ │ PVC (1Gi) │ │ Secrets │ │
│ │ SQLite database │ │ - SSH key │ │
│ │ ReadWriteOnce │ │ - RCON pass │ │
│ └─────────────────┘ └──────────────┘ │
└─────────────────────────────────────────────┘
| File | Description |
|---|---|
| k8s/deployment.yaml | Main application deployment with resource limits and health checks |
| k8s/service.yaml | LoadBalancer service exposing the dashboard on port 80 |
| k8s/pvc.yaml | PersistentVolumeClaim for SQLite database (1Gi) |
| k8s/configmap.yaml | Non-sensitive environment variables |
| k8s/secrets-example.yaml | Template for creating secrets (DO NOT commit with real values!) |
| k8s/kustomization.yaml | Kustomize configuration for easy manifest management |
Security: SSH key mounted from Kubernetes Secret (never baked into image) RCON password stored in Secret (not ConfigMap) Container runs as non-root user (UID 1000) Security context with dropped capabilities Consider using sealed-secrets or external-secrets for production
Persistence: SQLite database on PersistentVolume (survives pod restarts) Backup PVC regularly (SQLite database contains all session history) Consider using a managed database (PostgreSQL) for multi-replica deployments
Monitoring: Health checks configured (liveness and readiness probes) Add Prometheus metrics for production monitoring Set up alerts for pod restarts or health check failures
Scaling: SQLite limitation: Cannot scale beyond 1 replica (ReadWriteOnce PVC) For high availability, migrate to PostgreSQL or MySQL and use ReadWriteMany storage
# Build and push new image
docker build -t YOUR_DOCKERHUB_USERNAME/minecraft-dashboard:v1.0.1 .
docker push YOUR_DOCKERHUB_USERNAME/minecraft-dashboard:v1.0.1
# Update deployment
kubectl set image deployment/minecraft-dashboard \
dashboard=YOUR_DOCKERHUB_USERNAME/minecraft-dashboard:v1.0.1
# Or edit kustomization.yaml and reapply
kubectl apply -k k8s/All configuration is done via .env file (git-ignored for security):
| Variable | Description | Default |
|---|---|---|
MC_SERVER_HOST |
Minecraft server IP or hostname | localhost |
MC_RCON_PORT |
RCON port number | 25575 |
MC_RCON_PASSWORD |
RCON password (must match server.properties) | (required) |
| Variable | Description | Default |
|---|---|---|
DB_PATH |
Path to SQLite database file | data/minecraft_dashboard.db |
| Variable | Description | Default |
|---|---|---|
SSH_HOST |
SSH host for metrics collection | localhost |
SSH_PORT |
SSH port | 22 |
SSH_USER |
SSH username | ubuntu |
SSH_KEY_PATH |
Path to SSH private key (supports ~ expansion) |
~/.ssh/mc_dashboard_key |
MC_SERVER_DIR |
Minecraft server directory on remote host | /home/ubuntu/minecraft |
Edit the sleep interval in app.py background polling loop:
await asyncio.sleep(5) # Change 5 to desired seconds (line ~125)Note: Lower intervals increase RCON/SSH load. Recommended range: 5-30 seconds.
Edit cache_service.py to change when data is considered stale:
result["stale"] = age_seconds > 30 # Change 30 to desired seconds (line ~117)Health check for the dashboard backend.
Response:
{"ok": true}Complete server status with players, performance metrics, and metadata.
Response:
{
"online": true,
"players": {
"current": ["Steve", "Alex"],
"count": 2,
"max": 20
},
"performance": {
"tps": 19.87,
"memory_used_mb": 4096,
"memory_total_mb": 8192,
"cpu_percent": 45.2,
"disk_used_gb": 15.3,
"disk_total_gb": 50.0
},
"stale": false,
"last_updated": "2024-01-15T12:34:56Z",
"last_error": null
}Performance Metrics Explained:
tps: Server ticks per second (20.0 = perfect, <19.0 = lag)memory_used_mb/memory_total_mb: Java process memory usagecpu_percent: CPU usage of Minecraft process (0-100% per core)disk_used_gb/disk_total_gb: Disk usage of server directory
Player information only.
Response:
{
"current": ["Steve", "Alex"],
"count": 2,
"max": 20,
"stale": false,
"last_updated": "2024-01-15T12:34:56Z"
}Player activity statistics for today (midnight Pacific to now).
Response:
{
"date": "2024-02-15",
"timezone": "America/Los_Angeles (Pacific)",
"players": [
{
"name": "Steve",
"total_playtime_seconds": 7200,
"total_playtime_formatted": "2h 0m",
"session_count": 3,
"currently_online": false
},
{
"name": "Alex",
"total_playtime_seconds": 3600,
"total_playtime_formatted": "1h 0m",
"session_count": 1,
"currently_online": true
}
],
"summary": {
"unique_players": 2,
"total_playtime_seconds": 10800,
"total_sessions": 4
}
}Notes:
- "Today" is calculated based on Pacific time (midnight to now)
currently_onlinereflects real-time RCON data- Active sessions (players still online) are included with current duration
- Data persists across dashboard restarts via SQLite
minecraft_dashboard/
├── app.py # FastAPI application and background polling
├── cache_service.py # In-memory cache for server data
├── rcon_service.py # RCON communication with Minecraft server
├── db_service.py # SQLite database for player session tracking
├── ssh_service.py # SSH-based performance metrics collection
├── stats_service.py # Minecraft statistics collection (blocks, distance, playtime)
├── config.py # Environment variable configuration
├── requirements.txt # Python dependencies (FastAPI, mcrcon, paramiko, etc.)
├── .env # Local config (git-ignored, contains secrets)
├── .env.example # Template for .env file
├── .gitignore # Git ignore rules
├── RCON_SETUP.md # Guide to enable RCON on Minecraft server
├── Dockerfile # Multi-stage Docker build configuration
├── .dockerignore # Files to exclude from Docker build context
├── data/ # Auto-created directory for database (git-ignored)
│ └── minecraft_dashboard.db # SQLite database (player sessions)
├── k8s/ # Kubernetes manifests
│ ├── deployment.yaml # K8s Deployment with health checks and resource limits
│ ├── service.yaml # K8s Service (LoadBalancer)
│ ├── pvc.yaml # PersistentVolumeClaim for SQLite database
│ ├── configmap.yaml # Non-sensitive environment variables
│ ├── secrets-example.yaml # Template for creating Secrets (git-ignored)
│ └── kustomization.yaml # Kustomize configuration
├── static/
│ ├── index.html # Dashboard UI (server status, player activity table, leaderboards)
│ ├── styles.css # Styling (gradient theme, responsive tables)
│ └── app.js # Frontend JavaScript (API polling, UI updates)
└── README.md # This file
The SQLite database uses a single table for player session tracking:
CREATE TABLE player_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_name TEXT NOT NULL,
joined_at TIMESTAMP NOT NULL, -- UTC timestamp when player joined
left_at TIMESTAMP DEFAULT NULL, -- NULL = currently online
duration_seconds INTEGER DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Session Tracking Logic:
- Background polling compares current player list with previous poll
- New players trigger
INSERTwithleft_at=NULL(indicates online) - Players who left trigger
UPDATEto setleft_atand computeduration_seconds - On startup, orphaned sessions (left_at=NULL from crashes) are closed with 0 duration
Example Query (Today's Activity):
SELECT
player_name,
COUNT(*) as session_count,
SUM(CASE
WHEN left_at IS NULL THEN (strftime('%s', 'now') - strftime('%s', joined_at))
ELSE duration_seconds
END) as total_playtime_seconds
FROM player_sessions
WHERE joined_at >= datetime('now', 'start of day', '-8 hours') -- Pacific time
GROUP BY player_name
ORDER BY total_playtime_seconds DESC;The dashboard collects real performance metrics from the Minecraft server host via SSH:
| Metric | SSH Command | Description |
|---|---|---|
| CPU | ps -p {pid} -o %cpu |
CPU usage of Java/Minecraft process |
| Memory | ps -p {pid} -o rss |
RSS memory usage in MB |
| Disk | df -BG {server_dir} |
Disk usage of server directory |
| TPS | tail -100 logs/latest.log | grep -i tps |
Server TPS (if logged by mods) |
Process Discovery:
- Finds Minecraft PID:
pgrep -f 'java.*minecraft|java.*forge|java.*neoforge' - Caches PID to reduce SSH overhead
- Falls back to safe values (0.0, 20.0 TPS) on SSH failure
The server runs with auto-reload enabled. Any changes to Python files will automatically restart the backend.
Start development server:
python -m uvicorn app:app --reload --host 0.0.0.0 --port 8000Stop server:
Ctrl+C
View database contents:
sqlite3 data/minecraft_dashboard.db "SELECT * FROM player_sessions ORDER BY joined_at DESC LIMIT 10;"Clear database (fresh start):
rm data/minecraft_dashboard.db
# Database will be recreated on next app startFeel free to open issues or submit pull requests!
MIT
Built using FastAPI, RCON, and modern web technologies.