GarageWare

GarageWare

Docker app from make-ware's Repository

Overview

Management console for a self-hosted Garage S3 cluster - buckets, access keys, node health, and a storage ledger for everyone sharing it. Garage gives you the storage; GarageWare gives you the front door: accounts, an object browser, S3 connection details, cluster status and per-node metrics, background repairs, and an append-only record of who is entitled to how much space. Runs as one container with one data path against a Garage cluster you already have.

garage-ware

Self-hosted management console for a Garage HQ S3-compatible storage cluster. One Docker image, one /data volume to back up. It manages a cluster you already run — it does not install one.

Users get self-service S3 access keys, buckets and quotas; admins get cluster status, storage grants, node repairs and a cluster event timeline. Next.js 16 + React 19 over PocketBase (identity, key/bucket mappings, storage claims) and the Garage admin API, proxied server-side so its token never reaches the browser.

Repo: make-ware/garage-ware · Images: dastron/garage-ware (Docker Hub) and ghcr.io/make-ware/garage-ware (GHCR) — both public, same digests, either works.

Quick start

You need a running Garage cluster with a layout applied and an admin token.

With Docker Compose — the recommended path:

git clone https://github.com/make-ware/garage-ware.git
cd garage-ware

cp .env.docker.example .env.docker   # set GARAGE_ADMIN_URL, GARAGE_ADMIN_TOKEN, GARAGE_S3_ENDPOINT
docker compose up -d
docker compose logs garage-ware | grep '\[setup\]'

With plain Docker (docker run) — no checkout needed: download .env.docker.example, save it as .env.docker (that filename, not .env) and fill in the same three values.

docker run -d \
  --name garage-ware \
  --restart unless-stopped \
  -p 8080:80 \
  -v $PWD/data:/data \
  --env-file .env.docker \
  dastron/garage-ware:latest      # or ghcr.io/make-ware/garage-ware:latest

docker logs garage-ware | grep '\[setup\]'

The health check ships in the image, so both paths get it.

The banner prints a URL and a one-time claim token. Open the URL, create your account, paste the token — you are now the administrator, and land on /admin/status.

/admin/status is the answer to "why isn't this working?" It names anything still misconfigured and the fix for each, and when it cannot reach a cluster at all it links to Garage's install docs and to the admin-only config generator, which writes a garage.toml for you to place on each node without ever handling a secret.

Pin a release with dastron/garage-ware:v1.8.3 instead of tracking latest; to build from your checkout, uncomment the build: block in docker-compose.yml. docker/README.md has the full environment reference, SMTP and bucket-CORS setup, and a troubleshooting table.

Admin token

Create one on any Garage node:

garage admin-token create --name garage-ware

For least-privilege scope and hardening, see docs/ARCHITECTURE.md#admin-token-scope.

Before you invite anyone

  • Sign-up is closed by default (SIGNUP_MODE); it stays open only while no administrator exists, which is why claiming promptly matters.
  • Configure email at /_/#/settings/mail, or invites and password resets silently never arrive.
  • A new user has no storage until you grant a claim from Admin → Claims.
  • Self-service claiming is off until FEATURE_NODE_CLAIMS / FEATURE_ASSET_CLAIMS are set; until then an admin assigns node owners and imports existing keys and buckets.

Backup

Everything that persists lives under /data — database, uploads, generated superuser password, first-run state. Snapshot it with tar czf backup.tgz -C $PWD data; restore by extracting on the new host and bringing the container up against the same path.

Develop locally

yarn install && yarn setup   # node 20+, yarn 4 via corepack; downloads the PocketBase binary
yarn dev                     # webapp :3000, PocketBase :8090, shared in watch mode

CONTRIBUTING.md covers scripts, adding a collection and the CI order; docs/ARCHITECTURE.md covers why the boundaries are where they are.

Releases

Conventional commits on main drive release-please; merging its release PR tags vX.Y.Z and calls docker-build.yml. That one build pushes each arch by digest to both registries, then assembles a manifest list per registry, so Docker Hub (dastron/garage-ware) and GHCR (ghcr.io/make-ware/garage-ware) serve byte-identical images under matching tags. Pushing to Docker Hub needs the DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repo secrets; GHCR uses the built-in GITHUB_TOKEN. A fork must edit the one hardcoded DOCKERHUB_IMAGE line — a Docker Hub namespace does not follow the git repo.

License

MIT — see LICENSE.

Media gallery

1 / 4

Install GarageWare on Unraid in a few clicks.

Find GarageWare in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for GarageWare Review the template variables and paths Click Install

Requirements

An existing Garage (garagehq.deuxfleurs.fr) S3 cluster with a layout applied, plus an admin token minted with `garage admin-token create --name garage-ware`. This container is the management console for that cluster - it does not store objects itself.

Download Statistics

287
Total Downloads

Related apps

Details

Repository
dastron/garage-ware:latest
Last Updated2026-08-21
First Seen2026-08-22

Runtime arguments

Web UI
http://[IP]:[PORT:80]/
Network
bridge
Shell
sh
Privileged
false

Template configuration

WebUI PortPorttcp

Host port for the web app, PocketBase API and admin (proxied by nginx on container port 80).

Target
80
Default
8890
Value
8890
App DataPathrw

Persistent storage: PocketBase database, uploads, the generated superuser credentials, and first-run state.

Target
/data
Default
/mnt/user/appdata/garage-ware
Value
/mnt/user/appdata/garage-ware
Garage Admin URLVariable

Base URL of your Garage cluster's ADMIN API - port 3903 by default, NOT the S3 port 3900. Example: http://YOUR-GARAGE-IP:3903. Without it there is no cluster status, no buckets, no keys and no metrics.

Target
GARAGE_ADMIN_URL
Garage Admin TokenVariable

Admin API token for the cluster. Mint one on a Garage node with: garage admin-token create --name garage-ware

Target
GARAGE_ADMIN_TOKEN
Garage S3 EndpointVariable

Your Garage S3 gateway URL - example: https://s3.example.com or http://YOUR-GARAGE-IP:3900. Required for the in-app file browser and the bucket connect page. The browser signs S3 requests against this endpoint directly, so each bucket must allow CORS from this app's origin (see the project README).

Target
GARAGE_S3_ENDPOINT
App Public URLVariable

How users actually reach this container, including the WebUI Port - example: http://YOUR-UNRAID-IP:8890 or https://garage.example.com. Used to build links in storage invites, usage alerts and the setup banner. If unset, those emails are skipped entirely - the invite row is still written, but nobody is told.

Target
APP_PUBLIC_URL
Sign-up ModeVariable

Who may create an account: 'closed' (admins create every account), 'invite' (only addresses holding a pending storage or admin invite), or 'open' (anyone who can reach the sign-up page). While no administrator exists yet sign-up stays open regardless, so you can create the account you will claim with - claim promptly.

Target
SIGNUP_MODE
Default
closed
Value
closed
Garage S3 RegionVariable

S3 region name your Garage cluster is configured with.

Target
GARAGE_S3_REGION
Default
us-east-1
Value
us-east-1
Public S3 EndpointVariable

Optional. The S3 URL advertised to users, when it differs from the CORS-enabled gateway the in-app browser must talk to. Falls back to Garage S3 Endpoint.

Target
GARAGE_PUBLIC_S3_ENDPOINT
Enable Node ClaimsVariable

Optional. Set to 'true' to let users claim cluster nodes (by proving the full node id) and grant storage sourced from them. Off by default - admins assign owners and grant all storage. Ownership decides who may grant, never how much.

Target
FEATURE_NODE_CLAIMS
Enable Asset ClaimsVariable

Optional. Set to 'true' to let users self-claim pre-existing Garage keys (by proving the secret) and the buckets those keys own. Off by default - admins onboard assets via import instead.

Target
FEATURE_ASSET_CLAIMS
Setup Claim TokenVariable

Optional. Supply the one-time first-run claim token yourself instead of letting the container mint one and print it to the log. Never written to disk.

Target
SETUP_CLAIM_TOKEN
Setup Owner EmailVariable

Optional and WEAKER than the claim token: auto-promotes whoever signs up with this address, but only while no administrator exists yet. An address is rarely secret and sign-up is open until the instance is claimed, so anyone who reaches the app first and guesses it becomes the administrator. Prefer the token unless this container is unreachable from the internet.

Target
SETUP_OWNER_EMAIL
Admin EmailVariable

Optional. Email for the PocketBase superuser. Leave BOTH this and Admin Password blank and the container generates a password on first boot, storing it at /data/pb_superuser.env (mode 0600) - back that file up. Setting only one of the two is a hard error.

Target
POCKETBASE_ADMIN_EMAIL
Admin PasswordVariable

Optional. Password for the PocketBase superuser. Set this only together with Admin Email - setting one without the other is a hard error and the container will not start.

Target
POCKETBASE_ADMIN_PASSWORD
Avg Object Size (MB)Variable

Optional. Average object size in MB. When set, each bucket also gets an object-count cap derived from its byte quota. Leave blank to apply no object cap.

Target
GARAGE_AVG_OBJECT_SIZE_MB
Node Metrics Retention (days)Variable

How many days of per-node metrics history to keep. 0 keeps everything forever.

Target
NODE_METRICS_RETENTION_DAYS
Default
90
Value
90