Skip to content

vm driver: /etc/hosts is empty in a MicroVM sandbox, so localhost does not resolve #3934

Description

@vineethsai7

Summary

In a sandbox started by the MicroVM (vm) compute driver, /etc/hosts is an
empty file, and the guest's loopback DNS relay (nameserver 127.0.0.53) answers
a localhost query with SERVFAIL. As a result nothing in the sandbox can
resolve localhost: getent hosts localhost fails, dev servers and local MCP
servers that listen on localhost fail, and programs that open a local
listener at start exit at once. Antigravity CLI 1.2.12 (agy), for example,
exits with:

Failed to start: listen tcp: lookup localhost on 127.0.0.53:53: server misbehaving

The docker driver does not have the problem: Docker bind-mounts a generated
/etc/hosts (with 127.0.0.1 localhost and ::1 localhost) into every
container.

Environment

  • OpenShell 0.1.1 release binaries (openshell-gateway, openshell-driver-vm),
    macOS 27.0 on Apple silicon, Docker Desktop 29.1.5 (containerd image store).
  • Sandbox image built locally on ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:aeef1c63…
    (Ubuntu 24.04, glibc 2.39, hosts: files dns in /etc/nsswitch.conf).
  • [openshell.drivers.vm] sandbox_uid = 501, sandbox_gid = 20.

Steps to reproduce

  1. Start a gateway with compute_driver = "vm".

  2. Create a sandbox from any local image, for example the community base.

  3. In the sandbox:

    $ ls -l /etc/hosts; wc -c /etc/hosts
    -rwxr-xr-x 1 root root 0 … /etc/hosts
    0 /etc/hosts
    $ grep '^hosts:' /etc/nsswitch.conf
    hosts:          files dns
    $ cat /etc/resolv.conf
    nameserver 127.0.0.53
    options timeout:2 attempts:2
    $ getent hosts localhost; echo $?
    2
    $ node -e 'require("dns").lookup("localhost", (e, a) => console.log(e ? e.code : a))'
    EAI_AGAIN
    

The same failure reproduces without OpenShell by running the image with an
empty file mounted over /etc/hosts and a resolver at 127.0.0.53 that
answers SERVFAIL: getent, Node (EAI_AGAIN), Python
(Temporary failure in name resolution), Go programs with and without cgo
(lookup localhost on 127.0.0.53:53: server misbehaving) and agy all fail.
(curl resolves localhost internally and is not affected.)

Expected

localhost resolves to 127.0.0.1 and ::1 in every sandbox, as on the docker
driver, and the sandbox hostname resolves to a loopback address.

Cause

  • The vm driver prepares a local image's root filesystem through the Docker
    API (/containers/create, then /containers/{id}/export). Docker's init
    layer creates empty placeholder files for /etc/hosts, /etc/hostname and
    /etc/resolv.conf (mode 0755) over the image's own, and the export includes
    them, so the image's /etc/hosts is lost.
  • The guest init (run_post_overlay_setup in the driver's embedded init
    script) writes /etc/hostname (configure_hostname) and /etc/resolv.conf
    (the loopback DNS relay), but never /etc/hosts.
  • The DNS relay does not answer localhost (SERVFAIL), which RFC 6761 says
    resolvers should answer with loopback themselves.
  • The workload runs as the unprivileged sandbox uid with no capabilities and
    cannot write /etc, and init drop-ins run only when the driver injects them,
    so an image cannot repair it.

Suggested fix

In the guest init, next to configure_hostname, write /etc/hosts before
handing control to the workload, for example:

configure_hosts() {
    cat >"$(root_path /etc/hosts)" <<EOF
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
127.0.1.1	${sandbox_hostname}
EOF
    chmod 0644 "$(root_path /etc/hosts)"
}

(root-owned, 0644, with the hostname configure_hostname chose), or keep the
image's own /etc/hosts when it has one instead of Docker's init-layer
placeholder. Independently, the loopback DNS relay could answer localhost
and *.localhost with loopback addresses (RFC 6761 section 6.3) instead of
SERVFAIL, which also covers programs that skip /etc/hosts.

Workaround used downstream

DefenseClaw installs libnss-myhostname in its sandbox images and sets
hosts: files myhostname dns, which answers localhost for programs that use
the system resolver (glibc, Node, Python, Go built with cgo). Go's pure
resolver and static musl binaries read /etc/hosts and DNS themselves and stay
broken until the guest writes /etc/hosts.

🤖 Generated with Claude Code

Activity

  1. shiju-nv commented on Oct 4, 2026

    @shiju-nv
    Collaborator

    I reproduced this on a newer build than the report used, and traced where the empty file comes from.

    Reproduction. 0.1.3-dev.82+g8983642e2 (the rolling dev release), macOS 26.7 on Apple Silicon, VM driver with the default bootstrap image nvcr.io/nvidia/base/ubuntu:24.04, no local image. In a new sandbox:

    $ ls -l /etc/hosts
    -rw-r--r-- 1 502 root 0 Oct  4 09:23 /etc/hosts
    $ getent hosts localhost
    $            # no output, exit status 1
    

    Cause, from reading the guest init. crates/openshell-driver-vm/scripts/openshell-vm-sandbox-init.sh writes /etc/hostname and /etc/resolv.conf in run_post_overlay_setup, but nothing writes /etc/hosts, so the image's empty placeholder survives.

    A fix is up in #4182. It makes guest init write the loopback names when /etc/hosts is missing or empty, and leaves a non-empty file or a symlink alone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions