A modern, lightweight web UI for managing HashiCorp Nomad clusters. Built with Hono + React 19 + Vite, deployable to Cloudflare Workers or Docker.
- Job Management: Create, edit, monitor, and delete jobs
- Container Configuration: Configure Docker/Podman containers with ease
- Environment Variables: Manage environment variables with sorting
- Network Configuration: Configure service networking and port mappings
- Service Health Checks: Set up and monitor service health checks
- Log Viewing: Real-time log streaming with stdout/stderr filtering
- Remote Exec: Secure terminal access to running containers via WebSocket
- Multi-Namespace Support: Work with multiple Nomad namespaces
- Dark Mode: Full dark mode support
Nomad Compass is designed with security in mind:
- httpOnly Cookies: Nomad ACL tokens are stored in httpOnly cookies, preventing XSS attacks from accessing tokens via JavaScript
- SameSite=Strict: Cookies are configured with SameSite=Strict to prevent CSRF attacks
- Secure Flag: In production, cookies are only sent over HTTPS
The Remote Exec feature uses a secure one-time ticket pattern:
- No Token in URL: The actual Nomad token never appears in WebSocket URLs
- HMAC-Signed Tickets: Short-lived tickets (30 seconds) are cryptographically signed
- CSRF Protection: Ticket requests require valid CSRF tokens
- Stateless Validation: No server-side storage needed - tickets are self-validating
Browser Server Nomad
│ │ │
│─── POST /api/auth/ws-ticket ──>│ │
│ (+ CSRF header) │ │
│<── { ticket: "signed..." } ────│ │
│ │ │
│─── WebSocket + ticket ────────>│ │
│ │── validate ticket │
│ │── get token from cookie │
│ │── connect with X-Nomad-Token>│
│<═══════════ relay ════════════>│<═════════════════════════════│
For production deployments, set a strong TICKET_SECRET environment variable:
# Generate a secure secret
openssl rand -hex 32
# Set in your environment
export TICKET_SECRET="your-generated-secret"- Runtime: Bun - Fast JavaScript runtime, package manager, and bundler
- API: Hono - Lightweight, ultrafast web framework
- Frontend: React 19 + React Router 7 + Tailwind CSS
- Build: Vite (frontend) + Bun bundler (backend)
- Deploy: Cloudflare Workers or Docker (Bun)
- Bun 1.0+
- A running Nomad cluster
- Nomad ACL token
- Clone the repository:
git clone https://github.com/ingvarch/nomad-compass.git
cd nomad-compass- Install dependencies:
bun install- Start the development server:
bun run dev- Open http://localhost:5173 in your browser.
On first access, you'll be prompted to enter your Nomad server address and ACL token.
- View Jobs: The jobs page shows all jobs across namespaces or within a selected namespace
- Job Details: Click on a job to view details, configurations, and task groups
- Create Jobs: Use the "Create Job" button to launch the job creation form
- Edit Jobs: Modify job configurations through the edit interface
- Manage Tasks: Configure resources, environment variables, and networking per task
Job detail pages include a logs section that allows:
- Selecting specific allocations and tasks
- Switching between stdout and stderr
- Auto-refreshing logs
- Manual refresh
Nomad Compass supports two deployment targets from the same codebase:
| Target | Best For | Latency | Infrastructure |
|---|---|---|---|
| Cloudflare Workers | Global access, edge performance | Low (edge) | Serverless |
| Docker | Self-hosted, on-premise, air-gapped | Depends on location | Container |
Recommended for global edge performance. Your app runs on Cloudflare's network close to users.
Prerequisites:
- Cloudflare account
- Wrangler CLI (
bun add -g wrangler)
Setup:
# Login to Cloudflare
wrangler login
# Set Nomad server address as a secret
wrangler secret put NOMAD_ADDR
# Enter: https://your-nomad-server.example.com
# Deploy
bun run deploy:cfYour app will be available at https://nomad-compass.<your-subdomain>.workers.dev
Custom domain (optional):
Add to wrangler.toml:
routes = [
{ pattern = "nomad.example.com", custom_domain = true }
]For self-hosted, on-premise, or air-gapped environments.
Build and run:
# Build the image
bun run docker:build
# or directly with Docker
docker build -t nomad-compass .
# Run
docker run -d \
--name nomad-compass \
-p 3000:3000 \
-e NOMAD_ADDR=http://your-nomad-server:4646 \
nomad-compassDocker Compose:
services:
nomad-compass:
build: .
# or use pre-built: image: ghcr.io/ingvarch/nomad-compass:latest
ports:
- "3000:3000"
environment:
- NOMAD_ADDR=http://nomad:4646
restart: unless-stoppedWith reverse proxy (Traefik example):
services:
nomad-compass:
build: .
environment:
- NOMAD_ADDR=http://nomad:4646
labels:
- "traefik.enable=true"
- "traefik.http.routers.nomad-compass.rule=Host(`nomad.example.com`)"
- "traefik.http.services.nomad-compass.loadbalancer.server.port=3000"| Variable | Description | Default | Used In |
|---|---|---|---|
NOMAD_ADDR |
Nomad server address | http://localhost:4646 |
Both |
PORT |
Server port | 3000 |
Docker only |
TICKET_SECRET |
HMAC secret for WebSocket auth tickets | Auto-generated | Both |
Two development modes are available, matching the two deployment targets:
Uses Wrangler to emulate the Cloudflare Workers environment locally.
bun run dev # Vite (frontend) + Wrangler (API)Configure your Nomad server in .dev.vars:
echo 'NOMAD_ADDR=http://localhost:4646' > .dev.varsUses the Bun backend directly, useful for Docker deployment testing.
bun run dev:bun # Vite (frontend) + Bun API serverSet your Nomad server via environment variable:
NOMAD_ADDR=http://localhost:4646 bun run dev:bunbun run dev:vite # Vite only (no backend)
bun run dev:worker # Wrangler only (no frontend dev)
bun run dev:api # Bun API only
bun run build # Build frontend
bun run build:bun # Build Bun server
bun run build:all # Build both
bun run lint # Run ESLint
bun run typecheck # Run TypeScript type checkingsrc/
├── api/ # Hono API layer
│ ├── app.ts # App factory
│ ├── routes/ # API routes
│ └── middleware/ # Auth middleware
├── client/ # React SPA
│ ├── pages/ # Page components
│ ├── components/ # Reusable components
│ ├── hooks/ # Custom hooks
│ ├── lib/ # Utilities and API client
│ └── context/ # React contexts
├── entry.cloudflare.ts # Cloudflare Workers entry
├── entry.bun.ts # Bun production entry
└── entry.bun.dev.ts # Bun dev entry (API only)
Contributions are welcome! Please feel free to submit a Pull Request.
MIT