# PeaNUT puts a dashboard on top of a NUT server, and its config lives in /config

> PeaNUT is a small TypeScript and Next.js dashboard for UPS devices exposed by Network UPS Tools, with widgets, an HTTP API, Prometheus and InfluxDB integrations, and a single user login. It runs as a container that expects a writable /config directory owned by UID 1000.

**Brandawg93/PeaNUT** — A tiny dashboard for Network UPS Tools

- Repository: https://github.com/Brandawg93/PeaNUT
- Stars: 1,712 · Forks: 51
- Language: TypeScript
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/brandawg93-peanut

## The container runs as 1000:1000 and that single fact causes most installs to fail

The docker run example in the README is short enough to copy without reading, which is also why it misleads people:

```bash
docker run -v ${PWD}/config:/config -p 8080:8080 --restart unless-stopped \
--env WEB_PORT=8080 brandawg93/peanut
```

The compose file then adds the comment the quick start omits. The container runs as UID and GID 1000:1000, and the host directory mounted at /config must be writable by that user, or settings made in the web UI will not survive a restart. The documented symptom is an EACCES permission denied error on /config in the logs, and the two documented remedies are chowning the host directory to 1000:1000 or setting user in the compose file to match the owner of the host directory.

The last line of that comment is the part to note: PUID and PGID environment variables are not supported. Many container images offer those, and PeaNUT deliberately does not, so a deployment that relies on a fixed uid will fail here in a way that looks like a bug until you read the compose comment.

Platform support is narrow in the same spirit. The README lists linux/amd64 and linux/arm64, the latter covering Raspberry Pi 4 and newer, and states that arm/v7 hardware such as the Raspberry Pi 3 stopped being supported as of version 5.17.0.

## Everything is an environment variable, and there are ten of them

PeaNUT is configured primarily through its UI, but the container contract is a flat set of environment variables, which is what you need for an orchestrator. WEB_HOST defaults to localhost and WEB_PORT to 8080, so a container that only exposes the port still binds locally unless you change the host.

Authentication is three of the ten. WEB_USERNAME and WEB_PASSWORD are the initial credentials used for auto-creation on startup, and AUTH_DISABLED set to true removes authentication entirely. Two more handle TLS directly rather than delegating it: SSL_CERT_PATH and SSL_KEY_PATH point at a certificate and private key, which matters when the dashboard should not sit behind a proxy at all.

BASE_PATH exists for the reverse proxy case, and the README points to a wiki page for the full setup with a specific warning that missing favicon or font loading errors usually mean a missing sub_filter rule. The last two are operational switches: DEBUG enables debug logging for server components, and DISABLE_CONFIG_FILE set to true turns off all config file saving, which is the variable to reach for when a deployment should be driven entirely from the UI without state written to the volume.

## Single user auth, bcrypt, and a setup page on first run

The authentication model is deliberately small. PeaNUT provides a single user authentication system and stores passwords with bcrypt hashing, so there is no user table to seed and no role to assign.

On the first run, if no user is configured, the application redirects to a setup page to create the administrator account. Setting WEB_USERNAME and WEB_PASSWORD skips that step and creates the account on startup if it does not already exist, which is what makes an unattended container deployment possible.

API calls require Basic Authentication unless authentication is disabled, using an Authorization header with the Base64 encoded credentials. That is the whole mechanism: no tokens, no scopes, no per endpoint keys. For a dashboard reachable only from a private network that is proportionate. For anything exposed beyond that, the README offers no second factor and no token option, which is the boundary to check before this becomes your monitoring source of truth.

There is also a separate feature called direct NUT server access via terminal, which is a shell against the upstream server rather than through this API.

## An API that covers devices, and integrations that do not go through it

The HTTP API is a conventional REST surface under /api/v1, and the documented endpoints show how far it reaches. GET /api/ping is a health check that returns pong, which is what the repository's healthcheck.mjs would be probing. GET /api/v1/info returns information about the application itself, and two endpoints report on the upstream server rather than on PeaNUT: GET /api/v1/version for the NUT server version currently in use and GET /api/v1/netversion for the network protocol version.

Device data comes from GET /api/v1/devices for all UPS devices and GET /api/v1/devices/[ups] for one, with a vars endpoint beneath it for all variables on that device. Those are the endpoints a script would use.

Two monitoring paths deliberately bypass that API. InfluxDB v2 integration exists for feeding data into Grafana, and Prometheus support covers scraping and alerting. The README also lists customizable widgets for Homepage dashboard integration, so there are three distinct consumers of the same status data with three different shapes of configuration.

The repository ships worked examples under examples/, with basic, influxdb and prometheus directories, and the repository's own docker-compose.yml runs brandawg93/nut-upsd alongside rather than pointing at a real UPS, exposing port 3493 with API_PASSWORD set to a test value.

## Building locally needs pnpm, and build:local exists for a reason

The source build path exists, and the interesting part is a script name that explains itself only after you read package.json. build is plain next build, while build:local is npm run build followed by a copy of .next/static into .next/standalone/.next/. That extra copy exists because start:local runs node .next/standalone/server.js, and the standalone output does not pick up static assets on its own.

The documented build sequence is:

```bash
git clone https://github.com/Brandawg93/PeaNUT.git
cd PeaNUT
npm i -g pnpm # only if you don't have pnpm installed
pnpm i
pnpm run build:local
pnpm run start:local
```

The repository declares pnpm 10.25.0 as its packageManager, and the Dockerfile enables pnpm through corepack, fetches with a frozen lockfile and a cache mount, then patches the build with next-ws. The runner stage is a different base image, dhi.io/node:24, and the Dockerfile creates /config in the build stage so the mount point always exists.

Testing is split into jest for units and playwright for end to end, and the e2e script runs the suite twice, once bare and once with WEB_USERNAME=admin and WEB_PASSWORD=test123 set, so both the authenticated and unauthenticated paths are exercised in one command.

## Deployment surface beyond Docker, and what the licence allows

The repository carries more than an application. There is a helm/ directory for Kubernetes, deploy.sh behind three package scripts for local, test and production targets, ct.yaml for chart testing,.actrc for running GitHub Actions locally, and a.devcontainer/ for editor setup. The.github/ workflows,.husky/ hooks, sonarcloud badge and CODE_OF_CONDUCT.md round out a project with a visible review process rather than a single maintainer shortcut.

AGENTS.md and CLAUDE.md at the root, along with.cursor/, indicate that the project documents conventions for automated coding assistants, which is unusual to see committed and worth reading if you plan to have a tool contribute.

Licensing is Apache-2.0, with the licence file at the root and an org.opencontainers.image.licenses label in the Dockerfile reading Apache-2.0, so the container image and the source carry the same terms. That is permissive enough for internal deployment and modification, with the attribution and patent clauses the licence normally carries.

The last push was on 2026-10-16, and the current version in package.json is 6.0.0, matching the v6.0.0 release from 2026-05-02.

## Conclusion

PeaNUT fits a home lab or a small rack where a NUT server already runs and you want battery state visible without SSH, and it does not fit a monitoring estate that needs multi user roles, since authentication is a single user account by design. Before the first restart, chown the host directory you mount at /config to 1000:1000 or set user in the compose file, because the container runs as that fixed UID and PUID and PGID are not supported, which is the one failure the README calls out by name.

## FAQ

### How do I run PeaNUT with Docker?

The README gives docker run with a volume at /config, port 8080 published, and WEB_PORT=8080, or a compose service using brandawg93/peanut. The container runs as UID and GID 1000:1000.

### Why do my PeaNUT settings disappear after a restart?

The mounted /config directory on the host is not writable by UID 1000:1000. The README documents chowning the host directory to 1000:1000 or setting user in the compose file, and notes PUID and PGID are not supported.

### Can I run PeaNUT without authentication?

Yes. Set the AUTH_DISABLED environment variable to true. Otherwise the first run redirects to a setup page, or WEB_USERNAME and WEB_PASSWORD create the account on startup.

### How does PeaNUT send data to Prometheus and Grafana?

The README lists InfluxDB v2 integration for monitoring through Grafana and separate Prometheus support for monitoring and alerting. Worked configurations are in the examples directory.

### Does PeaNUT still support a Raspberry Pi 3?

No. The README states that arm/v7 models such as the Raspberry Pi 3 are no longer supported as of version 5.17.0, and that linux/arm64 hardware is required.

## Sources

- [Brandawg93/PeaNUT on GitHub](https://github.com/Brandawg93/PeaNUT)
- [Issues](https://github.com/Brandawg93/PeaNUT/issues)
- [License: Apache-2.0](https://github.com/Brandawg93/PeaNUT/blob/main/LICENSE)
- [README](https://github.com/Brandawg93/PeaNUT/blob/main/README.md)
- [Releases](https://github.com/Brandawg93/PeaNUT/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/brandawg93-peanut
