<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Idmap on Dexome</title>
        <link>https://blog.dexome.com/tags/idmap/</link>
        <description>Recent content in Idmap on Dexome</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Mon, 17 Aug 2026 00:00:00 +0530</lastBuildDate><atom:link href="https://blog.dexome.com/tags/idmap/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Sharing Files Between Containers with Different UIDs</title>
        <link>https://blog.dexome.com/post/shared-file-permissions-across-containers/</link>
        <pubDate>Mon, 17 Aug 2026 00:00:00 +0530</pubDate>
        
        <guid>https://blog.dexome.com/post/shared-file-permissions-across-containers/</guid>
        <description>&lt;p&gt;I have multiple containers which need to write into the same media folders.
Copyparty runs as UID 924, the arr applications run as UID 911 and Frigate uses
UID/GID 914 on the host. They need to create, rename and delete each other&amp;rsquo;s
files without running all services as the same user.&lt;/p&gt;
&lt;p&gt;I faced three related issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copyparty ran as UID 924 and shared media with the arr stack at UID 911.&lt;/li&gt;
&lt;li&gt;Frigate had to initialize as root in its container while retaining host
ownership as UID/GID 914.&lt;/li&gt;
&lt;li&gt;A mergerfs view combined two branches whose copies of the same directory had
different modes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fixing only the owner or group was not enough. I needed idmapped mounts, correct
directory modes, application-specific chmod settings and inherited ACLs.&lt;/p&gt;
&lt;h2 id=&#34;why-a-writable-file-could-not-be-replaced&#34;&gt;Why a writable file could not be replaced
&lt;/h2&gt;&lt;p&gt;For a process to replace &lt;code&gt;movies/title/video.mkv&lt;/code&gt;, it needs write and execute on
&lt;code&gt;movies/title/&lt;/code&gt;. Write permission on &lt;code&gt;video.mkv&lt;/code&gt; controls modification of the
file&amp;rsquo;s contents; it does not grant permission to remove that directory entry.&lt;/p&gt;
&lt;p&gt;Three inputs decide the normal Unix permission check:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;UID/GID ownership chooses the owner, group, or other class.&lt;/li&gt;
&lt;li&gt;The inode mode or access ACL says what that class may do.&lt;/li&gt;
&lt;li&gt;The containing directory controls create, rename, and delete.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;article-diagram&#34;&gt;
    &lt;pre class=&#34;mermaid&#34;&gt;
flowchart TB
		PROC[Radarr uid 911, media group] --&gt; CLASS{Matching class?}
		CLASS --&gt;|directory gid media| GROUP[Use group mode or ACL]
		GROUP --&gt;|r-x on directory| DENY[Read and traverse, cannot replace]
		GROUP --&gt;|rwx on directory| ALLOW[Create, rename, and delete]
		FILE[Writable target file] -. does not decide unlink .-&gt; DENY
&lt;/pre&gt;
    &lt;figcaption&gt;Ownership chooses a permission class; directory permissions decide whether another service can rename or delete an entry.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So the first check was the owner and mode of the parent directory, not only the
movie file.&lt;/p&gt;
&lt;h2 id=&#34;copyparty-rename-blocked-a-radarr-import&#34;&gt;Copyparty rename blocked a Radarr import
&lt;/h2&gt;&lt;p&gt;Radarr failed while upgrading a movie:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Access to the path &amp;#39;.../Run (2002)/Run (2002) SDTV.mp4&amp;#39; is denied
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The destination directory told the story:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;drwxr-sr-x  924 media  Run (2002)/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;UID 924 was Copyparty. GID &lt;code&gt;media&lt;/code&gt; was correct, and the setgid bit ensured new
children inherited that group. But the group had &lt;code&gt;r-x&lt;/code&gt;, not &lt;code&gt;rwx&lt;/code&gt;. Radarr&amp;rsquo;s UID
911 could enter the directory and read the old file but could not unlink it.&lt;/p&gt;
&lt;p&gt;I had set &lt;code&gt;UMASK=002&lt;/code&gt; on the container. Copyparty&amp;rsquo;s image did not honor that for
this operation and produced a &lt;code&gt;0755&lt;/code&gt; directory. The permanent fix was
application-level creation policy:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;flags&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;chmod_f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;664&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;	&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;chmod_d&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;775&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I applied that policy to the Copyparty volumes shared with the arr stack,
qBittorrent, and Picard. Existing entries needed a one-time repair because
creation policy is not retroactive.&lt;/p&gt;
&lt;h2 id=&#34;fixing-both-mergerfs-branches&#34;&gt;Fixing both mergerfs branches
&lt;/h2&gt;&lt;p&gt;My first repair changed the affected directory under the &lt;code&gt;btank&lt;/code&gt; backing tree.
The error persisted. The visible media path was a mergerfs union of &lt;code&gt;btank&lt;/code&gt; and
&lt;code&gt;btwo&lt;/code&gt;, and the directory existed on both branches:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;btank branch:  drwxrwsr-x 924 media
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;btwo branch:   drwxr-sr-x 924 media
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;merged view:   drwxr-sr-x
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;mergerfs surfaced metadata from the stale &lt;code&gt;btwo&lt;/code&gt; copy. With
&lt;code&gt;default_permissions&lt;/code&gt;, the kernel enforced the mode visible through the union.
The branch containing the media file was not necessarily the branch supplying
the directory metadata.&lt;/p&gt;
&lt;figure class=&#34;article-diagram&#34;&gt;
    &lt;pre class=&#34;mermaid&#34;&gt;
flowchart TB
		VIEW[&#34;/mnt/merged-media/title&#34;] --&gt; PICK{Metadata selected by mergerfs}
		B1[&#34;/mnt/btank/@media/title: 2775&#34;] --&gt; PICK
		B2[&#34;/mnt/btwo/@media/title: 2755&#34;] --&gt; PICK
		PICK --&gt;|stale branch wins| MODE[Visible mode 2755]
		MODE --&gt; DENIED[Radarr group write denied]
&lt;/pre&gt;
    &lt;figcaption&gt;Repairing only the branch with the file left a stale directory mode on the other mergerfs branch.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The correct repair swept every raw backing branch, never only the merged view:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;find /mnt/btank/@media/movies /mnt/btwo/@media/movies &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;	-type d ! -perm -2775 -exec chmod &lt;span class=&#34;m&#34;&gt;2775&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{}&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;After both branch copies were corrected, Radarr deleted the old file and
completed the import.&lt;/p&gt;
&lt;h2 id=&#34;frigate-runs-as-root-inside-the-container&#34;&gt;Frigate runs as root inside the container
&lt;/h2&gt;&lt;p&gt;Frigate&amp;rsquo;s official image starts through s6-overlay and needs container root for
initialization. Forcing &lt;code&gt;--user 914:914&lt;/code&gt; broke startup. Running it rootful with a
plain bind mount would create host &lt;code&gt;root:root&lt;/code&gt; state, which would break the
native rollback path and Copyparty access.&lt;/p&gt;
&lt;p&gt;A per-bind idmapped mount solved ownership without changing the process:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;idmap=uids=914-0-1;gids=914-0-1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Podman&amp;rsquo;s triplet is backing ID, mapped ID, length. Through that mount, host
&lt;code&gt;914:914&lt;/code&gt; appears as &lt;code&gt;0:0&lt;/code&gt; to the container. Frigate can initialize as root and
its files still land on btrfs and ZFS as host &lt;code&gt;914:914&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;article-diagram&#34;&gt;
    &lt;pre class=&#34;mermaid&#34;&gt;
flowchart LR
		F[Frigate container root 0:0] --&gt; M[Idmapped bind mount]
		M --&gt;|uids 914-0-1, gids 914-0-1| DISK[Backing files 914:914]
		N[Native rollback process 914:914] --&gt; DISK
		C[Copyparty uid 924 plus group 914] --&gt; DISK
&lt;/pre&gt;
    &lt;figcaption&gt;A per-bind idmap changes the ownership view for one mount; it does not recursively mutate the backing files.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This is different from the alternatives:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Mechanism&lt;/th&gt;
          &lt;th&gt;What it changes&lt;/th&gt;
          &lt;th&gt;Why I did or did not use it&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;--user 914:914&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Process identity&lt;/td&gt;
          &lt;td&gt;Broke Frigate&amp;rsquo;s root-requiring init&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;:U&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Recursively changes backing ownership&lt;/td&gt;
          &lt;td&gt;Mutates data and is expensive&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Whole-container user namespace&lt;/td&gt;
          &lt;td&gt;Ownership view for the container&lt;/td&gt;
          &lt;td&gt;Complicated GPU and group mappings&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Per-bind idmap&lt;/td&gt;
          &lt;td&gt;Ownership view for one mount&lt;/td&gt;
          &lt;td&gt;Preserved both image and host contracts&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;s6-reset-the-process-umask&#34;&gt;s6 reset the process umask
&lt;/h2&gt;&lt;p&gt;In a disposable directory, &lt;code&gt;--umask=0002&lt;/code&gt; produced directories at &lt;code&gt;0775&lt;/code&gt; and
files at &lt;code&gt;0664&lt;/code&gt;. Copyparty, running as UID 924 with supplementary GID 914, could
create, rename, and delete them.&lt;/p&gt;
&lt;p&gt;The real Frigate processes produced &lt;code&gt;2755&lt;/code&gt; directories and &lt;code&gt;0644&lt;/code&gt; files.
s6-overlay had reset the live process umask to &lt;code&gt;0022&lt;/code&gt; after Podman launched the
container. A shell test that bypassed the supervisor had proved the kernel and
idmap mechanics, not the application&amp;rsquo;s final runtime behavior.&lt;/p&gt;
&lt;p&gt;The reliable check was the live process state:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;grep &lt;span class=&#34;s1&#34;&gt;&amp;#39;^Umask:&amp;#39;&lt;/span&gt; /proc/PID/status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Because the image did not preserve the desired umask, I used default ACLs on
the recordings tree. Newly created directories inherit group write even when
the process requests a stricter base mode. Copyparty also joins supplementary
GID 914, and its own creation policy emits group-writable entries when it writes
into the same tree.&lt;/p&gt;
&lt;h2 id=&#34;final-permission-setup&#34;&gt;Final permission setup
&lt;/h2&gt;&lt;p&gt;The working setup uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stable host ownership through Frigate&amp;rsquo;s per-bind idmap;&lt;/li&gt;
&lt;li&gt;shared or supplementary groups selecting the intended permission class;&lt;/li&gt;
&lt;li&gt;setgid directories preserving group ownership;&lt;/li&gt;
&lt;li&gt;application &lt;code&gt;chmod_f&lt;/code&gt; and &lt;code&gt;chmod_d&lt;/code&gt; policy where the image ignored &lt;code&gt;UMASK&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;inherited default ACLs where s6 reset the process umask;&lt;/li&gt;
&lt;li&gt;repairs performed on every mergerfs backing branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For each shared mount, I test read, create, modify, rename, delete, &lt;code&gt;mkdir&lt;/code&gt; and
&lt;code&gt;rmdir&lt;/code&gt; using the real service UIDs and groups. I also test files created by the
actual application, not only a shell started in the same image. This was important
for Frigate because s6 changed the umask after Podman started the container.&lt;/p&gt;
&lt;p&gt;If mergerfs is involved, check and repair every backing branch. The permissions
shown through the merged path may come from a different branch than the file
being modified.&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
