Get docker container versions within openhab

  • Platform information:
    • Hardware: Raspberry Pi 5
    • OS: Raspberry pi OS with docker
    • openHAB version: 4.2.2

Im trying to get the versions of all my running docker containers within openhab so i can display them and write a update list on my MainUI.

Its very easy to get the latest github versions available but i stuck on get the local informations but i guess im close, just dont know how to finish it with openhab.

What I tried so far:

  1. enable the docker unix socket to access docker with curl (added “/var/run/docker.sock:/var/run/docker.sock” to volumes)
  2. did some tests with command line and am able to receive a very long json string with e.g.
curl --silent --unix-socket /var/run/docker.sock "http://localhost/containers/zigbee2mqtt/json"

Result:

{
    "Id": "faaa17084d2aced603c8a7d336814d2af9459e1c1a5a6d072e5c217362292e85",
    "Created": "2024-11-02T05:16:41.273273291Z",
    "Path": "docker-entrypoint.sh",
...
    "Config": {
        "Hostname": "smarthome",
        "Domainname": "",
        "User": "",
        "AttachStdin": false,
        "AttachStdout": false,
        "AttachStderr": false,
        "Tty": false,
        "OpenStdin": false,
        "StdinOnce": false,
...
        "Image": "koenkk/zigbee2mqtt:1.41.0",
        "Volumes": {
            "/app/data": {},
            "/run/udev": {}
        },

so desired information is under {Config:{Image}} > “Image”: “koenkk/zigbee2mqtt:1.41.0”

But whats the best way to get this data for all my containers?
After many lost hours i need the community joker :slight_smile:

First I’ll say that OH doesn’t really do well with lists of data. It’s going to take a bit of work to actually display this information in a meanginful manner. It’s not impossible but it is going to be a good bit of work. In the long run you’ll almost certainly be happier with a special purpose tool for monitoring your IT infrastructure.

Beyond that, since it’s JSON you can probably just use a JSONPATH thransform chained to a REGEX.

JSONPATH:$.Config.Image∩REGEX:.*:(.*)
1 Like

I see. Didn’t expected that. Because within zigbee2mqtt things the json path is common.

So with this variant it’s best to use exec binding or better executeCommandLine or even JavaScript? Saw many options online but I hate these as I had always trouble with them.

Alternatively I have portainer which can be used over http. I was not able to set it up so far but looks much easier than the command line option. Just the access token is only valid for 8 hours but even receive a new one should be possible with a rule


By special purpose tool I mean something like Watchtower, Zabbix, Portainer, etc. OH really isn’t designed to be an IT monitoring system and therefore it does a relatively poor job of it. A service special built for IT monitoring is almost always going to be less work and work better than OH for this task.

The issue isn’t extracting the data from the JSON or getting the data into OH. That’s easy and I provided the transformation you need above to get it straight from Docker using that curl command.

Displaying it in MainUI in a meaningful way is going to take a good bit of work though. That’s where the work is going to come in.

2 Likes

Ah, now I understand what you mean. In the first step I dont want something fancy. Ah list of running and available versions would be great.
Next step I would write a java script which checks the version numbers, should also be not too heavy as soon as I have the required informations. Then its easy to visualize an switch item which tells me the container is up to date or not. When i group the switch items of all countainers I have an counter which tells me how many updates are available.

So after the portainer api is too complex for me to use i went back to my good old “friend” exec binding.

I tried to copy the example from the documentation without input argument to keep it simple. The command is executing but I get an empty response. I already added the command to the whitelist and i see no errors in the log.
Any hint what IÂŽm missing?

Thing:

 Thing exec:command:zigbee2mqtt_exec [ 
    command="curl --silent --unix-socket /var/run/docker.sock 'http://localhost/containers/zigbee2mqtt/json'",
    interval=0,
    timeout=5,
    autorun=false
]

Items:

// state of the execution, is running or finished
Switch yourcommand_Run {channel="exec:command:zigbee2mqtt_exec:run", autoupdate="false"}
// Output of command line execution 
String yourcommand_Out {channel="exec:command:zigbee2mqtt_exec:output", transform="JSONPATH:$.Config.Image∩REGEX:.*:(.*)"}

Rules:

rule "begin your execution"
when
   Item DebugSwitch received command
then
      // Separately triggering RUN allows subsequent executions with unchanged parameter %2
      // which autorun does not.
   if (yourcommand_Run.state != ON) {
      yourcommand_Run.sendCommand(ON)
         // the Run indicator state will go ON shortly, and return OFF when script finished
   }else{
      logInfo("Your command exec", "Script already in use, skipping execution.")
   }
end

rule "script complete"
when
   Item yourcommand_Run changed from ON to OFF
then
      // Logging of ending
   logInfo("Your command exec", "Script has completed.")
      // Caution : openHAB bus is asynchronous
      // there is no guarantee the output Item will get updated before the run channel triggers rules
end

rule "process your results"
when
   Item yourcommand_Out received update
then
      // Logging of raw command line result
   logInfo("Your command exec", "Raw result:" + yourcommand_Out.state )
      // If the returned string is just a number it can be parsed
      // If not, a regex or another transformation could be used
   YourText.postUpdate(yourcommand_Out.state.toString)
end

Output in log:

[INFO ] [.core.model.script.Your command exec] - Script has completed.
[INFO ] [.core.model.script.Your command exec] - Raw result:

there shoul be a big block of json code coming out of the command like when I execute direct in the terminal.

You are outside of OH at that point. If the script is returning nothing then that’s what OH is going to get and process. You need to debug why the script is returning nothing. curl is probably throwing an error and because it has the silent flag it’s not returning anything.

The exit Channel has the return code from the ran script. If that’s not 0 that will tell you that the command is failing.

Don’t forget that the commands are running as user openhab so it’s that user that needs permissions on all the resources used by the script.

1 Like

FWIW I run my docker containers on portainer. Portainer spits out as you already stated a pretty complex (but useful) JSON via its API. I only use it for “container runs: ON/OFF” switch-items for a dashboard in openHAB. If it’s useful and you’re running node-red, you could use this:
https://flows.nodered.org/flow/648cb2a8aeb19d1f665d1c0db1e4541b

and then you could send this to openHAB via MQTT or REST API.

Ok so i added the exit code and removed the silent flag and the error is “could not connect to server”. Thats the point where i hate to use exec binding as I dont understand the further steps. User is already openhab which i tested with command “whoami” also with the exec binding.

Anyway i found that its not recommended to expose the docker socket as it contains some risk :frowning:

@binderth thats awesome! i run node-red anyway for mqtt usage of my bambu lab printer within openhab. I found 2 tokens for portainer, one to be configured within the web interface and one that can be requested by terminal but has to be refreshed every 8 hours. Which one is used with your node-red?

If the openhab user doesn’t have permission to read the socket it’s never going to work. Usually you can add the openhab user to the docker group but you’ll have to check the permissions on the socket file to know for sure.

the 8hours (configurable) refer to how long the session cookie lives. If you follow the API documentation, you create a API Access token, which lives “forever” so to say:

1 Like

I never worked with node-red myself so far but I was able to make your flow going to work. Only issue that i found was that i have some containers without health check value. In result of that i changed the node function which grabs this to not cause an error (else all other values form this node are missing ether):

// ensure values are available
let state = (msg.payload && msg.payload.State) ? msg.payload.State : {};
let health = (state && state.Health) ? state.Health : {};
let config = (msg.payload && msg.payload.Config) ? msg.payload.Config : {};

// return result if available
return [
    { payload: state.Running || "N/A" },
    { payload: state.Status || "N/A" },
    { payload: health.Status || "N/A" },
    { payload: state.StartedAt || "N/A" },
    { payload: config.Image || "N/A" }
];

A question how to proceed. As I want to extract all containers versions i could start a different http request with the containers name. I already was able to get the version of that container but this is very static.

But as you loop all available containers anyway i guess its possible to expose a dynamic list of containers and their version. Could you give me a rough idea how to do this?
EDIT:
I managed to extract a list of running containers but the question is how i can send http requests for all of them and process the result to extract the version number.

@rlkoshak you are right, from the containers terminal i get root as active user so i would have to figure out how to make the exec command work but I hope i can go with the node-red variant because it looks like much more versatile and dynamic then my anyway not recommended docker socket variant. But anyway great thanks for your fast support!

oh, sorry. I forgot. Back then I also changed the node, but simply to this:

var dockerJSON = '{';
for (let i = 0; i < msg.payload.length; i++) {
  dockerJSON += '"'+msg.payload[i]["Names"][0].replace("/", "")+'": {"state": "'+msg.payload[i]["State"]+'", "status": "'+msg.payload[i]["Status"]+'"},';
} 
dockerJSON += '"empty": ""}';
msg.topic = "servers/Homeserver/dockercontainer";
msg.payload = JSON.parse(dockerJSON);
return msg;

(the empty-part is just lazy-me avoiding to work around the fact, that I included the last “,”
)
Now, the JSON looks like this:

{
  "grafana": {
    "state": "running",
    "status": "Up 16 hours"
  },
  "evcc": {
    "state": "running",
    "status": "Up 35 hours (healthy)"
  },
  "empty": ""
}

This one I also send to a topic (servers/Homeserver/dockercontainer) and that one I can then use for my MQTT-thing and generate channels:

UID: mqtt:topic:mqtt:homeserver
label: MQTT Homeserver
thingTypeUID: mqtt:topic
configuration: {}
bridgeUID: mqtt:broker:synology
channels:
  - id: mosquitto_online
    channelTypeUID: mqtt:switch
    label: Mosquitto MQTT server online
    description: ist der Container online
    configuration:
      stateTopic: servers/Homeserver/dockercontainer
      transformationPattern: JSONPATH:$.['eclipse-mosquitto'].state∩MAP:dockerStates.map
  - id: mosquitto_status
    channelTypeUID: mqtt:string
    label: Mosquitto MQTT server Status
    description: Status des Container
    configuration:
      stateTopic: servers/Homeserver/dockercontainer
      transformationPattern: JSONPATH:$.['eclipse-mosquitto'].status
  - id: zabbixWeb_online
    channelTypeUID: mqtt:switch
    label: Zabbix Web server online
    description: ist der Container online
    configuration:
      stateTopic: servers/Homeserver/dockercontainer
      transformationPattern: JSONPATH:$.['zabbix7-web'].state∩MAP:dockerStates.map
  - id: zabbixWeb_status
    channelTypeUID: mqtt:string
    label: Zabbix Web server Status
    description: Status des Container
    configuration:
      stateTopic: servers/Homeserver/dockercontainer
      transformationPattern: JSONPATH:$.['zabbix7-web'].status
...

That way I can then link a switch-item to all _online-channels and a string-item to all _status-channels. My dockerStates.map looks like this:

running=ON
stopped=OFF
exited=OFF
paused=OFF
created=OFF
NULL=UNDEF
default=OFF

That way I do get the “ON”-state for all “running” containers and can display them along with the statuses just like that.

If you’d like to compare versions, you’ll have to include those information in the node-red node also. the version is always here: [i].Labels.['com.docker.compose.version']. You won’t need another call for that.

1 Like

Hey, thanks for the detailed description.

I practiced a bit with node-exporter and finally got this solution where i get name, state and version over mqtt. If someone is interested i can figure out how to export the required data.

I let your stuff inside in case i need further informations for copy&paste but for now i cut all after the first http request as i dont need the other data at the moment

Thanks! Finally IÂŽm very close to my update counter and as bonus i learnded some node-red stuff :slight_smile: