Skip to content
Login

Service discovery

An agent can enumerate what is running around it and report the list to the dashboard. You pick something from that list and it becomes an endpoint — no hostname to look up, no port to remember.

Discovery is read-only. It never creates, changes, or deletes anything in your cluster or on your Docker host.

Seeing what an agent would find

Run this on the machine itself, before or after enrolling it:

Terminal window
hle agent services
Discovered services (k8s, docker)
Name Where Address Suggested label
grafana monitoring http://grafana.monitoring.svc… grafana
gitea git http://gitea.git.svc.cluster… gitea
plex media http://172.18.0.4:32400 plex
ColumnMeaning
NameThe Service or container name
WhereKubernetes namespace, or Compose project / provider
AddressThe URL the agent would use — this is what becomes service_url
Suggested labelA DNS-safe subdomain guess derived from the name

The address is always from the agent’s point of view. A cluster-internal DNS name is perfectly normal there and is exactly what you want stored.

FlagMeaning
--providerOnly show one provider: k8s or docker
--jsonMachine-readable output

Providers

Providers are self-detecting and opt-in. Each is asked whether it applies before it is used, so an agent on a plain VM finds nothing and reports nothing rather than erroring.

If nothing is active you’ll see:

No discovery providers are active here.

Kubernetes

Active when the agent runs inside a cluster — it needs the projected ServiceAccount credentials at /var/run/secrets/kubernetes.io/serviceaccount/ (token and CA certificate) and the KUBERNETES_SERVICE_HOST environment variable. Running the agent outside the cluster with a kubeconfig is not supported.

It lists Services cluster-wide, which needs a read-only ClusterRole:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: hle-agent-discovery
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list"]

get and list on Services is the entire permission set. No secrets, no pods, no write verbs anywhere.

Two things are filtered out deliberately:

  • Namespaces kube-system, kube-public, and kube-node-lease — cluster plumbing, and dangerous to put on the internet.
  • Ports named metrics, telemetry, health, or probe, and any non-TCP port — infrastructure rather than something you’d want to reach.

The address is the Service’s cluster DNS name, e.g. http://grafana.monitoring.svc.cluster.local:3000. Port 443, and ports named https or tls, are reported as https.

Docker

Active when the Docker socket is present — /var/run/docker.sock by default, overridable with DOCKER_SOCKET. It reads running containers and their ports via GET /containers/json.

Where a container has a network alias, that is preferred over its raw IP, so the resulting endpoint keeps working when the container is recreated with a new address. Compose projects are reported in the Where column.

Exposing something that was found

In the dashboard, open Connections → Agents → Discovered, pick a service, and confirm. The discovered address becomes the endpoint’s target and the suggested label becomes its subdomain — both editable before you save.

From there it is an ordinary endpoint: it appears in your tunnel list, obeys the same access controls, and the agent starts serving it within seconds.

Services the agent can tell are already exposed are marked as such, so you don’t create a duplicate.

Nothing showing up?

“No discovery providers are active here.” — neither provider detected its environment. On Kubernetes, check the ServiceAccount is mounted and that KUBERNETES_SERVICE_HOST is set. On Docker, check the socket path and that it is mounted into the container.

A service is missing. — check it isn’t in a skipped namespace, and that the port isn’t named metrics/telemetry/health/probe or using a non-TCP protocol. Only Services are listed on Kubernetes; bare Pods are not.

Some providers had trouble — one provider failed while another succeeded. The scan reports what it could reach rather than failing entirely; the message names the provider that had the problem.

Scans are bounded by a timeout, so a hung socket slows discovery down but never blocks the agent’s own work.