Applications & App Store
The App Store lets you deploy containerized workloads to any Kubernetes-enabled device in your fleet. Apps are defined as Kubernetes manifests paired with a metadata.yaml file, and can be sourced from the built-in library, your own Git repositories, or Helm charts.
How It Works
App Source (Git / Helm / Local)
│
▼
Repository Manager ←── sync on schedule or manual trigger
│
▼
App Catalog ←── browse, search, filter by tag
│
▼
Configure Fields ←── fill in API keys, settings, secrets
│
▼
Template Substitution ←── ${MY_FIELD} replaced in manifests
│
▼
kubectl apply ──→ target device's K3s cluster
App Sources
| Type | Description |
|---|---|
| Local | Built-in apps shipped with Watchgrid (in apps/ directory) |
| Git | Any public or private Git repository |
| Helm | Helm chart repositories |
Adding an App Repository
Go to Applications → Repository Manager (gear icon in the top right).
Git Repository
Create a Git repository that follows the app directory layout, then add it to Watchgrid:
| Field | Example |
|---|---|
| Name | my-apps |
| Type | git |
| URL | https://github.com/youruser/watchgrid-apps.git |
| Branch | main |
| Username | (optional, for private repos) |
| Password | (optional, personal access token) |
| SSH Private Key | (optional, PEM-encoded key contents) |
For SSH-based access (git@host:org/repo.git), paste the contents of the PEM private key into the SSH Private Key field — Watchgrid stores it in the database and writes it to a temp file (mode 0600) only for the duration of each sync. The server uses it via GIT_SSH_COMMAND=ssh -i <tmp> -o IdentitiesOnly=yes -o StrictHostKeyChecking=no. Stored secrets (password and SSH key) are never returned by the API — repository listings expose has_password / has_ssh_key booleans instead.
Once added, Watchgrid clones the repo and scans it for apps. Use Sync to pull the latest changes at any time. The Sync All button fires every repository in parallel and surfaces per-repository status (Syncing…, Synced at HH:MM:SS, or Sync failed: <reason>) inline within each repository row.
Helm Repository
| Field | Example |
|---|---|
| Name | bitnami |
| Type | helm |
| URL | https://charts.bitnami.com/bitnami |
| Chart | nginx |
App Directory Layout
Each app lives in its own subdirectory within the repository:
my-apps-repo/
nginx-proxy/
metadata.yaml ← app definition and config fields
deployment.yaml ← Kubernetes Deployment + Service
mqtt-broker/
metadata.yaml
deployment.yaml
configmap.yaml ← any number of manifest files
grafana/
metadata.yaml
deployment.yaml
pvc.yaml
secret.yaml
All .yaml / .yml files except metadata.yaml are treated as deployable manifests and applied together on deploy.
Writing metadata.yaml
metadata.yaml defines how your app appears in the catalog and what configuration fields users fill in before deploying.
Minimal Example
name: "Nginx Proxy"
description: "A simple reverse proxy"
version: "1.0.0"
namespace: "default"
author: "Your Name"
tags:
- "web"
- "proxy"
Full Example with Config Fields
name: "MQTT Broker"
description: "Mosquitto MQTT broker for IoT devices"
version: "2.0.1"
namespace: "iot"
author: "Your Name"
tags:
- "iot"
- "mqtt"
- "networking"
icon: "📡"
expose_port: 1883
expose_protocol: tcp
min_replicas: 1
max_replicas: 3
default_replicas: 1
config_fields:
- name: "ADMIN_PASSWORD"
type: "secret"
description: "Broker admin password"
required: true
- name: "MAX_CONNECTIONS"
type: "string"
description: "Maximum concurrent client connections"
default: "1000"
required: false
- name: "ALLOW_ANONYMOUS"
type: "boolean"
description: "Allow anonymous client connections"
default: "false"
required: false
Metadata Fields
| Field | Type | Description |
|---|---|---|
name |
string | Display name in the app catalog |
description |
string | Short description shown in the catalog card |
version |
string | App version label |
namespace |
string | Default K8s namespace (default if omitted) |
author |
string | Informational — shown in app details |
tags |
list | Used for search and filtering |
icon |
string | Emoji shown in the catalog card |
expose_port |
int | Primary port the app listens on |
expose_protocol |
string | http, https, tcp, udp |
min_replicas |
int | Minimum replica count |
max_replicas |
int | Maximum replica count |
default_replicas |
int | Starting replica count |
config_fields |
list | Fields shown in the configure modal |
config_fields Schema
| Field | Type | Description |
|---|---|---|
name |
string | Key used in manifest template substitution |
type |
string | string, secret, or boolean |
description |
string | Help text shown in the UI |
default |
string | Pre-filled default value |
required |
bool | Blocks deployment if empty |
UI rendering:
- string → text input
- secret → masked password input
- boolean → true/false dropdown
Writing the Manifest
Kubernetes manifests are standard YAML. Use ${FIELD_NAME} placeholders anywhere you want user-provided values substituted.
Example: deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mqtt-broker
labels:
app: mqtt-broker
spec:
replicas: 1
selector:
matchLabels:
app: mqtt-broker
template:
metadata:
labels:
app: mqtt-broker
spec:
containers:
- name: mosquitto
image: eclipse-mosquitto:2
ports:
- containerPort: 1883
env:
- name: ALLOW_ANONYMOUS
value: "${ALLOW_ANONYMOUS}"
- name: MAX_CONNECTIONS
value: "${MAX_CONNECTIONS}"
resources:
limits:
memory: "128Mi"
cpu: "500m"
requests:
memory: "64Mi"
cpu: "100m"
---
apiVersion: v1
kind: Secret
metadata:
name: mqtt-broker-secret
type: Opaque
stringData:
ADMIN_PASSWORD: "${ADMIN_PASSWORD}"
---
apiVersion: v1
kind: Service
metadata:
name: mqtt-broker-service
spec:
selector:
app: mqtt-broker
ports:
- name: mqtt
protocol: TCP
port: 1883
targetPort: 1883
type: ClusterIP
How Template Substitution Works
Before applying the manifest, Watchgrid replaces all ${FIELD_NAME} placeholders with the values the user entered in the configure modal.
Substitution happens in three ways:
- Direct string replacement —
${FIELD_NAME}anywhere in the manifest - Environment variables —
- name: FIELD_NAMEfollowed byvalue:is updated automatically - Secret
stringData— keys instringDatamatching the field name are updated automatically
Configuration values are persisted per device — redeploying the same app on the same device reuses the saved configuration.
Deploying an App
- Select a device — only Kubernetes-enabled devices appear
- Select a namespace — discovered from the target device
- Browse the catalog — search by name or filter by tag
- Click Deploy on an app
- Fill in config fields — required fields must be completed
- Deploy or Install:
- Deploy — applies the manifest and starts the app immediately
- Install — downloads the app without starting it
Managing Deployed Apps
The deployed apps list shows per-device status with the following actions:
| Action | Description |
|---|---|
| Start | Start a stopped app |
| Stop | Scale replicas to zero (preserves config) |
| Uninstall | Remove all Kubernetes resources for this app |
Routines (Scheduling)
Routines let you schedule recurring actions on deployed apps.
Go to Applications → Routines and create a routine:
| Field | Description |
|---|---|
| Action | start, stop, or restart |
| Schedule | Cron expression — e.g. 0 2 * * * for 2:00 AM daily |
| Target | Specific app on a specific device |
| Timezone | Configurable per routine |
Example: Restart your monitoring stack every night at 3:00 AM: