Because gruyère is full of holes!
Gruyere is a web dashboard for monitoring and syncing multiple Pi-hole instances from a single page (well... two actually). It is designed to be simple, fast, and easy to deploy—just like a good cheese board.
That said, Gruyère is not an alternative UI for Pi-hole: it just handles the synchronisation of instances from a defined reference instance.
- Apply settings from a reference Pi-Hole instance to all the other instances
- Perform actions on all Pi-hole instances at once (e.g. pause/restart blocking)
- Monitor multiple Pi-hole instances in real time
- See stats like total queries, blocked queries, and more
- Quick status overview for each instance
- Responsive UI, works on desktop and mobile
The easiest way to deploy Gruyere is using Docker Compose:
- Docker and Docker Compose installed
- Access to your Pi-hole instances and their API keys
-
Clone the repository:
git clone https://github.com/yourusername/gruyere.git cd gruyere -
Set up configuration files:
# Copy template files to create your config cp config/password.example.json config/password.json cp config/instances.example.json config/instances.json cp config/config.example.json config/config.json -
Configure your Pi-hole instances:
Edit
config/instances.jsonand add your Pi-hole instances:[ { "id": 1, "name": "Pi-hole Main", "url": "http://192.168.1.100", "apiKey": "your-pihole-api-key-here", "isReference": true, "sid": "", "csrf": "", "status": "active" } ]Finding your Pi-hole API key:
- Log in to your Pi-hole web interface
- Go to Settings → API / Web interface
- Click "Show API token" and copy it
-
Change the default password:
Edit
config/password.json:{ "password": "your-secure-password-here" } -
Configure environment variables (optional):
Create a
.envfile to customize settings:cp .env.example .env
Edit
.envand adjust as needed:# Set your actual domain/IP ORIGIN=http://192.168.1.50:3141 # Optional: Change port PORT=3141 # Optional: Set log level LOG_LEVEL=info
-
Start the application:
docker-compose up -d
-
Access Gruyere:
Open your browser and navigate to
http://localhost:3141(or your configured address)
# Start the container
docker-compose up -d
# Stop the container
docker-compose down
# View logs
docker-compose logs -f
# Rebuild after code changes
docker-compose up -d --build
# Check container status
docker-compose ps
# Restart the container
docker-compose restartThe config/config.json file contains application settings:
{
"isRefreshInstance": true, // Enable auto-refresh of instance status
"instanceRefreshInterval": 30, // Refresh interval in seconds
"synchronizeWithReference": "partial" // Sync mode: "partial" or "complete"
}| Variable | Default | Description |
|---|---|---|
PORT |
3141 |
Port the application listens on |
HOST |
0.0.0.0 |
Host binding (0.0.0.0 for all interfaces) |
ORIGIN |
http://localhost:3141 |
Origin URL for CORS and security |
NODE_ENV |
production |
Node environment |
LOG_LEVEL |
info |
Log level (fatal, error, warn, info, debug, trace) |
- Change the default password immediately after first deployment
- Keep API keys secure - never commit
config/*.jsonfiles (except templates) - Use HTTPS in production with a reverse proxy (nginx, Traefik, Caddy)
- Set correct ORIGIN to prevent CSRF attacks
- Restrict network access - only expose port 3141 to trusted networks
The default docker-compose.yml sets resource limits:
- CPU: 1 core max, 0.25 core reserved
- Memory: 512MB max, 128MB reserved
Adjust these in docker-compose.yml under deploy.resources if needed.
Gruyere uses a "reference instance" pattern:
- Mark one Pi-hole as your reference instance (set
isReference: true) - Configure groups, lists, clients, and domains on that instance
- Use Gruyere to sync those settings to other instances
Available sync operations:
- Update Groups - Sync group configurations
- Update Lists - Sync blocklists and allowlists
- Update Clients - Sync client group assignments
- Update Domains - Sync domain rules
- Update Gravity - Update all instances' blocklists
Check logs:
docker-compose logs gruyereCommon issues:
- Config files missing - ensure you copied the templates
- Port 3141 already in use - change
PORTin.env - Permission issues - ensure
./configdirectory is writable
-
Check network connectivity:
docker-compose exec gruyere curl -v http://your-pihole-ip -
Verify API key:
- Log in to Pi-hole web interface
- Go to Settings → API
- Verify the key matches what's in
instances.json
-
Check Pi-hole API is enabled:
- Ensure Pi-hole web interface is accessible
- API should be available at
http://your-pihole/api
- API key is incorrect or expired
- Edit the instance in Gruyere UI to update the API key
- Gruyere will re-authenticate automatically
- Pi-hole is offline or network is unreachable
- Check Pi-hole IP address and port
- Verify firewall rules allow traffic from Gruyere container
For local development without Docker:
# Install dependencies
npm install
# Start development server
npm run dev
# Type checking
npm run check
# Linting
npm run lint
# Format code
npm run format
# Build for production
npm run buildContributions are welcome! Please feel free to submit a Pull Request.
MIT
