Skip to main content

Docker Volumes and Bind Mounts: Permission Pitfalls You Need to Avoid

Journal / Field notes
FIELD NOTE Field note

Docker Volumes and Bind Mounts: Permission Pitfalls You Need to Avoid

When I first started messing with Docker on my home lab, I ran into the classic "permission denied" error right after mounting a host folder. It felt like the container was looking at a locked door. I think the problem is easier to understand once you know how Docker translates host file ownership into the container.

Volumes vs. Bind Mounts – the basics

Docker volume is a managed storage area that lives under /var/lib/docker/volumes. The engine creates a directory there and takes care of setting the owner to root:root. When you run a container, Docker can mount that volume inside the container at any path you choose. Because the volume is owned by root, the container sees the files as root unless you explicitly change the user.

Bind mounts are different. You tell Docker to mount a specific host path, like /home/user/data. Docker does not change ownership or permissions on that host path; it simply makes the directory visible inside the container. If the host directory is owned by youruser:youruser, the container will see those same UID/GID numbers. If your container runs as www-data (UID 33), it will likely get a permission error.

Why permission errors show up

Two things usually cause the problem:

  • The UID/GID inside the container does not match the UID/GID of the host files.
  • The host directory has restrictive permissions like 700 or 750. Even if the UID matches, the mode can block access.

Docker does not magically translate UID 33 on the container side to UID 1000 on the host side. It just passes the numbers through. That means you have to align them yourself.

Inspecting the situation

First step is to see who owns the files on the host and what user the container runs as. On the host run:

# ls -ln /home/user/data

That prints numeric IDs instead of names, so you can compare them with the container user. Inside the container you can check the effective user with:

# id -u

If they differ, you have a mismatch.

Fixing host directory ownership

The simplest fix is to change the host directory ownership to match the container user. Suppose your container runs as appuser with UID 2000. You can run on the host:

# chown -R 2000:2000 /home/user/data

That makes the files appear owned by the same UID inside the container. If you cannot change ownership because the host directory is shared with other processes, you can adjust permissions instead.

Adjusting mode bits

Giving read/write permissions to "group" or "others" often solves the issue without touching owners. For example:

# chmod -R 775 /home/user/data

Now any user in the same group can read and write. If the container runs as a different GID, add that GID to the group ownership:

# chgrp -R 2000 /home/user/data

Be careful not to open the directory to the whole world unless you really need to.

Using userns remapping

Docker has a feature called user namespace remapping that translates container UIDs to different host UIDs. You enable it in /etc/docker/daemon.json:

{
  "userns-remap": "default"
}

After restarting the daemon, Docker creates a sub‑uid/sub‑gid range for containers. This can keep host files safe because the container never runs as root on the host. The downside is you lose the ability to directly map a specific UID without extra steps.

Specifying a user at run time

Instead of changing the host, you can tell Docker to run the container as a specific UID that matches the host files. The --user flag accepts UID or UID:GID syntax:

docker run -v /home/user/data:/app/data \
  --user $(id -u):$(id -g) myimage

This runs the container as the current host user. If the image expects a particular user name, you might need to add that UID to the image's /etc/passwd in a Dockerfile.

Example Dockerfile for a custom UID

FROM python:3.11-slim
# create a user with the same UID as the host user (adjust as needed)
ARG HOST_UID=1000
RUN adduser --uid $HOST_UID --disabled-password --gecos "" appuser
USER appuser
WORKDIR /app
COPY . .
CMD ["python", "app.py"]

Build with docker build --build-arg HOST_UID=$(id -u) -t myapp .. The resulting container runs as the host UID, so bind mounts work without extra chmod.

When volumes are the better choice

If you don’t need to see the exact host path, a Docker volume avoids most permission headaches. Docker creates the directory with root:root, and the container can read/write because it runs as root inside. You can still control ownership by adding a chown step in the Dockerfile or an entrypoint script:

RUN mkdir -p /data && chown -R appuser:appuser /data

Then mount the volume:

docker run -v mydata:/data myimage

If you later need to inspect the data on the host, you can locate the volume path with docker volume inspect mydata and adjust permissions there.

Quick checklist

  • Check host file UID/GID with ls -ln.
  • Check container user with id -u.
  • Align ownership using chown or chmod on the host.
  • Consider --user flag to match host UID.
  • Use a Docker volume when you don’t need the exact host path.

When you get the numbers straight, the "permission denied" errors disappear and your containers start up without the ugly log noise. The next time you mount a folder, I recommend you run ls -ln first and compare the IDs – it saves a lot of trial and error.

Photo by panumas nikhomkhai on Pexels