Skip to content
Docs · Documentation

Kubyl Server

Kubyl Server: install and pairing

Run Kubyl Server in a container, pair the mobile app and configure it with flags and environment variables.

Kubyl Server runs on a machine you control, holds your kubeconfigs and credentials, and serves them over gRPC to Kubyl Mobile. The phone never gets a kubeconfig, an exec plugin or a cluster token: it pairs with the server and the server talks to your clusters. The desktop app does not need it.

Kubyl Server is a subscription (no free tier, no trial). The container image is private: your subscription comes with an OCI pull token for harbor.tyrola.dev/kubyl/server, which stops working when the subscription ends. The mobile app is free in the stores.

What you need

  • A Linux host (or any Docker or Podman host) that can reach your clusters' API servers.
  • A kubeconfig for each cluster, readable by the server.
  • A way for your phone to reach the server: the same network or, recommended, a private network such as Tailscale or WireGuard. The server is not meant to face the public internet. See Server operations.
  • A subscription, which you activate with a code from your customer portal at kubyl.dev. See Server license.

Install with a container

docker login harbor.tyrola.dev          # the pull token from your portal
docker run --rm -it \
  -p 8443:8443 \
  -v kubyl-config:/config \
  -v "$HOME/.kube:/kube:ro" \
  -e KUBYL_SERVER_PUBLIC_URL=https://kubyl.example.net:8443 \
  harbor.tyrola.dev/kubyl/server:latest
  • `-it` gives you the console, a full-screen terminal UI. Without a terminal (plain docker run -d, Kubernetes, systemd) the server writes logs to stdout instead, prints the pairing QR code at startup and offers everything through the web UI.
  • /config is the server's state: settings, the TLS certificate, paired devices, the encrypted credential store and its key, and the license. Keep it on a volume and back it up. See Credentials and sign-ins.
  • KUBYL_SERVER_PUBLIC_URL is the address your phone reaches the server at. It goes into the pairing link and into the self-signed certificate's names. Without it the server guesses its default route's address.
  • The container runs as UID 65532. Mount kubeconfigs readable by that user.

Tell the server where the kubeconfigs are in /config/settings.json. Create it before the first start, or edit it after and restart.

settings.json
{
  "kubernetes": {
    "load_default_kubeconfig": false,
    "load_kubeconfig_env": false,
    "kubeconfigs": ["/kube/config"]
  }
}

Without a kubernetes section the server reads $KUBECONFIG and ~/.kube/config of its user.

Command line and environment

FlagEnvironmentMeaning
--config-dirKUBYL_SERVER_CONFIG_DIRState directory. Default ~/.config/kubyl-server, mode 0700. The server refuses a directory others can read.
--listenKUBYL_SERVER_LISTENListen address. Default 0.0.0.0:8443; the image sets it.
--public-urlKUBYL_SERVER_PUBLIC_URLThe URL clients use.
--tls-cert, --tls-keyKUBYL_SERVER_TLS_CERT, KUBYL_SERVER_TLS_KEYYour own PEM certificate and key instead of the self-signed one.
--helm-writesKUBYL_SERVER_HELM_WRITESfalse turns Helm writes off. Default: on when helm is found. See What the mobile client can and cannot do.
--agentsKUBYL_SERVER_AGENTStrue turns AI agents on. Default: off. See AI agents.
--no-consoleNever open the console, even in a terminal.
--activation-codeKUBYL_SERVER_ACTIVATION_CODEActivate at startup. Prefer the variable. See Server license.
KUBYL_SERVER_KEYThe credential store's key (base64, 32 bytes) instead of a key file.

settings.json also takes "server": {"listen": …, "public_url": …} and "license": {"online": true, "service_url": …}. Changes need a restart.

Release builds and the container image have no --dev, --dev-token or license-test options: only paired devices get in.

Install with Helm

The private chart is in the same registry. Create an image pull secret from your subscription's pull token, log in to the registry for the chart, and install it with your values:

kubectl create secret docker-registry harbor --docker-server=harbor.tyrola.dev   --docker-username=<robot> --docker-password=<pull token>
helm registry login harbor.tyrola.dev
helm install kubyl oci://harbor.tyrola.dev/kubyl/helm/kubyl-server --version <v> -f values.yaml
ValueMeaning
imagePullSecretsNames of your pull secrets (the image is private).
publicUrlThe URL your phone uses; goes into the pairing link and certificate.
persistence.size, .storageClass, .existingClaimThe /config volume (default 1Gi). An init container makes it mode 0700 for UID 65532.
credentialKeySecretA Secret with the key key (openssl rand -base64 32) so the credential store's key lives apart from the volume.
activationCodeSecretA Secret with the key code: activates the install at first start (online only).
kubeconfigSecret, kubeconfigFilesA Secret whose keys are kubeconfig files, mounted at /kube (default file name config).
settingsYour settings.json as YAML. When set, the chart writes it at every start and owns the file: changes from the console or web UI do not survive a restart.
helm.writesfalse turns Helm writes off.
agents.enabled, .existingSecret, .limits, .disclosure, .resourcesAI agents; see AI agents.
service, ingress, gatewayAPI.tlsRoute, tlsExposure and certificates; see Server operations.
extraEnv, resources, nodeSelector, tolerations, affinityThe usual Kubernetes knobs.

First start and pairing

  1. Start the container with -it. The first thing the console shows is the web UI admin token, once. Copy it somewhere safe.
  2. On the Overview tab you see the pairing QR code, its link and how long it works. A code is good for one device and 10 minutes; R makes a new one. The QR code is 45x23 characters: in a small window press F to show it full screen, or use the link or the web UI's Pair page.
  3. In Kubyl Mobile, add a server and scan the code, or paste the link. The app pins the server's certificate (its public key hash is in the link), so there is no certificate authority to trust and nothing to type. See Pairing and servers.
  4. A server without a license is Unlicensed: the phone can pair and shows the license screen, nothing else works. Activate it next.

Paired devices appear on the Devices tab, where X revokes one: its sessions end at once and its streams close. A phone can also remove itself, and any paired phone can list and revoke devices, because there is one user per server. Failed pairing attempts are limited per address (5 in 10 minutes).

Something missing or wrong? Open an issue on GitHub.