See it in action: docker-socket-manager-demo
A Java library for connecting to Docker daemons over local Unix sockets or remote SSH tunnels with automatic socat relay management.
Built to be embedded in backend applications that need to manage Docker containers remotely - for example, triggering database backups, scaling services, or monitoring container state across multiple hosts.
For local connections, the library connects directly to a Unix socket (e.g. /var/run/docker.sock).
For remote SSH connections, the library:
- Establishes an SSH connection to the remote host
- Checks if socat is already running on the configured port - starts it if not
- Opens a local SSH port-forward tunnel
- Connects the Docker client through the tunnel
All connections are pooled in memory and reused across calls. Dead connections are detected via Docker ping and replaced on the next request.
- Java 17+
- Docker daemon accessible (local socket or remote SSH)
- For SSH connections:
socatinstalled on the remote host (sudo apt-get install socat)
<dependency>
<groupId>tech.nomad4</groupId>
<artifactId>docker-socket-manager</artifactId>
<version>0.2.0</version>
</dependency>implementation 'tech.nomad4:docker-socket-manager:0.2.0'Currently published to GitHub Packages and JitPack. See Package registry setup below.
DockerSocketConfig config = DockerSocketConfig.builder()
.id(1L)
.type(SocketType.LOCAL)
.socketPath("/var/run/docker.sock")
.build();
DockerSocketService service = new DockerSocketService();
DockerClient client = service.getClient(1L, config);
client.listContainersCmd().exec().forEach(c ->
System.out.println(c.getId() + " " + c.getStatus())
);DockerSocketConfig config = DockerSocketConfig.builder()
.id(2L)
.type(SocketType.REMOTE_SSH)
.sshHost("192.168.1.100")
.sshPort(22)
.sshUser("ubuntu")
.sshPrivateKeyPath("/home/user/.ssh/id_rsa") // or use sshPassword
.sshKeyPassphrase("keyPassphrase") // only if the key above is encrypted
.sshHostKeyFingerprint("SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") // ssh-keygen -lf; omit to accept any host key
.remoteDockerSocketPath("/var/run/docker.sock")
.remoteSocatPort(2375)
.build();
DockerSocketService service = new DockerSocketService();
DockerClient client = service.getClient(2L, config);
client.listContainersCmd().exec().forEach(c ->
System.out.println(c.getId() + " " + c.getStatus())
);// Check if a connection is alive
boolean alive = service.isAlive(2L);
// Manually disconnect a specific socket
service.disconnect(2L);
// Disconnect all (e.g. on application shutdown)
service.close();// Check if running inside a Docker container
boolean inDocker = DockerEnvironmentDetector.isRunningInDocker();
// Check if Docker socket is accessible at default path
boolean socketAvailable = DockerEnvironmentDetector.isDockerSocketAvailable();
// Get environment-specific setup recommendations
String hint = DockerEnvironmentDetector.getSetupRecommendations();If your application itself runs in a container, mount the Docker socket:
services:
your-app:
image: your-app:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
group_add:
- "${DOCKER_GROUP_ID:-986}"Get the Docker group ID on the host and put it in .env:
echo "DOCKER_GROUP_ID=$(getent group docker | cut -d: -f3)" >> .envThe fallback value
986is a common default but varies by system - always setDOCKER_GROUP_IDexplicitly in production.
Warning: Mounting the Docker socket gives the container full access to the host's Docker daemon. Only do this in trusted environments.
| Field | Type | Description |
|---|---|---|
id |
Long |
Pool key - unique identifier for this connection |
type |
SocketType |
LOCAL or REMOTE_SSH |
socketPath |
String |
Path to local Unix socket (LOCAL only) |
sshHost |
String |
Remote host address (REMOTE_SSH only) |
sshPort |
Integer |
SSH port, typically 22 (REMOTE_SSH only) |
sshUser |
String |
SSH username (REMOTE_SSH only) |
sshPassword |
String |
SSH password - use key auth in production |
sshPrivateKeyPath |
String |
Path to private key file (preferred over password) |
sshKeyPassphrase |
String |
Passphrase for an encrypted private key at sshPrivateKeyPath - optional |
sshHostKeyFingerprint |
String |
Expected SSH host key fingerprint (ssh-keygen -lf format, e.g. SHA256:...) - if unset, any host key is accepted |
remoteDockerSocketPath |
String |
Docker socket path on remote host |
remoteSocatPort |
Integer |
TCP port socat will listen on (or already listens on) |
connectTimeoutMillis |
Integer |
Docker HTTP client connect timeout - defaults to DockerSocketService.DEFAULT_CONNECT_TIMEOUT_MILLIS (10s) |
readTimeoutMillis |
Integer |
Docker HTTP client per-call read timeout - defaults to DockerSocketService.DEFAULT_READ_TIMEOUT_MILLIS (30s) |
Requires a GitHub account and a personal access token with read:packages scope (GitHub → Settings → Developer settings → Personal access tokens).
Add the repository to your pom.xml:
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/nomad4tech/docker-socket-manager</url>
</repository>
</repositories>Add credentials to ~/.m2/settings.xml:
<servers>
<server>
<id>github</id>
<username>YOUR_GITHUB_USERNAME</username>
<password>YOUR_GITHUB_TOKEN</password>
</server>
</servers>No token or credentials required. Add the repository and use the JitPack groupId:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories><dependency>
<groupId>com.github.nomad4tech</groupId>
<artifactId>docker-socket-manager</artifactId>
<version>0.2.0</version>
</dependency>- SSH host key verification is skipped (
PromiscuousVerifier) unlessDockerSocketConfig.sshHostKeyFingerprintis set - without it, any host key is accepted (MITM risk). Set the fingerprint (format fromssh-keygen -lf, e.g.SHA256:...) to enable verification. - Only
LOCALandREMOTE_SSHconnection types are supported. Direct TCP (REMOTE_TCP) and TLS are planned. - No built-in health check scheduler - connection liveness is checked lazily on
getClient(). ScheduleisAlive()/evict()calls from your application layer if you need proactive monitoring. connectTimeoutMillis/readTimeoutMillisapply per HTTP call on the underlying Docker client, including to any streaming endpoint (log follow, exec attach) invoked through the returnedDockerClient- don't set them lower than the longest idle gap you expect on such a stream.ensureSocatRunning()'s occupied-port check and the actualsocatstart are not atomic (check-then-act). If something else on the remote host (e.g. a systemd-managedsocatunit) binds the same port between the check and the start, both can end up racing for it. Not fixed in this release - if you managesocatexternally, make sure it's already listening before this library ever tries to connect.
- docker-java - Docker API client
- sshj - SSH connections and port forwarding
- Lombok - boilerplate reduction
MIT