Problem or Use Case
Summary
Hermes currently hardcodes docker as the only OCI container runtime for sandboxed command execution. Podman — a daemonless, rootless-by-default, drop-in replacement for Docker — is the default container runtime on Fedora, RHEL, and CentOS, and is widely used on Debian, Ubuntu, and Arch as well. There is currently zero mention of podman or rootless anywhere in the codebase.
Supporting Podman would bring genuine security improvements (rootless user-namespace isolation out of the box, no privileged daemon), align with Hermes's own zero-telemetry security posture, and unblock a meaningful segment of Linux self-hosters who run Podman exclusively — including those deploying Hermes on low-power hardware like a Raspberry Pi 5 with rootless Podman quadlets.
The scope is moderate. Hermes already centralizes all container CLI calls through self._docker_exe (resolved once via find_docker()), so the primary work is making that resolution runtime-aware, handling a few Podman-specific flags, and updating the setup wizard and doctor.
Proposed Solution
Motivation
Security. Hermes markets itself around security: zero telemetry, Tirith command scanning, Docker sandboxing with --cap-drop ALL and --security-opt no-new-privileges. Rootless Podman goes further. There is no privileged daemon — Podman runs as a regular user process. Containers use kernel user namespaces so that even a container escape only yields access as an unprivileged host user, not root. This is a meaningful improvement over Docker's model where the daemon runs as root and group membership effectively grants root-equivalent access. Supporting Podman would make Hermes's security story stronger and more consistent.
User base. Podman is the default on Fedora (the OS behind RHEL/CentOS) and ships out of the box on current Ubuntu and Debian. Users who run Podman often do not have Docker installed at all — there is no fallback. hermes setup terminal → Docker → "Docker not found in PATH" is where it currently ends for them.
Self-hosting on constrained hardware. Hermes positions itself as something you can run on a $5 VPS or a Raspberry Pi. Podman's daemonless architecture is ideal for this — no background daemon eating RAM. Rootless Podman quadlets (systemd .container unit files) are the modern way to manage containers as user services on Linux, and they're a natural fit for running the Hermes gateway as a persistent user service.
Not a Red Hat-only thing. Podman is packaged and well-supported on Fedora, RHEL, CentOS Stream, Debian, Ubuntu, Arch, openSUSE, Alpine, and Gentoo. It's an OCI-compliant tool, not a vendor lock-in play.
What would need to change
I cloned the repo and audited every file that touches the Docker backend. Here's the concrete breakdown.
1. Runtime discovery: tools/environments/docker.py → find_docker()
Current behavior: find_docker() calls shutil.which("docker"), then probes macOS Docker Desktop paths (/usr/local/bin/docker, /opt/homebrew/bin/docker, /Applications/Docker.app/...). Returns the path or None. Result is cached in a module-level global _docker_executable.
What needs to change: Rename or extend to find_container_runtime(). Check for a user-configured preference first (see config below), then auto-detect: shutil.which("podman") and shutil.which("docker"). The macOS fallback paths in _DOCKER_SEARCH_PATHS are Docker Desktop-specific and can be skipped for Podman (Podman on macOS is typically installed via Homebrew and lands in PATH). The cached global should store both the path and which runtime was detected, since downstream code needs to know (for Podman-specific flags).
2. Config option: hermes_cli/config.py
Current state: Config keys are terminal.backend: "docker", terminal.docker_image, terminal.docker_forward_env, terminal.docker_volumes, terminal.docker_mount_cwd_to_workspace.
Proposed addition: Add terminal.container_runtime: "auto" (or "docker" / "podman") to let users explicitly choose. "auto" would auto-detect (prefer podman if found, fall back to docker, or vice versa — open to discussion on default order). The existing docker_* config keys should continue to work for both runtimes since the CLI flags are compatible. No breaking rename needed.
3. Security flags: _SECURITY_ARGS in docker.py
Current flags:
_SECURITY_ARGS = [
"--cap-drop", "ALL",
"--cap-add", "DAC_OVERRIDE",
"--cap-add", "CHOWN",
"--cap-add", "FOWNER",
"--security-opt", "no-new-privileges",
"--pids-limit", "256",
"--tmpfs", "/tmp:rw,nosuid,size=512m",
"--tmpfs", "/var/tmp:rw,noexec,nosuid,size=256m",
"--tmpfs", "/run:rw,noexec,nosuid,size=64m",
]
Podman compatibility: Most of these flags work identically with Podman. The important addition for rootless Podman is --userns=keep-id, which maps the host user's UID/GID to the same UID/GID inside the container. Without this, bind-mounted files (workspace, credentials, skills) get remapped through user namespaces and appear owned by a different UID on the host, breaking file access. With --userns=keep-id, the container runs as the host user, which is actually more secure than Docker's model of running as container-root mapped to host-root.
The --cap-add DAC_OVERRIDE, CHOWN, FOWNER capabilities are restricted in rootless Podman (rootless containers can't grant capabilities the host user doesn't have). In practice, with --userns=keep-id, these capabilities are unnecessary because the container user already owns the bind-mounted files. The fix is to conditionally skip these --cap-add flags when running under rootless Podman, or accept the harmless warning Podman emits.
4. Storage opt check: _storage_opt_supported() in docker.py
Current behavior: Probes whether Docker's overlay2 driver supports --storage-opt size= (requires XFS with pquota). This is entirely Docker-specific.
Podman behavior: Podman does not support --storage-opt size= in rootless mode (the overlay driver in rootless mode uses fuse-overlayfs or native overlay without pquota). The check should be skipped entirely when the runtime is Podman.
5. Availability check: _ensure_docker_available() in docker.py
Current behavior: Runs docker version and checks the exit code.
Podman compatibility: podman version works identically. Just needs to use the detected runtime binary instead of hardcoded docker. The error messages should be updated to say "container runtime" rather than "Docker" when Podman is in use.
6. Container lifecycle commands
Current behavior: The DockerEnvironment class uses docker run -d, docker exec, docker stop, docker rm via subprocess calls with self._docker_exe.
Podman compatibility: All of these commands are CLI-compatible with Podman. podman run -d, podman exec, podman stop, podman rm work the same way. Since Hermes already centralizes the binary path in self._docker_exe, this is essentially free once the discovery is updated.
7. Setup wizard: hermes_cli/setup.py lines 2051–2069
Current behavior: When the user selects "Docker" as the terminal backend, the wizard checks shutil.which("docker") and prints Docker-specific install instructions.
Proposed change: Rename the menu entry to "Container (Docker/Podman)" or add Podman as a separate entry that maps to the same backend. The availability check should probe for both runtimes and report what's found. Install guidance should mention both Docker and Podman.
8. Doctor: hermes_cli/doctor.py lines 406–425
Current behavior: When terminal_env == "docker", checks shutil.which("docker") and runs docker info.
Proposed change: Check for the configured runtime (or auto-detect). podman info works the same way as docker info and would pass the same health check.
9. Terminal tool factory: tools/terminal_tool.py
Current behavior: _create_environment() dispatches on env_type == "docker" to create _DockerEnvironment.
Proposed change: Either accept "podman" as an alias for "docker" (since it uses the same DockerEnvironment class), or let the DockerEnvironment class itself handle which binary to use based on the container_runtime config. The second approach is cleaner — the user sets terminal.backend: docker and terminal.container_runtime: podman, and the Docker environment class uses Podman internally.
10. Tests: tests/tools/test_docker_find.py, test_docker_environment.py
Current state: Tests mock shutil.which("docker") and verify find_docker() behavior.
What's needed: Extend tests to cover Podman discovery (mock shutil.which("podman")), verify --userns=keep-id is added when Podman is detected, and verify _storage_opt_supported() is skipped for Podman.
Suggested implementation approach
The lowest-friction path that avoids breaking changes:
- Add
terminal.container_runtime config key ("auto", "docker", "podman"). Default "auto".
- Extend
find_docker() to become runtime-aware: check config preference, then auto-detect. Return a (path, runtime_type) tuple or store both in the cached global.
- In
DockerEnvironment.__init__(), conditionally add --userns=keep-id and skip _storage_opt_supported() when runtime is Podman.
- Update
_ensure_docker_available(), setup.py, and doctor.py to use the detected runtime.
- Add/extend tests for Podman code paths.
This is a targeted change — roughly 100-200 lines across 5-6 files — not a rewrite.
Alternatives Considered
I want to use Hermes. I'm not swapping my whole home lab over to Docker, where i run rootless podman quadlets - integrating with systemd. It is imo a simpler and more secure system that fits the Hermes architecture and use case better.
If you are up for a purely vibe-coded PR id be up for providing that.
I would defer to you, the maintainers, for implementation specifics tough.
As for the obvious AI generated issue, i know some communities find them offensive, if so I'm sorry.
Feature Type
Other
Scope
Medium (few files, < 300 lines)
Contribution
Problem or Use Case
Summary
Hermes currently hardcodes
dockeras the only OCI container runtime for sandboxed command execution. Podman — a daemonless, rootless-by-default, drop-in replacement for Docker — is the default container runtime on Fedora, RHEL, and CentOS, and is widely used on Debian, Ubuntu, and Arch as well. There is currently zero mention ofpodmanorrootlessanywhere in the codebase.Supporting Podman would bring genuine security improvements (rootless user-namespace isolation out of the box, no privileged daemon), align with Hermes's own zero-telemetry security posture, and unblock a meaningful segment of Linux self-hosters who run Podman exclusively — including those deploying Hermes on low-power hardware like a Raspberry Pi 5 with rootless Podman quadlets.
The scope is moderate. Hermes already centralizes all container CLI calls through
self._docker_exe(resolved once viafind_docker()), so the primary work is making that resolution runtime-aware, handling a few Podman-specific flags, and updating the setup wizard and doctor.Proposed Solution
Motivation
Security. Hermes markets itself around security: zero telemetry, Tirith command scanning, Docker sandboxing with
--cap-drop ALLand--security-opt no-new-privileges. Rootless Podman goes further. There is no privileged daemon — Podman runs as a regular user process. Containers use kernel user namespaces so that even a container escape only yields access as an unprivileged host user, not root. This is a meaningful improvement over Docker's model where the daemon runs as root and group membership effectively grants root-equivalent access. Supporting Podman would make Hermes's security story stronger and more consistent.User base. Podman is the default on Fedora (the OS behind RHEL/CentOS) and ships out of the box on current Ubuntu and Debian. Users who run Podman often do not have Docker installed at all — there is no fallback.
hermes setup terminal→ Docker → "Docker not found in PATH" is where it currently ends for them.Self-hosting on constrained hardware. Hermes positions itself as something you can run on a $5 VPS or a Raspberry Pi. Podman's daemonless architecture is ideal for this — no background daemon eating RAM. Rootless Podman quadlets (systemd
.containerunit files) are the modern way to manage containers as user services on Linux, and they're a natural fit for running the Hermes gateway as a persistent user service.Not a Red Hat-only thing. Podman is packaged and well-supported on Fedora, RHEL, CentOS Stream, Debian, Ubuntu, Arch, openSUSE, Alpine, and Gentoo. It's an OCI-compliant tool, not a vendor lock-in play.
What would need to change
I cloned the repo and audited every file that touches the Docker backend. Here's the concrete breakdown.
1. Runtime discovery:
tools/environments/docker.py→find_docker()Current behavior:
find_docker()callsshutil.which("docker"), then probes macOS Docker Desktop paths (/usr/local/bin/docker,/opt/homebrew/bin/docker,/Applications/Docker.app/...). Returns the path orNone. Result is cached in a module-level global_docker_executable.What needs to change: Rename or extend to
find_container_runtime(). Check for a user-configured preference first (see config below), then auto-detect:shutil.which("podman")andshutil.which("docker"). The macOS fallback paths in_DOCKER_SEARCH_PATHSare Docker Desktop-specific and can be skipped for Podman (Podman on macOS is typically installed via Homebrew and lands in PATH). The cached global should store both the path and which runtime was detected, since downstream code needs to know (for Podman-specific flags).2. Config option:
hermes_cli/config.pyCurrent state: Config keys are
terminal.backend: "docker",terminal.docker_image,terminal.docker_forward_env,terminal.docker_volumes,terminal.docker_mount_cwd_to_workspace.Proposed addition: Add
terminal.container_runtime: "auto"(or"docker"/"podman") to let users explicitly choose."auto"would auto-detect (preferpodmanif found, fall back todocker, or vice versa — open to discussion on default order). The existingdocker_*config keys should continue to work for both runtimes since the CLI flags are compatible. No breaking rename needed.3. Security flags:
_SECURITY_ARGSindocker.pyCurrent flags:
Podman compatibility: Most of these flags work identically with Podman. The important addition for rootless Podman is
--userns=keep-id, which maps the host user's UID/GID to the same UID/GID inside the container. Without this, bind-mounted files (workspace, credentials, skills) get remapped through user namespaces and appear owned by a different UID on the host, breaking file access. With--userns=keep-id, the container runs as the host user, which is actually more secure than Docker's model of running as container-root mapped to host-root.The
--cap-add DAC_OVERRIDE,CHOWN,FOWNERcapabilities are restricted in rootless Podman (rootless containers can't grant capabilities the host user doesn't have). In practice, with--userns=keep-id, these capabilities are unnecessary because the container user already owns the bind-mounted files. The fix is to conditionally skip these--cap-addflags when running under rootless Podman, or accept the harmless warning Podman emits.4. Storage opt check:
_storage_opt_supported()indocker.pyCurrent behavior: Probes whether Docker's overlay2 driver supports
--storage-opt size=(requires XFS with pquota). This is entirely Docker-specific.Podman behavior: Podman does not support
--storage-opt size=in rootless mode (the overlay driver in rootless mode uses fuse-overlayfs or native overlay without pquota). The check should be skipped entirely when the runtime is Podman.5. Availability check:
_ensure_docker_available()indocker.pyCurrent behavior: Runs
docker versionand checks the exit code.Podman compatibility:
podman versionworks identically. Just needs to use the detected runtime binary instead of hardcodeddocker. The error messages should be updated to say "container runtime" rather than "Docker" when Podman is in use.6. Container lifecycle commands
Current behavior: The
DockerEnvironmentclass usesdocker run -d,docker exec,docker stop,docker rmvia subprocess calls withself._docker_exe.Podman compatibility: All of these commands are CLI-compatible with Podman.
podman run -d,podman exec,podman stop,podman rmwork the same way. Since Hermes already centralizes the binary path inself._docker_exe, this is essentially free once the discovery is updated.7. Setup wizard:
hermes_cli/setup.pylines 2051–2069Current behavior: When the user selects "Docker" as the terminal backend, the wizard checks
shutil.which("docker")and prints Docker-specific install instructions.Proposed change: Rename the menu entry to "Container (Docker/Podman)" or add Podman as a separate entry that maps to the same backend. The availability check should probe for both runtimes and report what's found. Install guidance should mention both Docker and Podman.
8. Doctor:
hermes_cli/doctor.pylines 406–425Current behavior: When
terminal_env == "docker", checksshutil.which("docker")and runsdocker info.Proposed change: Check for the configured runtime (or auto-detect).
podman infoworks the same way asdocker infoand would pass the same health check.9. Terminal tool factory:
tools/terminal_tool.pyCurrent behavior:
_create_environment()dispatches onenv_type == "docker"to create_DockerEnvironment.Proposed change: Either accept
"podman"as an alias for"docker"(since it uses the sameDockerEnvironmentclass), or let theDockerEnvironmentclass itself handle which binary to use based on thecontainer_runtimeconfig. The second approach is cleaner — the user setsterminal.backend: dockerandterminal.container_runtime: podman, and the Docker environment class uses Podman internally.10. Tests:
tests/tools/test_docker_find.py,test_docker_environment.pyCurrent state: Tests mock
shutil.which("docker")and verifyfind_docker()behavior.What's needed: Extend tests to cover Podman discovery (mock
shutil.which("podman")), verify--userns=keep-idis added when Podman is detected, and verify_storage_opt_supported()is skipped for Podman.Suggested implementation approach
The lowest-friction path that avoids breaking changes:
terminal.container_runtimeconfig key ("auto","docker","podman"). Default"auto".find_docker()to become runtime-aware: check config preference, then auto-detect. Return a(path, runtime_type)tuple or store both in the cached global.DockerEnvironment.__init__(), conditionally add--userns=keep-idand skip_storage_opt_supported()when runtime is Podman._ensure_docker_available(),setup.py, anddoctor.pyto use the detected runtime.This is a targeted change — roughly 100-200 lines across 5-6 files — not a rewrite.
Alternatives Considered
I want to use Hermes. I'm not swapping my whole home lab over to Docker, where i run rootless podman quadlets - integrating with systemd. It is imo a simpler and more secure system that fits the Hermes architecture and use case better.
If you are up for a purely vibe-coded PR id be up for providing that.
I would defer to you, the maintainers, for implementation specifics tough.
As for the obvious AI generated issue, i know some communities find them offensive, if so I'm sorry.
Feature Type
Other
Scope
Medium (few files, < 300 lines)
Contribution