<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>acls on Samirtech</title>
    <link>https://samirtech.org/tags/acls/</link>
    <description>Recent content in acls on Samirtech</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 25 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://samirtech.org/tags/acls/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Docker, NFS and TrueNAS: Getting Permissions and ACLs Right</title>
      <link>https://samirtech.org/posts/docker-nfs-truenas-permissions-and-acls/</link>
      <pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate>
      
      <guid>https://samirtech.org/posts/docker-nfs-truenas-permissions-and-acls/</guid>
      <description>An NFS share can mount successfully, show every file and still refuse to let a container create, rename or delete anything.
The confusing part is that the failure crosses three systems:
container process -&amp;gt; bind mount on the Docker host -&amp;gt; NFS client identity -&amp;gt; TrueNAS share mapping -&amp;gt; dataset ACL In my case, the ordinary account on the Docker host used UID and GID 1000:1000, while the established TrueNAS account used 3000:3000.</description>
      <content>&lt;p&gt;An NFS share can mount successfully, show every file and still refuse to let a container create, rename or delete anything.&lt;/p&gt;
&lt;p&gt;The confusing part is that the failure crosses three systems:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;container process
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -&amp;gt; bind mount on the Docker host
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -&amp;gt; NFS client identity
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -&amp;gt; TrueNAS share mapping
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -&amp;gt; dataset ACL
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In my case, the ordinary account on the Docker host used UID and GID &lt;code&gt;1000:1000&lt;/code&gt;, while the established TrueNAS account used &lt;code&gt;3000:3000&lt;/code&gt;. The names could match, but those numbers did not.&lt;/p&gt;
&lt;p&gt;The safe solution was not to renumber either machine. It was to make the server-side identity handling and ACL explicit, then test the effective access through the same path the application uses.&lt;/p&gt;
&lt;h2 id=&#34;names-are-for-people-ids-cross-the-wire&#34;&gt;Names are for people; IDs cross the wire&lt;/h2&gt;
&lt;p&gt;Traditional Unix permission checks are based primarily on numeric user and group IDs. These two identities are not automatically equivalent:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Docker host: media -&amp;gt; 1000:1000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TrueNAS:     media -&amp;gt; 3000:3000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;NFSv4 can support name-based identity mapping in a deliberately configured environment, but using NFSv4 does not prove that mapping is working. The useful question is not what the account is called. It is which identity TrueNAS sees for the request.&lt;/p&gt;
&lt;h2 id=&#34;why-i-did-not-change-every-uid&#34;&gt;Why I did not change every UID&lt;/h2&gt;
&lt;p&gt;Renumbering an established account can affect far more than one share:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;existing dataset ownership;&lt;/li&gt;
&lt;li&gt;local bind mounts and container configuration;&lt;/li&gt;
&lt;li&gt;SMB access;&lt;/li&gt;
&lt;li&gt;application databases;&lt;/li&gt;
&lt;li&gt;backups and replication jobs;&lt;/li&gt;
&lt;li&gt;snapshots containing old ownership; and&lt;/li&gt;
&lt;li&gt;other NFS clients.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A recursive &lt;code&gt;chown&lt;/code&gt; is especially risky on shared data. It can destroy intentional ownership boundaries or interact badly with inherited ACL entries.&lt;/p&gt;
&lt;p&gt;Changing IDs may be appropriate during a planned migration, but it is a poor first response to one permission error.&lt;/p&gt;
&lt;h2 id=&#34;let-truenas-define-the-access-policy&#34;&gt;Let TrueNAS define the access policy&lt;/h2&gt;
&lt;p&gt;TrueNAS is authoritative for the exported dataset. Depending on the design, the right solution may be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an ACL entry for the identity arriving from the Docker host;&lt;/li&gt;
&lt;li&gt;access through a shared group;&lt;/li&gt;
&lt;li&gt;a narrowly scoped NFS user or group mapping;&lt;/li&gt;
&lt;li&gt;deliberately configured NFSv4 identity mapping; or&lt;/li&gt;
&lt;li&gt;a dedicated dataset for one application&amp;rsquo;s data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Map-root and map-all style settings have very different consequences. Root mapping controls how remote root is represented. Mapping all requests to one account can suit a tightly scoped single-purpose export, but it also removes useful attribution and can grant more access than intended.&lt;/p&gt;
&lt;p&gt;Use the narrowest option that works, on the smallest practical export. Identity mapping does not replace the dataset ACL: the resulting server-side identity must still have the required permissions.&lt;/p&gt;
&lt;h2 id=&#34;container-identity-is-a-separate-decision&#34;&gt;Container identity is a separate decision&lt;/h2&gt;
&lt;p&gt;Some images accept &lt;code&gt;PUID&lt;/code&gt; and &lt;code&gt;PGID&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;application&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;&amp;lt;IMAGE&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;PUID&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;PGID&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ae81ff&#34;&gt;/mnt/&amp;lt;share&amp;gt;/&amp;lt;directory&amp;gt;:/data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These variables are image conventions, not Docker features. Other images use Compose&amp;rsquo;s &lt;code&gt;user&lt;/code&gt; setting, and some start as one account before dropping privileges to another.&lt;/p&gt;
&lt;p&gt;Verify the running process rather than trusting the Compose file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker exec &amp;lt;container&amp;gt; id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker exec &amp;lt;container&amp;gt; ps -eo user,group,pid,args
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Changing a container to &lt;code&gt;3000:3000&lt;/code&gt; might make one NFS write succeed while breaking its local configuration directories. Server-side mapping or ACLs are often less disruptive.&lt;/p&gt;
&lt;h2 id=&#34;root-in-a-container-is-not-necessarily-root-on-the-nas&#34;&gt;Root in a container is not necessarily root on the NAS&lt;/h2&gt;
&lt;p&gt;NFS commonly applies root squash. A request from client UID &lt;code&gt;0&lt;/code&gt; becomes an anonymous or restricted identity on the server.&lt;/p&gt;
&lt;p&gt;That is a security boundary, not an inconvenience to disable casually. A root container may be able to read or create files yet fail to change ownership. First establish whether the application truly needs &lt;code&gt;chown&lt;/code&gt;, or merely needs create, write, rename and delete access.&lt;/p&gt;
&lt;p&gt;If elevated mapping is genuinely required, keep it limited to a dedicated export and trusted clients.&lt;/p&gt;
&lt;h2 id=&#34;posix-mode-bits-are-not-the-whole-acl&#34;&gt;POSIX mode bits are not the whole ACL&lt;/h2&gt;
&lt;p&gt;Commands such as these remain useful:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ls -ldn /mnt/&amp;lt;share&amp;gt;/&amp;lt;directory&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stat -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%u:%g %a %n&amp;#39;&lt;/span&gt; /mnt/&amp;lt;share&amp;gt;/&amp;lt;directory&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;They show numeric ownership and POSIX mode bits. They do not necessarily show the complete policy on a dataset using NFSv4 ACLs.&lt;/p&gt;
&lt;p&gt;An NFSv4 ACL can contain named users and groups, allow and deny entries, inheritance flags, and distinct rights for creating, deleting and traversing. It should not be treated as a verbose version of &lt;code&gt;chmod&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Inspect and modify the dataset with tools appropriate to its configured ACL type. Do not use recursive &lt;code&gt;chmod&lt;/code&gt;, &lt;code&gt;chown&lt;/code&gt; or ACL replacement as a substitute for understanding the existing policy.&lt;/p&gt;
&lt;h2 id=&#34;inheritance-matters&#34;&gt;Inheritance matters&lt;/h2&gt;
&lt;p&gt;Fixing access to the top-level directory is not enough. Newly created files and subdirectories need suitable inherited entries.&lt;/p&gt;
&lt;p&gt;Test that the intended identity can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;traverse each parent directory;&lt;/li&gt;
&lt;li&gt;create a file and a directory;&lt;/li&gt;
&lt;li&gt;append to and rename the file;&lt;/li&gt;
&lt;li&gt;delete it from the directory; and&lt;/li&gt;
&lt;li&gt;create content another intended service can subsequently read or modify.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A parent ACL can permit creation while producing a child that another service cannot process. Existing children also do not necessarily inherit a newly added rule retrospectively.&lt;/p&gt;
&lt;h2 id=&#34;test-the-direct-path&#34;&gt;Test the direct path&lt;/h2&gt;
&lt;p&gt;A successful write in the TrueNAS shell proves little about a request made by a container. Test each layer.&lt;/p&gt;
&lt;h3 id=&#34;on-the-docker-host&#34;&gt;On the Docker host&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;findmnt -T /mnt/&amp;lt;share&amp;gt;/&amp;lt;directory&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;findmnt -no SOURCE,FSTYPE,OPTIONS -T /mnt/&amp;lt;share&amp;gt;/&amp;lt;directory&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ls -ldn /mnt/&amp;lt;share&amp;gt;/&amp;lt;directory&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then perform a harmless test as the relevant host account in a disposable directory:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo -u &amp;lt;host-user&amp;gt; sh -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  f=&amp;#34;/mnt/&amp;lt;share&amp;gt;/&amp;lt;test-directory&amp;gt;/.permission-test-$$&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  : &amp;gt; &amp;#34;$f&amp;#34; &amp;amp;&amp;amp; printf &amp;#34;test\n&amp;#34; &amp;gt;&amp;gt; &amp;#34;$f&amp;#34; &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  mv &amp;#34;$f&amp;#34; &amp;#34;$f.renamed&amp;#34; &amp;amp;&amp;amp; rm -- &amp;#34;$f.renamed&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;inside-the-container&#34;&gt;Inside the container&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker exec &amp;lt;container&amp;gt; id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker exec &amp;lt;container&amp;gt; sh -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  d=&amp;#34;&amp;lt;container-test-path&amp;gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  f=&amp;#34;$d/.permission-test-$$&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  : &amp;gt; &amp;#34;$f&amp;#34; &amp;amp;&amp;amp; printf &amp;#34;test\n&amp;#34; &amp;gt;&amp;gt; &amp;#34;$f&amp;#34; &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  mv &amp;#34;$f&amp;#34; &amp;#34;$f.renamed&amp;#34; &amp;amp;&amp;amp; rm -- &amp;#34;$f.renamed&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the decisive test because it uses the effective process identity, bind mount and NFS route used by the application.&lt;/p&gt;
&lt;p&gt;Where possible, also reproduce the application&amp;rsquo;s real operations. Creating a file and renaming one can require different ACL rights.&lt;/p&gt;
&lt;h2 id=&#34;a-safe-diagnostic-sequence&#34;&gt;A safe diagnostic sequence&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Record the host user&amp;rsquo;s numeric identity with &lt;code&gt;id &amp;lt;host-user&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Record the container&amp;rsquo;s effective UID, GID and supplementary groups.&lt;/li&gt;
&lt;li&gt;Confirm that the expected NFS mount backs the exact host path.&lt;/li&gt;
&lt;li&gt;Confirm that neither the NFS nor container mount is read-only.&lt;/li&gt;
&lt;li&gt;Identify the dataset&amp;rsquo;s ACL type.&lt;/li&gt;
&lt;li&gt;Review the complete dataset ACL and inheritance flags.&lt;/li&gt;
&lt;li&gt;Review map-root, map-all and anonymous identity settings.&lt;/li&gt;
&lt;li&gt;Determine which identity TrueNAS sees after mapping.&lt;/li&gt;
&lt;li&gt;Test create, append, rename, delete, &lt;code&gt;mkdir&lt;/code&gt; and &lt;code&gt;rmdir&lt;/code&gt; on the host.&lt;/li&gt;
&lt;li&gt;Repeat the test inside the container.&lt;/li&gt;
&lt;li&gt;Inspect the owner and ACL of newly created objects.&lt;/li&gt;
&lt;li&gt;Change one layer at a time and repeat the same tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Before changing a share or ACL, preserve its configuration. Test against a non-production directory and avoid broad recursive operations.&lt;/p&gt;
&lt;h2 id=&#34;the-resulting-design&#34;&gt;The resulting design&lt;/h2&gt;
&lt;p&gt;For this kind of environment, a maintainable design is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;keep the Docker host&amp;rsquo;s established &lt;code&gt;1000:1000&lt;/code&gt; identity;&lt;/li&gt;
&lt;li&gt;keep the TrueNAS user&amp;rsquo;s established &lt;code&gt;3000:3000&lt;/code&gt; identity;&lt;/li&gt;
&lt;li&gt;run each container with a deliberate, documented account;&lt;/li&gt;
&lt;li&gt;translate or authorise that access on the TrueNAS side;&lt;/li&gt;
&lt;li&gt;preserve root squash;&lt;/li&gt;
&lt;li&gt;apply and verify suitable inheritance; and&lt;/li&gt;
&lt;li&gt;test access through the actual container path.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The aim is not to make every number identical. It is to make the server&amp;rsquo;s interpretation of each request explicit, narrow and testable.&lt;/p&gt;
</content>
    </item>
    
  </channel>
</rss>
