DevOps

Fix root:root Bind-Mount Ownership in VS Code Dev Containers

Fix root:root ownership for bind-mounted files in VS Code Dev Containers on Docker WSL2. Why chown fails and options: WSL2 filesystem, Docker volumes, or rsync.

1 answer 1 view

VS Code Dev Containers on Windows (WSL2 + Docker Desktop): Bind-mounted files owned by root:root, newly created files owned by non-root user (dev)

I’m using VS Code Dev Containers with Docker Desktop on Windows (WSL 2 backend).

Setup:

  • Windows 10
  • Docker Desktop with WSL 2

Dockerfile:

dockerfile
FROM ghcr.io/astral-sh/uv:0.9.21-python3.13-trixie AS builder

ARG SERVICE_PATH
WORKDIR /workspace/${SERVICE_PATH}

ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
ENV UV_PYTHON_DOWNLOADS=0

RUN --mount=type=cache,target=/root/.cache/uv \
 --mount=type=bind,source=${SERVICE_PATH}/uv.lock,target=uv.lock \
 --mount=type=bind,source=${SERVICE_PATH}/pyproject.toml,target=pyproject.toml \
 uv sync --locked --no-install-project

COPY ${SERVICE_PATH}/ .

RUN --mount=type=cache,target=/root/.cache/uv \
 uv sync --locked

FROM python:3.13-slim-bookworm

ARG SERVICE_PATH
ARG USERNAME=dev
ARG USER_UID=1000
ARG USER_GID=1000
WORKDIR /workspace/$SERVICE_PATH

RUN apt-get update && apt-get install -y --no-install-recommends \
 build-essential \
 libffi-dev \
 libpq-dev \
 make \
 curl \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

RUN groupadd --gid $USER_GID $USERNAME \
 && useradd --uid $USER_UID --gid $USER_GID --create-home --shell /bin/bash $USERNAME

COPY --from=ghcr.io/astral-sh/uv:0.9.21 /uv /uvx /bin/
COPY --from=builder --chown=$USERNAME:$USERNAME /workspace/$SERVICE_PATH/ .

ENV PATH=".venv/bin:$PATH"

USER $USERNAME

devcontainer.json:

json
{
 "workspaceFolder": "/workspace/services/users",
 "mounts": [
 "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"
 ],
 "remoteUser": "dev"
}

Problem:
All existing files and folders in the bind-mounted workspace appear owned by root:root inside the container, despite the container running as user dev.

Attempts:

  • COPY --chown=dev:dev in Dockerfile
  • chown -R in postCreateCommand
  • Running chown as root or via sudo

None of these change ownership of the existing bind-mounted files from the Windows host.

Observed behavior:

  • Deleting and recreating a file/directory inside the container results in dev:dev ownership, which persists after container rebuild.

Summary:

  • Existing mounted files: root:root
  • Newly created files (inside container): dev:dev

Question:
How can I recursively change ownership of all existing bind-mounted files to the non-root user (dev) in VS Code Dev Containers with WSL2 and Docker Desktop on Windows? What is the recommended best practice for handling file ownership in this setup?

Your files are owned by root because the workspace is being bind-mounted from the Windows-backed filesystem into the Docker engine; with VS Code Dev Containers and Docker WSL2 that Windows-mounted filesystem generally doesn’t honor POSIX ownership, so chown inside the container is ineffective. The reliable fixes are: move or clone the repo into the WSL2 (Linux) filesystem, use a Docker named volume and populate it (so you can chown its contents), or enable WSL automount metadata so chown is honored — then run a recursive chown (or use a postCreateCommand) as needed.


Contents


Why bind-mounted files appear as root:root (VS Code Dev Containers + Docker WSL2)

Short answer: your bind mount is exposing files from a Windows-backed filesystem (e.g., C:\ via /mnt/c) and that filesystem doesn’t provide true Unix UID/GID metadata when mounted into the Docker engine. Docker Desktop’s WSL2-backed engine will present those files inside the container with ownership that the host driver maps (often root:root), even if the container runs as your non-root user (dev). That’s why files created inside the container by the dev user are dev:dev (they’re created by that user inside the container), while files that already exist on the Windows host remain root:root.

A few authoritative notes and references:


How to change ownership recursively for bind mounts (practical options)

You basically have three reliable choices — I’ll list them from most recommended to least.

  1. Move or clone the repository into the WSL2 (Linux) filesystem — recommended
  • Why: files on the WSL2 distribution are real Linux files with POSIX metadata; chown works and is persistent.
  • Steps (high level): copy/clone the repo into your WSL distro (e.g., /home/youruser/projects/myrepo), open that folder in VS Code using Remote - WSL, then start the dev container. Once files live in WSL you can run:
  • sudo chown -R dev:dev /path/to/project
  • or inside the container: sudo chown -R dev:dev /workspace
  • Docs hint: the VS Code WSL docs explain using Dev Containers with source stored in WSL: https://code.visualstudio.com/remote/wsl and the community guidance repeatedly points to storing source inside Linux FS for containerized workflows (see https://stackoverflow.com/questions/64010923/developing-inside-docker-on-wsl2-ubuntu-from-vscode).
  1. Use a Docker named volume and populate it with files that you chown inside a container
  • Why: a volume is managed by Docker and is fully POSIX-capable; you can copy the Windows-host files into a volume from a temporary container and chown them there.
  • Example workflow (one-off population):
  1. Create the volume:
  • docker volume create my_project_vol
  1. Populate the volume from your host copy (run this from WSL so you can access /mnt/c path), then chown contents to UID/GID 1000 (dev):
  • docker run --rm -v my_project_vol:/workspace -v /mnt/c/Users/You/path/to/repo:/src alpine sh -c “cp -a /src/. /workspace && chown -R 1000:1000 /workspace”
  1. Update devcontainer.json to mount the named volume:
  • “mounts”: [ “source=my_project_vol,target=/workspace,type=volume” ]
  1. Start the dev container — files in the volume will have the ownership you set.
  1. Enable WSL automount metadata (advanced) or copy into WSL then chown
  • Why: WSL can be configured to store POSIX metadata for Windows files (so chown/chmod behave more like Linux). If you enable the metadata automount option in WSL and restart it, chown may start working on /mnt/c; after that a chown from the container or WSL will persist.
  • Caveats: this changes how drvfs behaves and can have side effects; treat carefully and test. If you prefer not to change automount options, copy the repo into WSL as in option (1).

When chown attempts fail

  • If chown returns “Operation not permitted” or chown seems to do nothing, that confirms the bind source is Windows-backed and the operation is unsupported at the mount layer (see the GitHub issue and Docker forum threads linked above). In that case, moving the files into WSL or into a Docker volume is the correct remedy — chown from inside the container won’t change ownership on a Windows-mounted filesystem.

Best practices for Dev Containers, file ownership, and WSL2 on Windows

  • Keep code in the WSL2 filesystem for day-to-day development (open the folder with Remote - WSL). That gives correct ownership, much better I/O perf, and avoids root:root bind-mount surprises (see https://code.visualstudio.com/remote/wsl).
  • Use Docker volumes for build artifacts, caches, or when you need container-controlled ownership. Volumes are the right place for data that must live with the container and be chowned at container time (see https://code.visualstudio.com/docs/devcontainers/containers).
  • Remember build-time chown (COPY --chown=…) only affects image layers; a later bind mount from the host overlays those files — so chown in the Dockerfile won’t change host files that are bind-mounted. That’s why Dockerfile chown didn’t help in your setup.
  • Add a postCreateCommand to adjust ownership when the mount actually supports chown. The VS Code docs mention this technique: https://code.visualstudio.com/remote/advancedcontainers/improve-performance. Example fallback in devcontainer.json:
  • “postCreateCommand”: “sudo chown -R dev:dev /workspace || true”
  • But remember: this only works when the mount supports chown.
  • If you must keep code on Windows and want live edits on Windows, look into a file-sync solution (rsync, Unison, Mutagen) that keeps a Linux copy in sync with Windows edits; work in the Linux copy for running containers. That gives you both correct ownership and live-edit convenience.

Recipes: move to WSL, populate a volume, or use rsync (step‑by‑step)

A. Move repo into WSL (quick, recommended)

  1. Open a WSL shell (Ubuntu) and run:
  • mkdir -p ~/projects
  • cp -a /mnt/c/Users//path/to/repo ~/projects/myrepo
  • sudo chown -R (idu):(id -u):(id -g) ~/projects/myrepo
  1. In Windows VS Code: use the Remote - WSL extension to open ~/projects/myrepo and rebuild the dev container.

B. Create and populate a named Docker volume (keeps ownership controlled by Docker)

  1. docker volume create my_project_vol
  2. Populate and chown (from WSL so /mnt/c is accessible):
  • docker run --rm -v my_project_vol:/workspace -v /mnt/c/Users//path/to/repo:/src alpine sh -c “cp -a /src/. /workspace && chown -R 1000:1000 /workspace”
  1. devcontainer.json snippet:
  • {
    “workspaceFolder”: “/workspace”,
    “mounts”: [“source=my_project_vol,target=/workspace,type=volume”],
    “remoteUser”: “dev”,
    “postStartCommand”: “sudo chown -R dev:dev /workspace || true”
    }
  1. Start the container. Files in /workspace will be owned by the UID/GID you set.

C. One-off fix when files already live in WSL or a POSIX-capable mount

  • Inside WSL or inside the container (when mount supports chown):
  • sudo chown -R dev:dev /workspace
  • Verify: ls -la /workspace

D. Sync approach (if you insist on editing on Windows)

  • Keep a working copy in WSL and use a sync tool to mirror changes from Windows to the WSL copy. Then mount the WSL copy into the container. This gives you correct permissions with live edits.

Troubleshooting & FAQ

Q — Why does COPY --chown in my Dockerfile not fix ownership?

  • Because a bind mount from the host overlays the files from the image. The image’s filesystem can be chowned at build time, but when the host directory is bind-mounted into /workspace it hides the image contents and the mount’s metadata governs ownership.

Q — Why can I create files as dev (they show dev:dev) but existing ones are root:root?

  • New files are created by the process running as dev inside the container and get dev ownership. Existing files created on the Windows host or by Docker Desktop’s mount driver keep the host/backing metadata (root:root).

Q — I ran sudo chown -R, but ownership didn’t change — what now?

  • That means the backing filesystem doesn’t support POSIX ownership changes (typical for Windows-mounted drives). Use one of the reliable options above: move files to WSL, copy into a Docker volume and chown there, or enable WSL automount metadata and retry.

Q — Where can I read more or see similar reports?


Sources


Conclusion

Because Windows-backed bind mounts don’t expose POSIX ownership to the container, you can’t reliably chown those files from inside the container; this is a known behavior with VS Code Dev Containers and Docker WSL2. The simplest, safest fix is to keep the repository in the WSL2 (Linux) filesystem or copy it into a Docker volume and chown there; if needed, enable WSL automount metadata so chown is honored. Those approaches give correct, persistent dev:dev ownership and avoid the root:root surprises.

Authors
Verified by moderation
Moderation
Fix root:root Bind-Mount Ownership in VS Code Dev Containers