CarbonPanel is a lightweight self-hosted server monitoring panel built with FastAPI and Vue 3. It provides a clean dashboard for live system metrics, basic site/service management, and account settings with optional TOTP-based 2FA.
- Live dashboard for CPU, RAM, GPU, disk, network, process, and system metrics
- Real-time updates over WebSocket
- JWT authentication with optional TOTP 2FA
- Manage tracked sites/services with support for:
- service actions
- config file read/write
- log streaming
- Adjustable metric refresh interval and process display limits
- SQLite-backed backend with Alembic migrations
- Local development workflow via
make - Docker support for running the full self-hosted stack as a single image
- Python 3.11+
- FastAPI
- SQLAlchemy + Alembic
- SQLite (
aiosqlite) - psutil
- Vue 3
- TypeScript
- Vite
- Pinia
- Chart.js
.
├── backend/ # FastAPI app, database models, API routes, services, migrations
├── docker/ # nginx config and startup script for the combined image
├── frontend/ # Vue 3 app, widgets, pages, stores, API client
├── Dockerfile # combined frontend + backend container image
├── Makefile # local setup and dev commands
└── docker-compose.yml
CarbonPanel is intended as a self-hosted app. The published GHCR package is a single image containing the Vue frontend, nginx, and the FastAPI backend.
- Pull the latest image from GHCR:
docker pull ghcr.io/leodenglovescode/carbonpanel:latest- Run it directly with Docker:
docker run -d \
--name carbonpanel \
--network host \
--restart unless-stopped \
-e APP_PORT=8787 \
ghcr.io/leodenglovescode/carbonpanel:latestDefault app port is
8787. ChangeAPP_PORTif you want CarbonPanel to listen on a different port, including when using--network host.
Or run it with Docker Compose after changing the service to use the published image instead of build:
docker compose up -dWith the default setup, the app is available at http://localhost:8787.
- Copy environment files:
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env- Install and initialize everything:
make setup- Start both apps:
make dev- Frontend:
http://localhost:5173 - Backend API:
http://localhost:8000/api/v1
Pull and run the latest published image:
docker pull ghcr.io/leodenglovescode/carbonpanel:latest
docker run -d \
--name carbonpanel \
--network host \
--restart unless-stopped \
-e APP_PORT=8787 \
ghcr.io/leodenglovescode/carbonpanel:latestDefault app port:
8787. SetAPP_PORTto override it. This is especially useful withnetwork_mode: host, where port publishing with-pdoes not apply.
If you prefer Docker Compose, use an image-based service definition like this:
services:
carbonpanel:
image: ghcr.io/leodenglovescode/carbonpanel:latest
environment:
APP_PORT: ${APP_PORT:-8787}
restart: unless-stopped
network_mode: hostThen start it with:
docker compose up -dDefault URL:
- App:
http://localhost:8787
Note: the container uses
network_mode: hostso system/network metrics can reflect the host more accurately.
The backend reads settings from backend/.env. Important variables include:
SECRET_KEYADMIN_USERNAMEADMIN_PASSWORDDATABASE_URLCORS_ORIGINSMETRICS_INTERVAL_SECONDSPROCESS_LIMIT
The first-time setup seeds an admin user from the configured ADMIN_USERNAME and ADMIN_PASSWORD.
The frontend reads from frontend/.env:
VITE_API_BASE_URLVITE_WS_BASE_URL
For local Vite development, these can be left blank as noted in .env.example.
make setup # create venv, install backend/frontend deps, migrate DB, seed admin
make dev # run backend and frontend together
make backend # run backend only
make frontend # run frontend only- REST API prefix:
/api/v1 - Auth routes:
/auth/* - Settings routes:
/settings/* - Sites routes:
/sites/* - WebSocket endpoints are mounted separately from the REST prefix
- Default database: SQLite (
backend/carbonpanel.db) - Metrics collection starts with the FastAPI app lifespan
- The dashboard is auth-protected; unauthenticated users are redirected to
/login
Idea and logic by @leodenglovescode, Code assisted by Claude Code & GPT-5.4