Add Podman fallback when Docker socket is missing.
Release rolling / release (push) Successful in 7m51s

Auto-detect rootless/rootful Podman sockets after Docker candidates, honor PEARDOCK_ENGINE and CONTAINER_HOST, and use podman compose for stacks when the engine is Podman.
This commit is contained in:
Raven Scott
2026-07-16 05:21:32 -04:00
parent 658bc1ee59
commit 8e6a0505a9
10 changed files with 553 additions and 76 deletions
+115
View File
@@ -0,0 +1,115 @@
import test from 'brittle'
import {
classifySocketPath,
listDockerSocketCandidates,
listPodmanSocketCandidates,
parseUnixHostEnv,
resolveContainerSocket,
engineDisplayName,
} from '../server/services/docker.js'
import { resolveComposeCli } from '../server/utils/composeManager.js'
test('classifySocketPath detects docker vs podman', (t) => {
t.is(classifySocketPath('/var/run/docker.sock'), 'docker')
t.is(classifySocketPath('/run/user/1000/podman/podman.sock'), 'podman')
t.is(classifySocketPath('//./pipe/dockerDesktopLinuxEngine'), 'docker')
t.is(classifySocketPath('//./pipe/podman-machine-default'), 'podman')
t.is(classifySocketPath('/tmp/custom.sock'), 'unknown')
})
test('parseUnixHostEnv accepts unix and bare paths', (t) => {
t.is(parseUnixHostEnv('unix:///run/podman/podman.sock'), '/run/podman/podman.sock')
t.is(parseUnixHostEnv('unix://'), '/var/run/docker.sock')
t.is(parseUnixHostEnv('/var/run/docker.sock'), '/var/run/docker.sock')
t.is(parseUnixHostEnv('tcp://1.2.3.4:2375'), null)
})
test('listPodmanSocketCandidates includes rootless and rootful paths', (t) => {
const list = listPodmanSocketCandidates(
{ XDG_RUNTIME_DIR: '/run/user/42', PODMAN_SOCK: '/custom/podman.sock' },
'/home/alice'
)
t.ok(list.includes('/custom/podman.sock'))
t.ok(list.includes('/run/user/42/podman/podman.sock'))
t.ok(list.includes('/run/podman/podman.sock'))
t.ok(list.includes('/home/alice/.local/share/containers/podman/machine/podman.sock'))
})
test('listDockerSocketCandidates includes env and colima', (t) => {
const list = listDockerSocketCandidates(
{ DOCKER_SOCK: '/tmp/my-docker.sock' },
'/home/alice'
)
t.ok(list.includes('/tmp/my-docker.sock'))
t.ok(list.includes('/var/run/docker.sock'))
t.ok(list.includes('/home/alice/.colima/default/docker.sock'))
})
test('resolveContainerSocket prefers Docker when present', (t) => {
const accessible = new Set(['/var/run/docker.sock', '/run/podman/podman.sock'])
const r = resolveContainerSocket({
env: {},
platform: 'linux',
homedir: '/home/alice',
access: (p) => accessible.has(p),
})
t.is(r.path, '/var/run/docker.sock')
t.is(r.kind, 'docker')
t.ok(r.exists)
})
test('resolveContainerSocket falls back to Podman when Docker missing', (t) => {
const accessible = new Set(['/run/user/1000/podman/podman.sock'])
const r = resolveContainerSocket({
env: { XDG_RUNTIME_DIR: '/run/user/1000' },
platform: 'linux',
homedir: '/home/alice',
access: (p) => accessible.has(p),
})
t.is(r.path, '/run/user/1000/podman/podman.sock')
t.is(r.kind, 'podman')
t.ok(r.exists)
})
test('resolveContainerSocket PEARDOCK_ENGINE=podman prefers podman', (t) => {
const accessible = new Set(['/var/run/docker.sock', '/run/podman/podman.sock'])
const r = resolveContainerSocket({
env: { PEARDOCK_ENGINE: 'podman' },
platform: 'linux',
homedir: '/home/alice',
access: (p) => accessible.has(p),
})
t.is(r.path, '/run/podman/podman.sock')
t.is(r.kind, 'podman')
})
test('resolveContainerSocket DOCKER_HOST wins', (t) => {
const r = resolveContainerSocket({
env: { DOCKER_HOST: 'unix:///custom/engine.sock' },
platform: 'linux',
homedir: '/home/alice',
access: () => true,
})
t.is(r.path, '/custom/engine.sock')
t.is(r.source, 'DOCKER_HOST')
})
test('resolveContainerSocket CONTAINER_HOST wins when no DOCKER_HOST', (t) => {
const r = resolveContainerSocket({
env: { CONTAINER_HOST: 'unix:///run/user/1/podman/podman.sock' },
platform: 'linux',
homedir: '/home/alice',
access: () => false,
})
t.is(r.path, '/run/user/1/podman/podman.sock')
t.is(r.kind, 'podman')
t.is(r.source, 'CONTAINER_HOST')
t.is(r.exists, false)
})
test('resolveComposeCli picks podman for podman engine', (t) => {
t.alike(resolveComposeCli('podman'), { cmd: 'podman', prefix: ['compose'] })
t.alike(resolveComposeCli('docker'), { cmd: 'docker', prefix: ['compose'] })
t.is(engineDisplayName('podman'), 'Podman')
t.is(engineDisplayName('docker'), 'Docker')
})