Skip to content

Commit 5fe219e

Browse files
committed
fix bind-mount failure on macOS Docker Desktop
Docker Desktop on macOS only shares specific directories with its Linux VM via VirtioFS ($HOME, /private, /Volumes, /tmp). Scripts installed to /usr/local/bin (e.g. via Homebrew) are not accessible, so Docker creates an empty directory placeholder in the VM. This makes /usr/bin/entrypoint a directory in the container, causing runc to fail with "is a directory". distrobox-create: on Darwin, copy distrobox-init, distrobox-export, and distrobox-host-exec to ~/.local/share/distrobox/ (always under $HOME, always VirtioFS-shared) before using them as bind-mount sources. distrobox-enter: the self-healing detection code reads .HostConfig.Binds, which Docker Desktop returns with a /host_mnt prefix (the VM's view of the VirtioFS mount). Strip this prefix on Darwin before checking file existence, preventing a spurious "mkdir: /host_mnt: Read-only file system" crash even for correctly-created containers. Signed-off-by: Eric Curtin <eric.curtin@docker.com>
1 parent c6cc6a3 commit 5fe219e

3 files changed

Lines changed: 83 additions & 0 deletions

File tree

distrobox

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,31 @@ distrobox_path="$(dirname "${0}")"
6262
distrobox_command="${1}"
6363
shift
6464

65+
# On macOS with Docker Desktop, only paths under $HOME are shared with the
66+
# Docker Linux VM via VirtioFS. Scripts installed to /usr/local/bin (e.g.
67+
# via Homebrew) are not accessible, so Docker creates an empty directory
68+
# placeholder in the VM, causing "exec: /usr/bin/entrypoint: is a directory".
69+
#
70+
# Maintain a managed cache in ~/.local/share/distrobox/ (always under $HOME,
71+
# always VirtioFS-shared). Copy each script only when the installed version
72+
# is newer than the cached copy, so upgrades are picked up automatically.
73+
if [ "$(uname -s)" = "Darwin" ]; then
74+
_dd_data_dir="${XDG_DATA_HOME:-"${HOME}/.local/share"}/distrobox"
75+
mkdir -p "${_dd_data_dir}"
76+
for _dd_script in distrobox-init distrobox-export distrobox-host-exec; do
77+
_dd_src="${distrobox_path}/${_dd_script}"
78+
[ ! -f "${_dd_src}" ] && _dd_src="$(command -v "${_dd_script}" 2> /dev/null || true)"
79+
if [ -f "${_dd_src}" ]; then
80+
_dd_dst="${_dd_data_dir}/${_dd_script}"
81+
if [ ! -f "${_dd_dst}" ] || [ "${_dd_src}" -nt "${_dd_dst}" ]; then
82+
cp -f "${_dd_src}" "${_dd_dst}"
83+
chmod +x "${_dd_dst}"
84+
fi
85+
fi
86+
done
87+
unset _dd_script _dd_src _dd_dst _dd_data_dir
88+
fi
89+
6590
# Simple wrapper to the distrobox utilities.
6691
# We just detect the 1st argument and launch the matching distrobox utility.
6792
case "${distrobox_command}" in

distrobox-create

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,25 @@ distrobox_hostexec_path="$(cd "$(dirname "${0}")" && pwd)/distrobox-host-exec"
102102
[ ! -e "${distrobox_export_path}" ] && distrobox_export_path="$(command -v distrobox-export)"
103103
[ ! -e "${distrobox_genentry_path}" ] && distrobox_genentry_path="$(command -v distrobox-generate-entry)"
104104
[ ! -e "${distrobox_hostexec_path}" ] && distrobox_hostexec_path="$(command -v distrobox-host-exec)"
105+
# On macOS with Docker Desktop, prefer the managed cache under
106+
# ~/.local/share/distrobox/ (always VirtioFS-shared) over the installed
107+
# paths. The main distrobox entry point keeps the cache up-to-date; when
108+
# distrobox-create is invoked directly the cache may also exist from a prior
109+
# run via the main entry point.
110+
if [ "$(uname -s)" = "Darwin" ]; then
111+
_dd_data_dir="${XDG_DATA_HOME:-"${HOME}/.local/share"}/distrobox"
112+
for _dd_script in distrobox-init distrobox-export distrobox-host-exec; do
113+
_dd_cached="${_dd_data_dir}/${_dd_script}"
114+
if [ -f "${_dd_cached}" ]; then
115+
case "${_dd_script}" in
116+
distrobox-init) distrobox_entrypoint_path="${_dd_cached}" ;;
117+
distrobox-export) distrobox_export_path="${_dd_cached}" ;;
118+
distrobox-host-exec) distrobox_hostexec_path="${_dd_cached}" ;;
119+
esac
120+
fi
121+
done
122+
unset _dd_script _dd_cached _dd_data_dir
123+
fi
105124
# If the user runs this script as root in a login shell, set rootful=1.
106125
# There's no need for them to pass the --root flag option in such cases.
107126
[ "${container_user_uid}" -eq 0 ] && rootful=1 || rootful=0

distrobox-enter

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,45 @@ if [ "${container_status}" != "running" ]; then
689689
# Here, we save the timestamp before launching the start command, so we can
690690
# be sure we're working with this very same session of logs later.
691691
log_timestamp="$(date -u +%FT%T).000000000+00:00"
692+
# On macOS with Docker Desktop + VirtioFS, only paths under $HOME and a
693+
# few system directories (/private, /Volumes) are shared with the Docker
694+
# VM. If distrobox-init lives outside those paths (e.g. /usr/local/bin
695+
# from Homebrew), Docker cannot find the bind-mount source in the VM and
696+
# creates an empty directory placeholder instead, causing the container
697+
# to fail with "exec: /usr/bin/entrypoint: is a directory".
698+
#
699+
# Detect this by reading the stored bind-mount source from
700+
# .HostConfig.Binds (the container config, not .Mounts which is empty
701+
# for stopped containers) and checking whether it is accessible as a
702+
# file. If it is not, copy the current distrobox-init to
703+
# ~/.local/share/distrobox/ (always under $HOME, always shared) and
704+
# write it to the stored source path so the bind-mount resolves to a
705+
# file when the container starts.
706+
entrypoint_source="$(${container_manager} inspect --type container \
707+
--format '{{range .HostConfig.Binds}}{{printf "%s\n" .}}{{end}}' \
708+
"${container_name}" 2> /dev/null |
709+
grep ':/usr/bin/entrypoint' | cut -d: -f1)"
710+
# On macOS with Docker Desktop, .HostConfig.Binds may store paths with a
711+
# /host_mnt prefix (Docker Desktop's VirtioFS mount prefix in the Linux VM).
712+
# Strip the prefix to get the actual macOS path for the file-existence check.
713+
entrypoint_source_check="${entrypoint_source}"
714+
if [ "$(uname -s)" = "Darwin" ]; then
715+
entrypoint_source_check="${entrypoint_source_check#/host_mnt}"
716+
fi
717+
if [ -n "${entrypoint_source}" ] && [ ! -f "${entrypoint_source_check}" ]; then
718+
distrobox_entrypoint_path="$(cd "$(dirname "${0}")" && pwd)/distrobox-init"
719+
[ ! -f "${distrobox_entrypoint_path}" ] &&
720+
distrobox_entrypoint_path="$(command -v distrobox-init 2> /dev/null)"
721+
if [ -f "${distrobox_entrypoint_path}" ]; then
722+
# Remove Docker's empty-directory placeholder (if present) and
723+
# replace it with the actual script so the bind-mount works.
724+
[ -d "${entrypoint_source}" ] && rmdir "${entrypoint_source}" 2> /dev/null
725+
mkdir -p "$(dirname "${entrypoint_source}")"
726+
cp "${distrobox_entrypoint_path}" "${entrypoint_source}"
727+
chmod +x "${entrypoint_source}"
728+
fi
729+
fi
730+
unset entrypoint_source entrypoint_source_check
692731
${container_manager} start "${container_name}" > /dev/null
693732
#
694733
# Check if the container is going in error status earlier than the

0 commit comments

Comments
 (0)