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

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