Problem Description
During local development a vector‑search service (e.g., Milvus, Faiss, or a custom LangChain vector store) is run inside a Docker Compose stack. The service stores its index files on a host‑mounted directory /data/index. When the service attempts to rebuild the index, the container crashes with errors such as:
Traceback (most recent call last):
File "/app/rebuild.py", line 42, in rebuild
faiss.write_index(index, "/data/index/segment_0.faiss")
PermissionError: [Errno 13] Permission denied: '/data/index/segment_0.faiss'
In addition, the container sometimes terminates abruptly with:
Killed (SIGKILL)
indicating an out‑of‑memory (OOM) kill during the intensive rebuild operation.
Root Cause Analysis
1. Bind‑mount permission mismatch
Docker bind mounts preserve the UID/GID of the host files. The host directory /data/index was created by the local user (or by sudo) with ownership root:root. The container runs as a non‑root user (e.g., UID 1000) defined in the Dockerfile. Because the UID inside the container does not match the UID owning the bind‑mounted path, any write attempt fails with Errno 13. This behavior is documented in the Docker Engine guide on bind mounts and file permissions.
2. Insufficient container resources
Rebuilding a large Faiss or Milvus index can require several gigabytes of RAM. Docker Desktop defaults to a 2 GB memory limit for containers unless overridden. When the process exceeds this limit, the kernel OOM killer terminates it, producing the “Killed (SIGKILL)” message. Docker’s resource constraints documentation explains that memory limits must be raised for memory‑intensive workloads.
3. Platform‑specific file‑sharing quirks
On macOS and Windows, Docker Desktop performs path conversion and applies additional permission checks. If the host folder is not listed in the Docker Desktop file‑sharing settings, write attempts are blocked, resulting in the same PermissionError even when UID/GID match. See the Docker Desktop file sharing guide for details.
Investigation and Debugging
- Inspect the host directory ownership
ls -ld /data/index drwxr-xr-x 2 root root 4096 Aug 30 10:12 /data/index - Check the UID/GID used inside the container
docker compose exec vector-service id uid=1000(user) gid=1000(user) groups=1000(user) - Verify the mount source path from the container’s perspective
docker compose exec vector-service stat -c "%u %g %n" /data/index 0 0 /data/indexThe output shows UID 0 (root) on the host side, confirming the mismatch.
- Examine container memory limits
docker compose exec vector-service cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2147483648Only 2 GB are allocated, which is insufficient for a 5 GB index rebuild.
- Capture OOM events (Linux host)
dmesg | grep -i kill | grep $(docker ps -qf "name=vector-service") [ 12345.678901] Out of memory: Kill process 5678 (python) score 1234 or sacrifice child - Check Docker Desktop file‑sharing (macOS/Windows)
Open Docker Desktop → Settings → Resources → File Sharing and verify that
/data(orC:\data) is listed.
Resolution
1. Align UID/GID between host and container
Option A – Change host directory ownership:
# On the host
sudo chown -R $(id -u):$(id -g) /data/index
Option B – Run the container with the host UID/GID (recommended for CI consistency):
# docker-compose.yml (before)
services:
vector-service:
image: myorg/vector-search:latest
volumes:
- ./data/index:/data/index
# No user specified → defaults to root or Dockerfile USER
# docker-compose.yml (after)
services:
vector-service:
image: myorg/vector-search:latest
user: "${UID}:${GID}" # inject host UID/GID at compose time
volumes:
- ./data/index:/data/index:rw
environment:
- UID=${UID}
- GID=${GID}
Invoke with:
export UID=$(id -u)
export GID=$(id -g)
docker compose up -d
2. Increase container memory limits
# docker-compose.yml (add resource limits)
services:
vector-service:
deploy:
resources:
limits:
memory: 8g
cpus: "2.0"
For Docker Desktop (where deploy.resources is ignored), use the mem_limit option:
services:
vector-service:
mem_limit: 8g
3. Ensure file sharing is enabled on macOS/Windows
Open Docker Desktop → Settings → Resources → File Sharing and add the absolute path to the host index directory (e.g., /data or C:\data). No further changes are required.
4. Optional: Use a named volume instead of a bind mount
Named volumes are managed by Docker and automatically receive the correct UID/GID.
# docker-compose.yml (named volume)
services:
vector-service:
volumes:
- index-data:/data/index
volumes:
index-data:
driver: local
Validation
- Re‑run the index rebuild command inside the container:
docker compose exec vector-service python /app/rebuild.py Index rebuild completed successfully. - Confirm that the index files exist and are writable:
docker compose exec vector-service ls -l /data/index -rw-r--r-- 1 user user 12345678 Aug 30 10:45 segment_0.faiss - Check container memory usage during rebuild:
docker stats vector-service --no-stream CONTAINER ID NAME CPU % MEM USAGE / LIMIT NET I/O a1b2c3d4e5f6 vector-service 12.34% 5.6GiB / 8GiB 12kB / 8kB - Verify no OOM events in the host kernel log:
dmesg | grep -i kill | grep $(docker ps -qf "name=vector-service") # (no output)
Operational Experience & Prevention
- Misleading symptom: The error message points to a “permission denied” path, but the underlying OOM kill can also surface as a truncated write error. Always check
dmesgfor OOM logs when the container exits abruptly. - Common incorrect assumption: “Running as root inside the container fixes everything.” On macOS/Windows the file‑sharing layer still enforces host‑side permissions, so root inside the container may still be denied.
- Production‑grade tip: Pin the UID/GID in the Dockerfile (e.g.,
ARG UID=1000,ARG GID=1000,RUN groupadd -g $GID app && useradd -u $UID -g $GID -m app) and always pass the same values at runtime. This eliminates drift between environments. - Monitoring guardrails: Add alerts on
container_memory_usage_bytesapproaching the limit and on Docker daemon events forcontainer_diewith OOM reasons. - Infrastructure safeguard: For large indexes, allocate a dedicated volume with sufficient disk space and set
tmpfsfor intermediate files if the host SSD is fast enough.
Best Practices and Prevention
- Always create bind‑mounted directories with the same UID/GID as the container process, or use the
user:directive in Compose. - Prefer named volumes for persistent data when UID/GID alignment is not critical.
- Set explicit memory limits that exceed the expected peak usage of index rebuilds (e.g., 2× the size of the index on disk).
- Enable Docker Desktop file sharing for any host path used by containers on macOS/Windows.
- Include a health‑check that verifies the index directory is writable:
healthcheck: test: ["CMD", "test", "-w", "/data/index"] interval: 30s timeout: 5s retries: 3
Related Topic Hub: Distributed Systems Troubleshooting Hub
FAQ
- Why does the index rebuild succeed on Linux but fail on macOS?
macOS Docker Desktop uses a file‑sharing layer that maps host permissions to the container. If the host path is not listed in the File Sharing settings, writes are blocked regardless of UID/GID. Adding the path to Docker Desktop’s shared folders resolves the issue.
- Can I keep the container running as root and still avoid permission errors?
Running as root bypasses UID mismatches, but on macOS/Windows the file‑sharing layer still enforces host‑side permissions. You must also ensure the host directory is writable by the user that Docker Desktop runs as (typically your login user).
- How do I determine the memory needed for an index rebuild?
As a rule of thumb, allocate at least 1.5–2× the size of the on‑disk index. Monitor
container_memory_usage_bytesduring a test rebuild and set themem_limitaccordingly. - What if SELinux is enforcing on the Linux host?
SELinux can block writes to bind‑mounted directories even when UID/GID match. Use
chcon -Rt svirt_sandbox_file_t /data/indexor add:zor:Zto the volume definition:volumes: - ./data/index:/data/index:Z - Is it safe to use the
:cachedflag on the volume?The
:cachedflag only changes the consistency semantics; it does not affect permission checks. It can improve performance for read‑heavy workloads but does not solve permission or OOM issues.