Frugal docs
Frugal

Frugal

Cheap, single-binary AWS + EKS observability — native endpoints over CloudWatch, no Prometheus, no database, live dashboard.

release ghcr license

Overview

Frugal is a single Go binary that is both an observability server (a live dashboard fed by CloudWatch, EKS, and free native collectors) and a metrics/logs agent (pushes host data from EC2/EKS nodes to a server). It ships as one binary with no external database and no Prometheus — recent data lives in in-memory ring buffers, optionally persisted to SQLite.

The point of the name: frugal prefers free, native endpoints (Redis INFO, OpenSearch stats, RabbitMQ management API, RDS Performance Insights, EKS metrics-server, a host agent) and uses paid CloudWatch only where there's no free alternative — with live cost visibility so you always know what a chart costs.

CloudWatch API ─────┐
Native endpoints ───┤
RDS Perf Insights ──┼─▶ collectors ─▶ ring buffers ─▶ HTTP + SSE ─▶ live dashboard
EKS APIs ───────────┤     (Go)         (+ optional SQLite)             (embedded HTML)
Agent push (/proc) ─┘

Two parts, one binary

In server mode the process runs as two parts: a web server that comes up immediately from bootstrap config (listen, data_dir, secret_key, auth) and always serves the login + dashboard; and a supervised data-collection service started from the runtime config (AWS/EKS/native targets + credentials). Changing settings tears down and relaunches the collectors without restarting the web server.

Quick start

Run the prebuilt multi-arch image from GHCR — no build, no Go toolchain:

# Docker
docker run -d --name frugal -p 8080:8080 \
  -e FRUGAL_SECRET_KEY=$(openssl rand -hex 32) \
  -e FRUGAL_DATA_DIR=/data \
  -v frugal-data:/data \
  ghcr.io/tools-plus/frugal:latest

# …or Docker Compose (docker-compose.prod.yml uses the same image)
FRUGAL_SECRET_KEY=$(openssl rand -hex 32) \
  docker compose -f docker-compose.prod.yml up -d

Then open http://localhost:8080, log in as admin / admin, set a new password, and configure what to collect under Admin ▸ Settings.

Keep FRUGAL_SECRET_KEY stable. It encrypts stored credentials; a changed key can't decrypt existing secrets. Images are published for amd64 + arm64 — pin a release with :vX.Y.Z instead of :latest.

Install

Docker (recommended)

docker pull ghcr.io/tools-plus/frugal:latest      # or :vX.Y.Z
docker run --rm ghcr.io/tools-plus/frugal version # confirm the build

Prebuilt binary

Linux amd64 & arm64 binaries are attached to every release (with sha256):

curl -sSL -o frugal \
  https://github.com/tools-plus/frugal/releases/latest/download/frugal-linux-amd64
chmod +x frugal
FRUGAL_SECRET_KEY=$(openssl rand -hex 32) FRUGAL_DATA_DIR=./data ./frugal

From source

Requires Go ≥ 1.24. The server needs CGO_ENABLED=1 for the SQLite driver (the default).

git clone https://github.com/tools-plus/frugal && cd frugal
go build -o frugal ./cmd/frugal
./frugal -config server.json

Run the server in EKS

deploy/k8s.yaml already points at ghcr.io/tools-plus/frugal:latest. Edit it to set the IRSA role ARN (ServiceAccount annotation) and, for persistence, a volume for data_dir. Then:

kubectl apply -f deploy/k8s.yaml
kubectl -n frugal port-forward svc/frugal 8080:80

Agents (EC2 / EKS)

Agents push host CPU/memory/network/load (from /proc) and tail log globs to the server, using the shared ingest token set in Admin ▸ Settings. Agents make zero AWS API calls — collecting host metrics this way is free.

# minimal agent config, via env
FRUGAL_SERVER_URL=http://frugal.internal:8080 \
FRUGAL_TOKEN=<shared-ingest-token> \
  ./frugal agent

Configuration

Frugal splits config into two layers:

Bootstrap (server.json / env)

The few things needed before the server can start. These live in server.json, and every key can be overridden by an environment variable (the env wins).

server.json keyenv overridemeaning
listenFRUGAL_LISTENbind address (default :8080)
data_dirFRUGAL_DATA_DIRdirectory for the SQLite databases (enables persistence)
secret_keyFRUGAL_SECRET_KEYencrypts stored credentials (AES-256-GCM)
auth.enabledFRUGAL_AUTH_ENABLEDrequire login (default true)
auth.db_pathFRUGAL_AUTH_DB_PATHcontrol-DB path (default <data_dir>/auth.db)
AWS_REGION / AWS_PROFILEAWS region / shared-config profile
{
  "listen": ":8080",
  "data_dir": "./data",
  "secret_key": "CHANGE_ME",
  "auth": { "enabled": true }
}

Runtime (Admin ▸ Settings)

Everything about what to monitor and how — AWS region/credentials/namespaces/poll intervals, Kubernetes, native targets, retention, the ingest token — is edited in the dashboard, stored encrypted in the control DB, and hot-applied (collectors restart; the web server keeps serving). On first boot with no stored config, frugal seeds the runtime config from any aws/kubernetes/native blocks present in server.json.

secret_key: without it the server still runs and the login works, but credentials can't be stored or used until you set one. Keep it out of source control; prefer the env var or a secret manager in production.

IAM permissions

Frugal only ever reads. Attach a policy with these read/list/describe actions to the IAM user whose keys you enter in Settings, or to the IRSA / instance role (see deploy/iam-policy.json):

cloudwatch:ListMetrics, cloudwatch:GetMetricData
ec2:DescribeInstances
rds:DescribeDBInstances, rds:DescribeDBClusters, pi:GetResourceMetrics
elasticache:DescribeCacheClusters, elasticache:DescribeReplicationGroups
es:ListDomainNames, es:DescribeDomains
mq:ListBrokers, mq:DescribeBroker
eks:ListClusters, eks:DescribeCluster
s3:ListAllMyBuckets
elasticloadbalancing:DescribeLoadBalancers, elasticloadbalancing:DescribeTargetGroups

Users & roles

The login is enabled by default. Users, roles, sessions, and the encrypted runtime config all live in the control DB (<data_dir>/auth.db).

Data sources & cost

Only CloudWatch GetMetricData is billable — everything else is free. Each series' origin is encoded in its ID prefix, so you always know what a chart costs.

SourcePrefixHowCost
CloudWatchcw|ListMetrics + GetMetricData
Native — Valkey/ElastiCachenv|INFO over Redis protocolfree
Native — OpenSearchnv|_cluster/health, _nodes/statsfree
Native — AmazonMQ (RabbitMQ)nv|management HTTP APIfree
RDS Performance Insightspi|pi:GetResourceMetricsfree (7-day)
EKS pods / nodesk8s|metrics-server (metrics.k8s.io)free
EC2 / host agentag|agent reads /proc, pushesfree

Keeping the CloudWatch bill down

Cost = metrics × polls. Levers in Admin ▸ Settings ▸ AWS:

The agent is always free. frugal agent never calls CloudWatch and never uses PutMetricData — it reads /proc and pushes to your server. Collecting EC2 host metrics via the agent costs $0, vs. per-read CloudWatch charges for the AWS/EC2 namespace.

Services covered

CloudWatch namespaces frugal collects by default: AWS/EC2, AWS/RDS, AWS/DocDB, AWS/ElastiCache, AWS/AmazonMQ, AWS/ES (OpenSearch), AWS/S3, AWS/ApplicationELB, AWS/NetworkELB, AWS/EKS (control plane), ContainerInsights. Native pollers add Valkey/ElastiCache, OpenSearch, and RabbitMQ; RDS Performance Insights adds DB load; the agent adds host metrics + logs.

Select which namespaces to collect (and trim paid ones you don't need) via the services checkboxes in Settings. Empty selection = all defaults.

EKS clusters

Clusters are found from three sources, deduplicated by name:

  1. Auto-discovery (default) — frugal lists your clusters via eks:ListClusters + DescribeCluster and mints a token per cluster, so node/pod metrics work straight from AWS credentials — no kubeconfig, no kubectl. Toggle under Settings ▸ Kubernetes.
  2. Uploaded kubeconfig — every context becomes a cluster; EKS exec-auth contexts are tokened automatically.
  3. Direct APIapi_url + bearer token entries.
Control plane vs nodes. The EKS control-plane charts come from CloudWatch's AWS/EKS namespace and appear from credentials alone. Node / pod metrics need a live cluster connection: a reachable API endpoint (public, or in-VPC for private endpoints), the IAM principal mapped in the cluster's access entries / aws-auth, and metrics-server installed. If Nodes shows 0, check those three.

Using the dashboard

CLI

frugal [server] [-config FILE]   # web dashboard + collectors (default mode)
frugal agent    [-config FILE]   # push host metrics + logs to a server
frugal version                   # print the version
frugal help                      # full usage, env vars, examples

Run frugal help for the full environment-variable list and examples. Key agent vars: FRUGAL_SERVER_URL, FRUGAL_TOKEN, FRUGAL_AGENT_KUBELOGS, FRUGAL_HOSTNAME.

Security

Troubleshooting

SymptomLikely cause / fix
EKS Nodes: 0 (control plane shows)Node/pod metrics need a live connection: reachable endpoint, access-entry RBAC, and metrics-server. Check kubectl top nodes; grep server logs for k8s: (403 = RBAC, 404 on metrics.k8s.io = no metrics-server, timeout = endpoint unreachable).
0 pts / "updated Xm ago"Normal when CloudWatch (5-min polls) is the only source — points arrive in bursts. Not a fault; the charts still update.
Short range looks sparse, 7d looks denseCloudWatch has ~1-min granularity with a few-minutes publish lag; coarse buckets on wide ranges hide it. Drag-zoom for detail.
No clusters / services at allCheck the AWS region + credentials in Settings, and the server log for aws: collector started and any GetMetricData / AccessDenied errors.
"credentials can't be saved"FRUGAL_SECRET_KEY isn't set — set it (env var) and restart.

Frugal is licensed under AGPL-3.0. Built by tools-plus. · GitHub