https://github.com/spy4x/zond
Internal health probe bridge for services behind SSO proxies
https://github.com/spy4x/zond
authelia deno docker gatus health-check monitoring probe self-hosted self-hosting sso-bypass
Last synced: about 18 hours ago
JSON representation
Internal health probe bridge for services behind SSO proxies
- Host: GitHub
- URL: https://github.com/spy4x/zond
- Owner: spy4x
- License: mit
- Created: 2026-06-25T20:43:14.000Z (10 days ago)
- Default Branch: main
- Last Pushed: 2026-07-03T18:47:53.000Z (2 days ago)
- Last Synced: 2026-07-03T20:26:26.487Z (1 day ago)
- Topics: authelia, deno, docker, gatus, health-check, monitoring, probe, self-hosted, self-hosting, sso-bypass
- Language: TypeScript
- Homepage: https://github.com/spy4x/zond
- Size: 7.81 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Zond π
[](https://github.com/spy4x/zond/pkgs/container/zond)
[](https://deno.com)
[](LICENSE)
[](https://github.com/spy4x/zond)
**Zond** (ΠΠΎΠ½Π΄ β Russian: "probe") is a tiny internal health probe bridge.
It receives external health check requests from monitoring systems
like [Gatus](https://github.com/TwiN/gatus) and forwards them to internal
containers via Docker DNS names β no authentication required, no internal
details exposed.
```
ββββββββββββ ββββββββββββ βββββββββββββββ
β Gatus ββββββΆβ Zond ββββββΆβ hl-metube β
β (cloud) β β (home) β β :8081 β
ββββββββββββ ββββββββββββ βββββββββββββββ
```
## Why Zond?
Monitoring services behind an SSO proxy (Authelia, Authentik) is painful.
You either accept `302` redirects as "healthy" or expose your apps with
dedicated monitoring users. Zond sits **_beside_** your containers (same
Docker network) and probes them directly, returning only `200` or `503`.
No auth bypass, no password management.
**One line in Gatus:**
```yaml
- name: Metube
url: "https://zond.example.com/health/metube"
conditions:
- "[STATUS] == 200"
```
## Features
- **Zero attack surface** β returns `200` or `503`, no internal URLs, no tokens
- **Single endpoint per service** β `GET /health/`
- **Bulk check** β `GET /` or `GET /health` lists all targets by name only
- **Config-driven** β YAML file (`zond.yml` by default, falls back to `zond.yaml`) or `ZOND_TARGETS` env var
- **Per-target timeout** β configure probe timeouts individually
- **Docker-native** β 30MB Deno image, no dependencies
- **Standalone binary** β `deno task compile` for bare-metal
## Quick start
### 1. Config file
```yaml
# zond.yml
port: 8080
targets:
- name: metube
url: http://hl-metube:8081/
- name: ollama
url: http://hl-ollama:11434/api/tags
timeout: 10000 # ms, default 5000
- name: grafana
url: http://hl-grafana:3000/api/health
timeout: 3000
```
```bash
docker run -p 8080:8080 \
-v $(pwd)/zond.yml:/app/zond.yml \
ghcr.io/spy4x/zond:latest
curl http://localhost:8080/health/metube
# ok
```
### 2. Environment variables
```bash
docker run -p 8080:8080 \
-e ZOND_TARGETS="metube=http://hl-metube:8081/,ollama=http://hl-ollama:11434/api/tags" \
ghcr.io/spy4x/zond:latest
```
Note: env var targets use the default timeout (5000ms). For custom timeouts,
use a config file.
### 3. Docker Compose
```yaml
services:
zond:
image: ghcr.io/spy4x/zond:latest
container_name: zond
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./zond.yml:/app/zond.yml:ro
networks:
- internal # same network as your probes
deploy:
resources:
limits:
memory: 32M
cpus: "0.1"
```
## API
### `GET /health/`
| Response | Status | Meaning |
|---|---|---|
| `ok\n` | `200` | Target responded 2xx/3xx |
| `unreachable\n` | `503` | Connection failed or 4xx/5xx |
| `unknown target: \n` | `404` | Target not in config |
### `GET /` or `GET /health`
Returns one line per target, overall `200` if all healthy:
```
OK metube
KO grafana
OK ollama
```
No internal URLs or other details exposed. Overall status is `503` if any
target is down.
## Configuration
| Option | Env var | Default | Description |
|---|---|---|---|
| `port` | `ZOND_PORT` | `8080` | HTTP listen port |
| `targets[].name` | β | required | URL slug in `/health/` |
| `targets[].url` | β | required | Internal URL to probe (Docker DNS or any) |
| `targets[].timeout` | β | `5000` | Request timeout in ms |
Config resolution order:
1. `ZOND_TARGETS` env var (default timeout applies to all)
2. `ZOND_CONFIG_PATH` env var β YAML file (supports `.yml` and `.yaml`)
3. `./zond.yml` in working directory (falls back to `./zond.yaml`)
## Docker
```bash
docker build -t ghcr.io/spy4x/zond:latest .
docker run --network proxy \
-v $(pwd)/zond.yml:/app/zond.yml \
ghcr.io/spy4x/zond:latest
```
## Compile (standalone)
```bash
deno task compile
./zond
```
## Development
```bash
deno task dev # run locally
deno task check # type-check
deno task lint # lint
deno task fmt # format
```
## Why not TCP checks?
TCP checks (`tcp://hl-metube:8081`) confirm a port is open. Zond performs
a real HTTP request and validates the response β catching cases where the
process is listening but returning 5xx errors.
## Why no authentication?
Zond returns only `ok` or `ko`. No data to protect, no session to steal,
no action to perform. Adding auth would reintroduce the exact problem Zond
solves. If you must, proxy it through your SSO β but the health endpoint
itself carries zero risk.
## License
MIT