The problem
getPodmanArgs() — which adds --security-opt label=disable and, for a non-root remoteUser, --userns=keep-id — lives in src/spec-node/singleContainer.ts and is typed to DevContainerFromDockerfileConfig | DevContainerFromImageConfig. src/spec-node/dockerCompose.ts contains no podman handling at all, so a compose-based dev container on rootless podman gets neither flag.
Result: with rootless podman, the host user maps to container root, so a workspace bind mount appears as root:0 inside and the non-root remoteUser cannot write to it.
Reproduction
devcontainer CLI 0.87.0, podman 5.8.2 rootless, Linux. Two configs differing only in single-container vs compose, both "remoteUser": "vscode", same base image.
repro-sc/.devcontainer/devcontainer.json
{ "name": "sc", "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "remoteUser": "vscode" }
repro-co/.devcontainer/devcontainer.json + docker-compose.yml
{ "name": "co", "dockerComposeFile": "docker-compose.yml", "service": "app",
"workspaceFolder": "/workspace", "remoteUser": "vscode" }
services:
app:
image: mcr.microsoft.com/devcontainers/base:ubuntu
command: sleep infinity
volumes: [ "..:/workspace:cached" ]
devcontainer up --workspace-folder repro-sc --docker-path $(which podman) --docker-compose-path $(which docker-compose)
devcontainer up --workspace-folder repro-co --docker-path $(which podman) --docker-compose-path $(which docker-compose)
--docker-path/--docker-compose-path are explicit only to make podman detection unambiguous; lookupCLIVariant correctly reports Podman in both runs.
Result
podman inspect <sc> --format '{{.HostConfig.UsernsMode}} {{.HostConfig.SecurityOpt}}'
private [label=disable]
podman inspect <co> --format '{{.HostConfig.UsernsMode}} {{.HostConfig.SecurityOpt}}'
(empty) []
Both workspaces are dev:1000 on the host:
|
workspace owner inside |
write as remoteUser |
| single container |
vscode:1000 |
OK |
| compose |
root:0 |
Permission denied |
Expected
The compose path should apply the same podman handling as the single-container path — emitting the flags into the generated docker-compose.devcontainer.*.yml override (userns_mode, security_opt) for the primary service.
Related but different
#1004 / #1018 (omit keep-id for root) and #1284 / #1285 (derive the mapping from the remote user's real UID/GID) all concern how getPodmanArgs should behave. This is that the compose path never calls it. Whatever #1285 settles for the mapping should presumably apply to both paths.
The problem
getPodmanArgs()— which adds--security-opt label=disableand, for a non-rootremoteUser,--userns=keep-id— lives insrc/spec-node/singleContainer.tsand is typed toDevContainerFromDockerfileConfig | DevContainerFromImageConfig.src/spec-node/dockerCompose.tscontains no podman handling at all, so a compose-based dev container on rootless podman gets neither flag.Result: with rootless podman, the host user maps to container root, so a workspace bind mount appears as
root:0inside and the non-rootremoteUsercannot write to it.Reproduction
devcontainer CLI 0.87.0, podman 5.8.2 rootless, Linux. Two configs differing only in single-container vs compose, both
"remoteUser": "vscode", same base image.repro-sc/.devcontainer/devcontainer.json{ "name": "sc", "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "remoteUser": "vscode" }repro-co/.devcontainer/devcontainer.json+docker-compose.yml{ "name": "co", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspace", "remoteUser": "vscode" }--docker-path/--docker-compose-pathare explicit only to make podman detection unambiguous;lookupCLIVariantcorrectly reports Podman in both runs.Result
Both workspaces are
dev:1000on the host:remoteUservscode:1000root:0Expected
The compose path should apply the same podman handling as the single-container path — emitting the flags into the generated
docker-compose.devcontainer.*.ymloverride (userns_mode,security_opt) for the primary service.Related but different
#1004 / #1018 (omit
keep-idfor root) and #1284 / #1285 (derive the mapping from the remote user's real UID/GID) all concern howgetPodmanArgsshould behave. This is that the compose path never calls it. Whatever #1285 settles for the mapping should presumably apply to both paths.