Development - Testing an external integration with Gladys locally

This tutorial describes how to develop and test an external integration without rebuilding a
Docker image for each change
: your code runs as a simple Node.js process on your machine and connects to a Gladys instance running locally.

This is the fastest development loop, and it complements step 4 of the official documentation.

What you will do

An external integration authenticates with Gladys using a token (a JWT) and a
selector (its unique identifier). These two values are created by Gladys when it
installs the integration (in developer mode) and builds its Docker container.

The trick of local development is therefore to:

  1. install the integration once in Gladys, which generates the token
  2. retrieve this token and delete the container
  3. run your code locally with this token — Gladys doesn’t see the difference.
┌──────────────────────────┐          WebSocket + REST          ┌─────────────────────────┐
│  Gladys (npm start)      │ ◄────────────────────────────────► │  your integration      │
│  API   localhost:1443    │   token + selector                 │  node index.js          │
│  Front localhost:1444    │                                    │  (outside container)    │
└──────────────────────────┘                                    └─────────────────────────┘

The Docker container is only used to obtain the token: the code you test runs alongside it.


Prerequisites

Gladys running locally

From the root of the Gladys repository:

nvm use 22
npm start

This command launches in parallel the server (API on http://localhost:1443) and the front (http://localhost:1444). Open the front and create your administrator account if this is a first installation.

Docker installed and started

The daemon must respond to the following command without error:

docker ps
Non-standard Docker socket

:warning: If you are using Colima (or a non-standard Docker socket)

Gladys communicates with Docker via the dockerode library, which does not read Docker contexts. It
only knows two things: the environment variable DOCKER_HOST, and by default the socket
/var/run/docker.sock.

With Colima, Podman, Rancher Desktop, or a remote Docker, this socket does not exist:

ls -la /var/run/docker.sock   # No such file or directory

Your docker CLI still works (it uses the context), but Gladys, on the other hand, will
see no daemon
and external integrations will be disabled.

Export the variable in the shell that launches Gladys, before npm start:

export DOCKER_HOST=$(docker context inspect --format '{{.Endpoints.docker.Host}}')
# ex. unix:///Users/me/.colima/default/docker.sock
npm start

Permanent alternative, if you prefer not to think about it anymore:

sudo ln -sf ~/.colima/default/docker.sock /var/run/docker.sock

Symptom if the step is forgotten: at startup, the server logs
External integrations are not available: Gladys has no access to a Docker socket, and any attempt to install an external integration fails.


Step 1 — Install the integration in developer mode

Go to http://localhost:1444/dashboard/integration (the button only appears for an administrator account), then click on « Install from GitHub ».

In the window that opens, unfold the link « Developer mode: install from a
Docker image »
.

Two fields are offered to you:

Field Required Remark
Docker Image Yes Takes precedence over the docker_image declared in the manifest
Manifest (JSON, optional) No Unnecessary if the image has the label io.gladysassistant.manifest, otherwise paste the contents of your gladys-assistant-integration.json

Click on Install.

What image to provide?

a) An image published on Github — the nominal case

ghcr.io/<owner>/<repo>:<version>

This is what you have if you have a GitHub repository. The CI already publishes images. The content of the image is not important here, since you will anyway run your code locally.

b) A « placeholder » image + the manifest pasted — to start without publishing anything

Haven’t published anything yet? Use any lightweight public image and paste your
manifest in the second field:

alpine:3

Gladys downloads alpine, validates your manifest gladys-assistant-integration.json, creates the service and generates the token: that’s all you need. The container won’t do anything useful (it will stop immediately, the status will go to « Degraded » then « Error ») — of no importance, it will be deleted in step 3.

c) A locally built image (pending PR #2841)

Build a local image from the development directory of your external integration

docker build -t <my-integration>:dev .

Variant: Install from the GitHub repository URL

The main form of the same window accepts a repository URL:

https://github.com/<owner>/<repo>

Gladys reads the gladys-assistant-integration.json at the root and downloads the image declared in the manifest. Practical when the repository is public and the image published — without manifest to copy.
Only difference for the rest: the selector becomes ext-<owner>-<repo> instead of
ext-dev-<name>.


Do you need to launch it once?

No, it’s automatic. The installation chains: image download → service creation in the database → container creation → start. It is this container creation that creates the token, so it exists as soon as the form ends — you are redirected to the integration page.

If the manifest is invalid, Gladys displays the details field by field (actions[1].depends_on: unknown field): it’s a developer screen, the errors are explicit.

Step 2 — Retrieve the selector and the token

The selector

After installation, you are redirected to the integration page. The selector is the last
segment of the URL:

http://localhost:1444/dashboard/integration/device/external/<my-selector>

Warning, this is not the name of your integration. Gladys derives it:

Installation mode Selector
Developer mode (Docker image) ext-dev-<manifest-name-in-lowercase-with-hyphens>
From a GitHub repository URL ext-<owner>-<repo>

In case of collision, a numerical suffix is added (ext-dev-my-integration-2).

This is not just a label: the SDK uses it to prefix the identifiers of your devices
(ext:<selector>:<my-device>), and Gladys rejects any identifier that falls outside this perimeter.

The token

The token is never displayed in the interface: Gladys injects it into the container’s
environment and does not display it again. We therefore read it directly in the container, which always
has the name gladys-<selector>:

docker inspect gladys-<my-selector> \
  --format '{{range .Config.Env}}{{println .}}{{end}}' | grep GLADYS_

Expected output:

GLADYS_HOST_API_URL=http://host.docker.internal:1443
GLADYS_INTEGRATION_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
GLADYS_INTEGRATION_SELECTOR=ext-dev-my-integration

You have your three variables. Two clarifications:

  • GLADYS_HOST_API_URL is host.docker.internal because the value is written for a container. From your machine, it will be http://localhost:1443.
  • The token has no expiration date: it remains valid as long as Gladys does not recreate the
    container (see the Troubleshooting section).
  • We find the name of the selector found previously thanks to the URL GLADYS_INTEGRATION_SELECTOR

Step 3 — Delete the container

A token cannot be shared: when a second client authenticates with the same token,
Gladys closes the first connection. If the container remains alive, it and your local process
cut each other off in a loop.

So delete the container:

docker rm -f gladys-<my-selector>

The token remains valid: its revocation only depends on a counter in the database, which this deletion does not affect.

Step 4 — Launch the integration with Node

In your integration repository:

nvm use 22
npm install

Then launch it with the three retrieved variables:

GLADYS_HOST_API_URL="http://localhost:1443" \
GLADYS_INTEGRATION_TOKEN="<the copied token>" \
GLADYS_INTEGRATION_SELECTOR="<my-selector>" \
LOG_LEVEL=debug \
npm start

Verify that it works

On the integration side, the logs display the connection to the WebSocket. On the Gladys side, refresh the
integration page: the status goes to « Running », even though no container is
running. You can now normally use the Configuration, Discovery,
Devices and Actions tabs.

Logs and Running state

Loop

Modify your code, Ctrl-C, restart.
The token survives all restarts of your process: no need to regenerate it.


Troubleshooting

Symptom Cause Fix
401 on the API, or WebSocket closure with code 4000 The token has been revoked: Gladys recreated the container (integration update, hardware modification, or Gladys server restart) Go back to step 2 to read the token in the new container, then do step 3 again
The container reappears on its own and cuts your connection Step 3 not done, or done with docker stop / the « Stop » button docker rm -f gladys-<my-selector>
UNABLE_TO_PULL_IMAGE during installation The image does not exist on any accessible registry Use a placeholder image (step 1b), or the branch of PR #2841
Installation fails with a generic error, and the server logs External integrations are not available Gladys does not see the Docker daemon Export DOCKER_HOST before npm start (see Prerequisites)
« Degraded » then « Error » status just after installation Expected with a placeholder image: the container never authenticates No consequence — the token remains valid, continue
GladysIntegration: missing "…" option One of the three variables was not passed Check the launch command / the source of the .env.local
The integration connects, but discovery fails with devices[0].external_id: must start with "ext:…:" The selector passed does not match that of the service: the SDK prefixes the external_id with ext:<selector>:, and Gladys rejects anything that falls outside its perimeter Take the exact selector in the integration page URL (step 2)

:warning: After each restart of the Gladys server, the deleted container is recreated at the start
of the integration, with a new token: read it again (step 2) and delete the
container again (step 3). This is the main surprise of this development loop.


Cleanup

To start from a clean slate, uninstall the integration from its Supervision tab →
Uninstall. Gladys deletes the container, its private network, its data folder and the
corresponding line in the database. Devices already created remain in Gladys until you delete them.

There’s nothing left but to go back to step 1.

2 Likes