The Permission Problem: A Terminal Session Reveals the Gap Between Containerized Infrastructure and Host Filesystem Realities

Introduction

In the life of any distributed systems project, there comes a moment when the carefully crafted scripts meet the messy reality of the operating system. This article examines a single user message from a coding session building a horizontally scalable S3-compatible storage architecture for the Filecoin Gateway. The message is deceptively simple: it shows the output of three shell commands—a stop, a start, and another stop—executed against a Docker Compose-based test cluster. But within those terminal lines lies a rich story about assumptions, permissions, container orchestration, and the subtle ways that infrastructure code reveals its weaknesses under real-world testing.

The message, sent by the user (index 281 in the conversation), captures a moment of empirical validation. The user has been iterating with an AI assistant to build a test cluster for "Kuri" storage nodes backed by YugabyteDB, all orchestrated through Docker Compose. The session has already produced several scripts—start.sh, stop.sh, init-data.sh, logs.sh, test.sh—and a docker-compose.yml defining a multi-service topology. What the user shows us in this single message is the raw output of exercising those scripts, and it reveals both what works and, more importantly, what does not.

The Message in Full

The user's terminal session, reproduced exactly as it appeared in the conversation, shows three sequential commands:

 theuser@biryani  ~/gw/test-cluster  pgf-port ● ? ↑2  ./stop.sh /data/fgw-test                                                                                              1 ↵  52135  22:07:25 
========================================
Stopping FGW Test Cluster
========================================

Data directory: /data/fgw-test

🛑 Stopping containers...
[+] down 4/4
 ✔ Container test-cluster-kuri-2-1   Removed                                                                                                                                                        0.0s 
 ✔ Container test-cluster-kuri-1-1   Removed                                                                                                                                                        0.0s 
 ✔ Container test-cluster-yugabyte-1 Removed                                                                                                                                                        0.2s 
 ✔ Network test-cluster_fgw-test     Removed                                                                                                                                                        0.1s 
✅ Cluster stopped successfully

========================================
Done!
========================================

To start the cluster again:
  ./start.sh /data/fgw-test

 theuser@biryani  ~/gw/test-cluster  pgf-port ● ? ↑2  ./start.sh /data/fgw2                                                                                                   ✔  52136  22:07:32 
========================================
FGW Test Cluster (2 Storage Nodes)
========================================

Data directory: /data/fgw2

Architecture:
  - kuri-1: S3 API (:8078) + Web UI (:9010)
  - kuri-2: Internal only (no exposed ports)
  - YugabyteDB: Shared metadata

✅ Docker image fgw:local exists

📁 Initializing data directories...
Initializing test cluster data directories in /data/fgw2
Setting permissions...
chmod: changing permissions of '/data/fgw2/yugabyte/conf': Operation not permitted
chmod: changing permissions of '/data/fgw2/yugabyte/conf/yugabyted.conf': Operation not permitted
chmod: changing permissions of '/data/fgw2/yugabyte/data': Operation not permitted
chmod: changing permissions of '/data/fgw2/yugabyte/data/yb-data': Operation not permitted
... (over 100 lines of similar errors) ...
chmod: changing permissions of '/data/fgw2/yugabyte/logs/yugabyted-ui.err': Operation not permitted

 theuser@biryani  ~/gw/test-cluster  pgf-port ● ? ↑2  ./stop.sh /data/fgw-test                                                                                              1 ↵  52137  22:07:39 
========================================
Stopping FGW Test Cluster
========================================

Data directory: /data/fgw-test

ℹ️  Cluster is not running (no containers found) 

The message contains no explanatory text from the user—no "hey, look at this problem" or "the stop works but start has issues." It is pure terminal output, a raw capture of what happened when the user pressed Enter three times. The absence of commentary is itself meaningful: the user is presenting evidence, not asking a question. This is a data point, submitted for the assistant to interpret.

Context: What Led to This Moment

To understand why this message matters, we must understand what came before it. The conversation leading up to this point shows an intense debugging session. The assistant had been building a test cluster infrastructure for a distributed S3 storage system. The architecture, as defined in a scalable-roadmap.md document, called for a three-layer design: stateless S3 frontend proxies that route requests to backend Kuri storage nodes, which in turn store data in their local RIBS blockstores and coordinate metadata through a shared YugabyteDB cluster.

The assistant had made a significant architectural error earlier in the session: it had configured the test cluster with Kuri nodes exposing S3 APIs directly and sharing a single configuration, violating the roadmap's requirement for separate stateless frontend proxy nodes. The user caught this and the assistant had to perform a major redesign, creating per-node independent settings files, restructuring the Docker Compose into the proper three-layer hierarchy, and implementing the routing layer as specified.

Several operational issues had already been discovered and fixed:

The Three Commands: A Narrative in Shell History

The terminal session tells a story in three acts.

Act One: The Successful Stop. The user runs ./stop.sh /data/fgw-test and sees clean, successful output. Four containers are removed (kuri-2, kuri-1, yugabyte, and the network). The script exits with code 1 (shown by the  1 ↵ in the prompt), but this is likely because docker-compose down returns non-zero when removing resources, or perhaps the script itself exits with 1 after showing "Done!"—the important thing is that the containers are gone and the cluster is stopped. The user's shell prompt shows they are on the pgf-port branch with 2 uncommitted changes, working from /home/theuser/gw/test-cluster.

Act Two: The Problematic Start. The user then runs ./start.sh /data/fgw2—note the different data directory. Instead of /data/fgw-test, they use /data/fgw2. This is a critical detail. The user is testing with a fresh data directory, but it turns out /data/fgw2 already contains files from a previous run. The script's init-data.sh phase attempts to set permissions with chmod, and this is where everything goes wrong.

The output shows over 100 lines of chmod: changing permissions of '...': Operation not permitted errors. Every single file and directory under /data/fgw2/yugabyte/ triggers a permission error. The files are owned by root (UID 0 inside the YugabyteDB container), and the user running the script (theuser) does not have permission to change their ownership or permissions. The errors cascade through the entire YugabyteDB data directory: configuration files, RocksDB data files, write-ahead logs, consensus metadata, tablet metadata, PostgreSQL data directory, logs—everything.

The script continues despite these errors (the chmod command likely uses 2>/dev/null suppression or simply ignores the exit code), and the output shows "✅ Docker image fgw:local exists" and proceeds to the initialization phase. But the permission errors are a clear signal that something is fundamentally wrong with how the script handles pre-existing data from container runs.

Act Three: The Anti-Climactic Stop. The user runs ./stop.sh /data/fgw-test a second time, but now the cluster was started with /data/fgw2, not /data/fgw-test. The script reports "Cluster is not running (no containers found)" because the containers that were started (if any) are associated with the /data/fgw2 directory, not /data/fgw-test. This is a subtle but important detail: the stop script checks for running containers, but the data directory argument is used primarily for path construction, and the containers may have been started under a different project name derived from the data directory path.

The Permission Problem: A Deep Dive

The chmod errors are the centerpiece of this message. They deserve careful analysis because they reveal multiple layers of assumptions and architectural decisions.

Why Does This Happen?

YugabyteDB, like many stateful Docker containers, runs its processes as the root user inside the container. When it writes data to mounted volumes, the files are created with root ownership (UID 0, GID 0). On Linux, when a Docker container mounts a host directory, the files written by the container retain the UID/GID of the process inside the container. Since the YugabyteDB process runs as root, all files in the mounted data directory are owned by root.

The init-data.sh script, written by the assistant, attempts to set permissions on these directories:

chmod -R 755 /data/fgw2/yugabyte

But the user theuser is not root, and does not own these files. The chmod command fails with "Operation not permitted" for every file. This is expected Unix behavior: only the file owner (or root) can change a file's permissions.

The Assumptions Behind the Bug

The assistant made several assumptions when writing the permission-setting code:

  1. The data directory would be empty or newly created. The script creates directories with mkdir -p and then sets permissions. But if the directory already contains files from a previous container run, those files have different ownership.
  2. The user would run as root. Many Docker orchestration scripts assume root access or use sudo. The assistant's script does neither.
  3. Permission changes would be idempotent. The assumption was that running chmod -R 755 on the data directory would be harmless even if files already existed. This is true only if the user owns the files.
  4. The chmod errors would be handled. The script does not check the exit code of chmod or suppress its errors. The errors stream to the terminal, creating a noisy and alarming output even though the script continues.

Why the Errors Are (Mostly) Harmless

Despite the alarming volume of errors, the permission problem may not prevent the cluster from functioning. Docker containers run as root by default (unless a USER directive is used in the Dockerfile or user: is specified in the Compose file). When the YugabyteDB container starts again, it runs as root and can read and write its own files regardless of the host-level permissions. The chmod errors are a host-side concern—they affect what the theuser user can do on the host, not what the container can do.

However, there are scenarios where this could cause real problems:

The Thinking Process Revealed by the Message

This message is unusual because it contains no explicit reasoning from the user. Unlike the assistant's messages, which often include detailed "Agent Reasoning" blocks explaining the thought process, the user's message is raw output. But we can infer the user's thinking from the sequence of commands.

The user is performing what software engineers call "exploratory testing." They are not following a script; they are probing the system's behavior. The choice to test with /data/fgw2 instead of reusing /data/fgw-test is telling. Perhaps the user wanted to see if the cluster would work with a different data directory. Perhaps they wanted to preserve the /data/fgw-test data for comparison. Perhaps they simply typed the wrong path. Whatever the reason, this choice inadvertently exposed the permission bug.

The user's decision to run stop.sh again after the failed start is also interesting. It suggests they were checking whether the stop script would correctly report that no cluster was running (since the start had failed or was using a different directory). This is a form of negative testing: verifying that the system handles the "nothing to do" case gracefully.

The fact that the user submitted this output without commentary suggests they trust the assistant to interpret it. This is a pattern common in AI-assisted development: the human provides the empirical results, and the AI provides the analysis. The message is an invitation for diagnosis.

Input Knowledge Required to Understand This Message

To fully grasp what this message communicates, a reader needs knowledge spanning several domains:

Docker and Containerization: Understanding that Docker containers run processes with specific user IDs, that mounted volumes preserve file ownership from inside the container, and that docker-compose down removes containers and networks but not volumes or images.

Linux File Permissions: Understanding the Unix permission model—that chmod requires ownership of the file (or root privileges), that files created by root inside a container are owned by root on the host, and that "Operation not permitted" is the standard error for permission violations.

YugabyteDB Architecture: Knowing that YugabyteDB is a distributed SQL database that stores data in multiple formats (RocksDB for tablet data, write-ahead logs, PostgreSQL for YSQL, etc.) and that its data directory has a complex nested structure.

The Filecoin Gateway Project: Understanding that "Kuri" nodes are storage nodes implementing the RIBS blockstore, that the architecture separates frontend proxies from storage nodes, and that the test cluster uses Docker Compose for orchestration.

Shell Scripting Conventions: Recognizing that ./stop.sh /data/fgw-test passes the data directory as an argument, that the script likely uses this to construct Docker Compose environment variables, and that the exit code shown in the shell prompt (e.g., 1 ↵) indicates the previous command's exit status.

Output Knowledge Created by This Message

This message creates several pieces of actionable knowledge:

  1. The stop.sh script works correctly for its primary purpose: stopping and removing containers. The clean output with checkmarks and the "Removed" status for each container confirms the script functions as designed.
  2. The permission handling in init-data.sh is broken for non-root users working with pre-existing data directories. The volume of errors (over 100 lines) indicates a systemic issue, not an edge case.
  3. The start.sh script is resilient to permission errors—it continues past the chmod failures and proceeds with initialization. This is arguably a good property (the script doesn't crash), but the noisy errors are a user experience problem.
  4. The data directory parameter affects container discovery. The second stop.sh call with /data/fgw-test reports no containers found, even though containers may have been started with /data/fgw2. This reveals that the stop script's container detection is tied to the data directory argument, which could lead to confusion if the user forgets which directory they used.
  5. The cluster infrastructure is testable and observable. The fact that the user can run these commands and see clear output (success, errors, status messages) means the scripts are fulfilling their purpose as test infrastructure, even when they expose bugs.

Mistakes and Incorrect Assumptions

Several incorrect assumptions are visible in this message, some made by the assistant when writing the scripts and some made by the user when testing them.

The assistant's mistakes:

  1. Assuming root-level access for permission management. The chmod -R approach assumes the user running the script has ownership of the files or is root. This is a common but dangerous assumption in Docker orchestration scripts.
  2. Not handling the pre-existing data case. The script treats the data directory as a fresh initialization target, but in practice, data directories persist across container restarts (that's the point of volumes). The script should detect existing files and handle them appropriately.
  3. Not suppressing expected errors. The chmod errors are expected when files are owned by root, but the script does not suppress them or provide a user-friendly message explaining why they occur and whether they matter.
  4. Not using sudo or providing instructions. If the script requires root privileges for certain operations, it should either use sudo internally (with appropriate warnings) or document the requirement. The user's assumptions:
  5. A different data directory would be clean. The user assumed /data/fgw2 would be empty or at least writable. In reality, it contained files from a previous run with different ownership.
  6. The start script would handle any data directory state. The user reasonably expected that the start script would either clean up or work correctly regardless of the data directory's prior state.
  7. The stop script would find containers regardless of data directory. The user ran stop.sh /data/fgw-test after starting with /data/fgw2, expecting it to find and stop the running containers. This didn't happen, revealing a coupling between the data directory and container discovery.

The Broader Implications for Distributed Systems Testing

This message, for all its apparent simplicity, illustrates a fundamental challenge in distributed systems development: the gap between the idealized architecture and the messy reality of the operating system.

The architecture document (scalable-roadmap.md) describes a clean, layered design with stateless proxies, storage nodes, and a shared metadata database. The Docker Compose file translates this architecture into container definitions with environment variables, port mappings, and dependency chains. But when the user runs the scripts, they encounter the operating system's permission model, which doesn't care about architectural elegance.

This tension between the logical architecture and the physical infrastructure is a recurring theme in distributed systems. The permission errors are not a bug in the storage node code or the proxy routing logic—they are a bug in the infrastructure code that bridges the gap between the architecture and the OS. And infrastructure bugs are often the hardest to catch because they only manifest in specific environmental conditions (non-root user, pre-existing data directory, specific filesystem layout).

The message also illustrates the importance of testing the tests. The test cluster scripts are themselves a form of test infrastructure, and they need to be as robust as the system they're testing. A test harness that fails with permission errors on the second run is a test harness that undermines confidence in the entire testing process.

Conclusion

The user's message at index 281 is a masterclass in the value of raw empirical data in software development. Three shell commands, executed in sequence, reveal more about the state of the test cluster infrastructure than any status report or code review could. The successful stop confirms that the basic orchestration works. The permission errors reveal a gap in the initialization logic. The second stop's "not running" message exposes the coupling between data directories and container discovery.

For the assistant receiving this message, the path forward is clear: fix the permission handling in init-data.sh to suppress errors gracefully, document the root-ownership issue, and perhaps add a --force flag or sudo integration. But more importantly, the assistant should recognize what the user has done: they have provided not just a bug report, but a complete testing session, captured in terminal output, that tells a coherent story about the system's behavior under real-world conditions.

This is the kind of message that separates experienced developers from novices. A novice might say "the script is broken." An experienced developer says nothing—they just run the commands and let the output speak for itself. The user's silence is not an absence of communication; it is a higher form of communication, one that trusts the evidence to tell its own story.

In the end, this message is about the gap between what we intend our systems to do and what they actually do when they meet the real world. Every line of chmod: Operation not permitted is a reminder that our assumptions about the environment are always incomplete, and that the only way to find the gaps is to run the code and watch what happens.