Originally we used the spotify/docker-client library to wrap the docker remote API in java method calls. They stopped updating that and put out their final release v6.1.1 in 2016.
We switched the Container Service to use a fork of that client, dmandalidis/docker-client in CS version 3.0.0. Given that this was a fork of the client we already used, it was a simple drop-in replacement with no changes needed.
But that library maintainer did continue to make changes. In 2023 they released a major version upgrade, v7.0.0, which dropped support for Java 8. That is the version of Java we use in XNAT (at time of writing) so this change meant we weren't able to update our version of this library. That was fine for a while...
...Until version 25 of the docker engine, in which they made an API change which caused an error in the version we used of docker-client. The library (presumably) fixed their issue but we weren't able to use that fix because our version of the library was frozen by their decision to drop Java 8 support.
This forced us to switch our library from docker-client to docker-java. This was not a drop-in replacement, and did require a migration. All the same docker API endpoints were supported in a 1:1 replacement—which took a little effort but was straightforward—except for one. The docker-java library did not support requesting GenericResources on a swarm service, which is the mechanism by which we allow commands to specify that they need a GPU. We opened a ticket reporting that lack of support (https://github.com/docker-java/docker-java/issues/2320), but at time of writing there has been no response. I created a fork (https://github.com/johnflavin/docker-java) and fixed the issue myself (https://github.com/docker-java/docker-java/pull/2327), but at time of writing that also has no response. I built a custom version of docker-java 3.4.0.1 and pushed that to the XNAT artifactory (ext-release-local/com/github/docker-java).
Long story short, as of CS version 3.5.0 we depend on docker-java version 3.4.0.1 for our docker (and swarm) API support.
--
Full documentation updates are available here: https://wiki.xnat.org/container-service/
Full changelog is available here: https://bitbucket.org/xnatdev/container-service/src/master/CHANGELOG.md
2025-04-22 16:24:12,310 [org.springframework.jms.JmsListenerEndpointContainer#6-9] ERROR org.nrg.containers.services.impl.ContainerServiceImpl - consumeResolveCommandAndLaunchContainer failed for wfid 38243.
java.lang.NullPointerException: authConfig was not specified
at java.util.Objects.requireNonNull(Objects.java:228)
at com.github.dockerjava.core.command.CreateServiceCmdImpl.withAuthConfig(CreateServiceCmdImpl.java:45)
at org.nrg.containers.api.DockerControlApi.createDockerSwarmService(DockerControlApi.java:803)
at org.nrg.containers.api.DockerControlApi.create(DockerControlApi.java:461)
at org.nrg.containers.api.DockerControlApi.create(DockerControlApi.java:434)
at org.nrg.containers.services.impl.ContainerServiceImpl.launchResolvedDockerCommand(ContainerServiceImpl.java:670)
at org.nrg.containers.services.impl.ContainerServiceImpl.launchResolvedCommand(ContainerServiceImpl.java:636)
at org.nrg.containers.services.impl.ContainerServiceImpl.launchResolvedCommand(ContainerServiceImpl.java:621)
at org.nrg.containers.services.impl.ContainerServiceImpl.consumeResolveCommandAndLaunchContainer(ContainerServiceImpl.java:596)
at org.nrg.containers.jms.listeners.ContainerStagingRequestListener.onRequest(ContainerStagingRequestListener.java:50)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119)
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:110)
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:84)
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:736)
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:696)
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:674)
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:331)
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:270)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1237)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1227)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1120)
at java.lang.Thread.run(Thread.java:750)