Troubleshooting
.crib-features/ in your project directory
Section titled “.crib-features/ in your project directory”When DevContainer Features are installed, crib creates a .crib-features/ directory inside your project’s build context during image builds. It’s cleaned up automatically after the build, but if the process is killed (e.g. SIGKILL, power loss), it may be left behind.
Add it to your global gitignore so it never gets committed in any project:
echo '.crib-features/' >> ~/.config/git/ignoreOr if you use ~/.gitignore as your global ignore file:
echo '.crib-features/' >> ~/.gitignoreMake sure git knows where your global ignore file is:
# only needed if you haven't set this beforegit config --global core.excludesFile '~/.config/git/ignore'Note: ~/.config/git/ignore is git’s default location (since git 1.7.12), so core.excludesFile only needs to be set if you use a different path.
Podman: short-name image resolution
Section titled “Podman: short-name image resolution”Podman requires fully qualified image names by default. If you see errors like:
Error: short-name "postgres:16-alpine" did not resolve to an alias and nounqualified-search registries are defined in "/etc/containers/registries.conf"Add Docker Hub as an unqualified search registry:
# /etc/containers/registries.conf (or a drop-in under /etc/containers/registries.conf.d/)unqualified-search-registries = ["docker.io"]This lets Podman resolve short names like postgres:16-alpine to docker.io/library/postgres:16-alpine, matching Docker’s default behavior.
Podman: “Executing external compose provider” warning
Section titled “Podman: “Executing external compose provider” warning”When using podman compose (which delegates to podman-compose), you’ll see this on every invocation:
>>>> Executing external compose provider "/usr/bin/podman-compose". Please see podman-compose(1) for how to disable this message. <<<<Silence it by adding to ~/.config/containers/containers.conf:
[engine]compose_warning_logs = falseNote: the PODMAN_COMPOSE_WARNING_LOGS=false env var is documented but does not work.
Podman: missing pasta and aardvark-dns
Section titled “Podman: missing pasta and aardvark-dns”If you see errors about pasta not found or aardvark-dns binary not found, install the networking packages:
# Debian/Ubuntusudo apt install passt aardvark-dnspasta provides rootless network namespace setup and aardvark-dns enables container DNS resolution. Without them, rootless Podman containers can’t start or resolve hostnames.
localhost:port not reachable even though the port is published
Section titled “localhost:port not reachable even though the port is published”If a container port is published but http://localhost:PORT fails (connection refused or reset),
check how localhost resolves on your machine:
getent hosts localhostIf it resolves to ::1 (IPv6) rather than 127.0.0.1 (IPv4), the port listener and the
address don’t match. Podman publishes ports to 0.0.0.0 (IPv4 only) by default in rootless
mode, so localhost → ::1 misses it.
Fix: Use 127.0.0.1 instead of localhost:
http://127.0.0.1:PORTOr, if you need localhost to work, add an explicit IPv4 entry to /etc/hosts:
127.0.0.1 localhost(Most systems have this but some distros only keep the IPv6 entry.)
Rootless Podman and bind mount permissions
Section titled “Rootless Podman and bind mount permissions”In rootless Podman, the host UID is remapped to UID 0 inside the container’s user namespace. This means bind-mounted workspace files appear as root:root inside the container, so a non-root remoteUser (like vscode) can’t write to them.
crib automatically adds --userns=keep-id when running rootless Podman. This maps your host UID to the same UID inside the container, so workspace files have correct ownership without any manual configuration.
If you need to override this behavior, set a different --userns value in your devcontainer.json:
{ "runArgs": ["--userns=host"]}When an explicit --userns is present in runArgs, crib won’t inject --userns=keep-id.
For Docker Compose workspaces, crib injects userns_mode: "keep-id" in the compose override. Since podman-compose 1.0+ creates pods by default and --userns is incompatible with --pod, crib also disables pod creation via x-podman: { in_pod: false } in the override.
Workspace files owned by a high UID (100000+) after switching to a non-root user
Section titled “Workspace files owned by a high UID (100000+) after switching to a non-root user”If a lifecycle hook (e.g. postCreateCommand: npm install) fails with permission denied, and
the workspace contains files owned by a UID like 100000, it means an earlier container run
created those files as root inside a rootless Podman container.
Why this happens: In rootless Podman with --userns=keep-id, container root (UID 0) maps to
a subordinate UID on the host (typically 100000). Any files created inside the container as
root — such as from a first run without remoteUser set, or before adding remoteUser to
devcontainer.json — are owned by that subordinate UID on the host filesystem. A subsequent run
with remoteUser set to a non-root user (like node or vscode) can’t write to those files.
Check for affected files:
ls -lan /path/to/project/node_modules | head -5If you see a high UID (100000+) instead of your own UID, those files are from a previous root container run.
Fix: Delete the affected directories from the host and then rebuild:
rm -rf node_modules # or whatever directory was created by the hookcrib rebuildIf the directory is large and rm -rf is slow, you can use podman unshare to remove it
faster from inside the same user namespace:
podman unshare rm -rf node_modulesBind mount changed permissions on host files
Section titled “Bind mount changed permissions on host files”If you ran chown or chmod inside a container on a bind-mounted directory and your host files now have wrong ownership, here’s how to recover.
Check the damage:
ls -la ~/.ssh/If you see an unexpected UID/GID (e.g., 100999 or root) instead of your username, the container wrote through to the host.
Rootless Podman/Docker: Use podman unshare (or docker equivalent) to run the fix inside the same user namespace that caused the problem:
podman unshare chown -R 0:0 ~/.sshThis maps container root (UID 0) back to your host user through the subordinate UID range, restoring your ownership. Then fix permissions:
chmod 700 ~/.sshchmod 600 ~/.ssh/*chmod 644 ~/.ssh/*.pub ~/.ssh/allowed_signers 2>/dev/nullRootful Docker: Your host user can’t chown these back without root:
sudo chown -R $(id -u):$(id -g) ~/.sshchmod 700 ~/.sshchmod 600 ~/.ssh/*chmod 644 ~/.ssh/*.pub ~/.ssh/allowed_signers 2>/dev/nullPrevention: Avoid bind-mounting directories where ownership matters (like .ssh/). The SSH plugin copies individual files into the container via docker exec rather than bind mounts, which sidesteps this entirely.
Plugin copy fails with “Read-only file system”
Section titled “Plugin copy fails with “Read-only file system””If you see warnings like:
level=WARN msg="plugin copy: exec failed" target=/home/vscode/.ssh/id_ed25519-sign.puberror="... cannot create /home/vscode/.ssh/id_ed25519-sign.pub: Read-only file system"A compose volume is already mounted at the same path the plugin is trying to write to. This
happens when your compose file manually mounts directories like ~/.ssh or ~/.claude into
the container:
# compose.yaml — these conflict with the ssh and coding-agents pluginsvolumes: - "./data/ssh:/home/vscode/.ssh" - "./data/claude:/home/vscode/.claude"The built-in plugins now handle SSH config/keys and Claude credentials automatically, so these compose mounts are redundant. Remove them from your compose file and let the plugins manage the files instead.
If you were using the compose mount as persistent storage (e.g. authenticating Claude inside the container), switch to the coding-agents plugin’s workspace mode instead.
remoteEnv: sh not found after setting PATH with ${PATH}
Section titled “remoteEnv: sh not found after setting PATH with ${PATH}”If you set PATH in remoteEnv using a bare ${PATH} reference:
{ "remoteEnv": { "PATH": "/home/vscode/.local/bin:${PATH}" }}Older versions of crib passed the literal string ${PATH} to docker exec -e, which overwrote the container’s real PATH and made basic commands like sh unfindable:
crun: executable file `sh` not found in $PATH: No such file or directoryFix: Update to crib v0.5.0+, which resolves bare ${VAR} references in remoteEnv against the container’s environment.
Alternatively, use the spec-standard ${containerEnv:PATH} syntax, which works in all versions:
{ "remoteEnv": { "PATH": "/home/vscode/.local/bin:${containerEnv:PATH}" }}SSH agent not working with Docker-in-Docker
Section titled “SSH agent not working with Docker-in-Docker”If SSH_AUTH_SOCK is set inside the container but the socket file doesn’t exist at that path,
the Docker-in-Docker feature is likely remounting /tmp as a fresh tmpfs, hiding the SSH agent
bind mount underneath it.
Verify: Check if /tmp is a separate tmpfs inside the container:
crib exec -- findmnt /tmpIf it shows tmpfs (not the container’s root filesystem), DinD has remounted /tmp.
Fix: Update to crib v0.7.0+, which mounts the SSH agent socket at /run/ssh-agent.sock
instead of /tmp/ssh-agent.sock to avoid this conflict.
If you’re on an older version, you can work around it by adding to your devcontainer.json:
{ "postStartCommand": "ln -sf /proc/1/root/tmp/ssh-agent.sock /tmp/ssh-agent.sock"}This creates a symlink from the DinD-mounted /tmp to the original mount point visible in PID 1’s
mount namespace.
crib exec can’t find tools installed by mise/asdf/nvm/rbenv
Section titled “crib exec can’t find tools installed by mise/asdf/nvm/rbenv”If crib exec -- ruby -v fails with “not found” but crib shell followed by ruby -v works,
the tool is installed via a version manager that modifies PATH through shell init files.
crib exec runs commands directly via docker exec without sourcing shell init files, so
PATH additions from mise, asdf, nvm, rbenv, etc. aren’t available.
Fix: Use crib run instead, which wraps commands in a login shell:
crib run -- ruby -vcrib run -- bundle installcrib run detects the container’s shell (zsh, bash, or sh) and runs your command through
$SHELL -lc '...', which sources login profiles and sets up PATH correctly.
Use crib exec for commands that don’t depend on shell init (system binaries, scripts with
absolute paths) or when you need raw docker exec behavior.
Package cache: bundler provider and version managers (mise/rbenv)
Section titled “Package cache: bundler provider and version managers (mise/rbenv)”The bundler cache provider sets BUNDLE_PATH=~/.bundle so that bundle install writes
gems into the cached volume. This overrides the default gem location, which means gems end up in
the volume instead of the version manager’s directory (e.g.
~/.local/share/mise/installs/ruby/3.4.7/lib/ruby/gems/).
This is generally fine for bundler-managed projects. The plugin also sets BUNDLE_BIN=~/.bundle/bin
and installs a /etc/profile.d/ script that adds it to PATH, so gem executables like rspec and
rubocop are available in crib shell and crib run after bundle install. crib exec doesn’t
source profile scripts, so use crib run for commands that depend on bundler binstubs.
Go: “permission denied” writing to module cache
Section titled “Go: “permission denied” writing to module cache”When using a Go base image (e.g. golang:1.26) with a non-root remoteUser, you may see:
go: writing stat cache: mkdir /go/pkg/mod/cache/download/...: permission deniedThe /go/pkg/mod/cache/ directory is owned by root in official Go images. When running as a non-root user, Go can’t write to it.
Fix in your Dockerfile:
RUN chmod -R a+w /go/pkgOr set a user-writable cache location in devcontainer.json:
{ "remoteEnv": { "GOMODCACHE": "/home/vscode/.cache/go/pkg/mod" }}This is not a crib issue. It affects any tool that runs Go containers with non-root users.