container_isolation Ansible role
Creates per-app service accounts (UID/GID 2000+) on every host, so each
Docker container can run as its own dedicated UID instead of root or a
shared user. jerick is added as a secondary member of every app
group (and the shared mediapipeline group), so jerick retains access
everywhere regardless of whatever folder permissions you set up
separately on the NFS server.
Scope: this role only manages accounts (groups + users). NFS folder creation, ownership, and permission bits are handled outside Ansible, by you, on the NFS server.
Layout
ansible/
├── site.yml # top-level playbook, hosts: all
└── roles/container_isolation/
├── tasks/
│ ├── main.yml # entry point, includes the others
│ ├── sync_users.yml # creates groups + users on every host
│ └── jerick_access.yml # adds jerick to every group, every host
├── vars/main.yml # source of truth: app list, uids, shared groups
└── files/
├── passwd.containers # reference copy, human-readable
└── group.containers # reference copy, human-readable
vars/main.yml is what Ansible actually reads. The files/*.containers
files are a human-readable reference / diff-friendly changelog — not
consumed directly by any task, since the built-in group/user
modules create accounts idempotently rather than appending raw lines
to /etc/passwd.
Before running
- Point this at your dynamic inventory however you normally do, e.g.:
No inventory group targeting is needed —
ansible-playbook -i your_dynamic_inventory.py site.ymlsite.ymlruns againsthosts: all, and the role applies the same logic everywhere. - Confirm
jerickalready exists as a real user on every host. The role does not create jerick's account (treated as a pre-existing human user) — it only adds jerick to the app groups. If jerick is missing on a host, the role prints a warning and skips the group-membership tasks for that host rather than failing the whole run.
Running
ansible-playbook -i your_dynamic_inventory.py site.yml --check --diff # dry run first
ansible-playbook -i your_dynamic_inventory.py site.yml
Safe to re-run any time — every task uses Ansible's built-in
idempotent modules (group, user), so a second run produces no
changes.
Adding a new app later
- Add a line to
roles/container_isolation/vars/main.ymlundercontainer_apps, with the next free UID (currently up to 2026 — use 2027 next). Never reuse or renumber an existing UID once real folders have been chowned to it on the NFS server. - If it needs shared access to another app's data, add it to the
relevant
shared_groups[].memberslist (or create a new shared group, GID 3001+). - Update
files/passwd.containersandfiles/group.containersto match, for the human-readable record. - Re-run the playbook.
What you still need to do yourself
- NFS folder creation/ownership/permissions on the NFS server —
chown <uid>:<gid>andchmodeach app's folder using the UIDs/GIDs fromvars/main.yml. With jerick in every app's group,770(owner+group rw, others none) gives jerick read-write access everywhere while keeping apps isolated from each other;750would make jerick read-only. /etc/exportson the NFS server — not touched by this role.docker-compose.ymledits — adduser: "UID:GID"(andgroup_add:for apps inmediapipeline) to each service, using the UIDs fromvars/main.yml. Happy to generate these once you share your compose files.- NFS protocol version / idmapping verification — confirm with
nfsstat -mon a client before relying on numeric UID/GID matching across hosts; NFSv4 idmapping misconfiguration is the most common cause of permissions silently not working as expected.