Bind-Mounting the Projects Folder Into a Container
Two small compose decisions, identical paths on both sides and a matched UID, make the host Projects folder safe to use from a container without chmod 777.
Hermes runs in a container, but all the project files live on the host. At first I hesitated to mount the Projects folder into the container, thanks to an old trauma: files turning root-owned on the host while my host user lost access.
I've been through it myself: a container running as root (uid 0) writes to a bind mount, and when you open it on the host, the file belongs to root. A regular user can't edit it, let alone delete it. The quick fix everyone reaches for is chmod 777 or routine chown. Garbage solution, because the problem returns every time the container writes a new file.
I even considered not mounting at all and copying files in whenever needed. That gets messier, because the context the agent sees stops matching what's on the host.
Path parity
First decision: mount at exactly the same path inside the container as on the host. In compose, the volume reads ${HOME}/Projects:${HOME}/Projects; identical paths on both sides. The effect: paths that show up in chat logs, configs, even editors, stay valid on both sides. No more "this is the container path, this is the host path".
Path parity is underrated. Most people sweat swapping paths on every environment change more than they sweat permissions. They focus on UID mismatch, while path mismatch quietly wrecks the agent workflow.
The final form in compose is this simple:
services:
hermes:
volumes:
- ${HOME}/Projects:${HOME}/Projects
One trick, though: both sides must carry the same number. Check it on the host first:
id -u
# 1000
Then compare with the user the container process runs as: run docker exec into the container and run id. If the numbers differ, that's where all the permission drama comes from. The fix isn't chmod; it's matching the number on the container side.
A concrete example that tips this decision daily: configs and logs written by tools inside the container embed absolute paths. Once both sides agree on one map, the config doesn't care whether it's read from a container shell or straight from the host. Same number, same file, same place. Once both sides agree on the map, there's no translation layer left to maintain.
UID parity
Second decision: the user in the container is uid 1000, exactly like my host user. The Linux kernel matches numeric UID/GID, not user names. Container root (uid 0) writing to a bind mount produces root-owned files on the host Understanding User File Ownership in Docker. Files the container writes to this mount immediately read as owned by the host user. Read-write works without chmod 777.
This is the classic illness that shows up as permission denied on Linux: the container runs as root, the host user is uid 1000, and the kernel only sees mismatched numbers Fixing Docker Permission Denied on Bind Mounted Volumes. A bind mount itself means mounting a host file or folder straight into the container, no copying Docker's bind mounts documentation. The standard fix is matching the container UID to the host user, via USER in the Dockerfile or user: in compose Docker Mount Permissions: UID/GID and Five Fixes.
I picked this over named volumes or rootless Docker because what I needed was direct access to an existing project tree, git history included. Mount once, both sides see the same data.
Why not a named volume and let Docker handle ownership? For pure application data, named volumes are cleaner. But a project tree under active work (edited in an editor, committed in git, read by an agent) needs to be one identical object on both sides, not a Docker-managed copy. At that point a bind mount with UID parity is the most honest choice.
Now Hermes reads and writes the host's Projects folder from inside the container without drama, and I'm confident modules like the host-mode OAuth loopback keep running on the same setup. One note for the future: if I ever switch to a host user with a different uid, the number in this compose file is what changes.