Shopify / Shopify/hydrogen

`shopify hydrogen dev --host` does not propagate to inner workerd config

Open
#3,759 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

inactive
Dominant language
TypeScript
Stars
2.1k
Forks
443
Avg merge
4d 19h
Merged PRs (30d)
24

Description

shopify hydrogen dev --host does not propagate to inner workerd config

Summary

When running shopify hydrogen dev --host inside a container, the outer Vite dev server correctly binds to 0.0.0.0, but the inner workerd (Cloudflare Workers runtime that MiniOxygen wraps) silently keeps its bind on 127.0.0.1. This makes external requests time out with instance refused connection even though the dev server appears to start successfully.

The host value is hard-coded as a string literal inside the compiled CLI bundle, so the --host flag has no path to reach it.

Environment

  • @shopify/cli 3.x (latest at the time of writing)
  • @shopify/hydrogen (current Pilot revision)
  • Node 24.x
  • Linux container (Debian slim), but reproducible on any container runtime
  • Started via npm run devshopify hydrogen dev --codegen --port 3456 --host

Reproduction

FROM node:24-slim
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates libatomic1 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN git clone --depth=1 https://github.com/Weaverse/pilot.git .
RUN npm ci
EXPOSE 3456
CMD ["npx", "shopify", "hydrogen", "dev", "--codegen", "--port", "3456", "--host"]
docker build -t hydrogen-repro .
docker run --rm -p 3456:3456 hydrogen-repro
# In another terminal:
curl --max-time 10 http://localhost:3456/
# → curl: (28) Operation timed out

Inside the container:

$ docker exec <id> sh -c 'cat /proc/net/tcp6 | awk "\$2 ~ /:0D80\$/"'
   0: 0000000000000000FFFF00000100007F:0D80 ...   ← 127.0.0.1:3456 only

(0100007F = 127.0.0.1 in little-endian hex.)

The dev server even prints a hint:

➜  Local:   http://localhost:3456/
➜  Network: http://172.17.0.2:3456/

— which is misleading. The Vite proxy is on the network, but workerd is not, so any request that bypasses the proxy (e.g. an HTTP/2 keepalive that pins to the bound socket) hangs forever.

Expected

--host propagates to both the outer Vite server and the inner workerd config. After shopify hydrogen dev --host, /proc/net/tcp6 should show :: or 0.0.0.0:3456 listening.

Actual

Vite is on 0.0.0.0. workerd is on 127.0.0.1. Requests proxied through Vite work; requests that land on the listener directly (Fly Replay, Cloud Run, ECS Fargate health checks, etc.) time out.

Root cause

host: "localhost" is a string literal in two locations inside the installed bundle:

  1. @shopify/cli/dist/workerd-*.js — the bundled workerd loader. The filename includes a content hash and varies across releases; the literal is consistent.
  2. @shopify/cli-hydrogen/dist/lib/mini-oxygen/workerd.js — present in older bundle layouts; still emitted in some Pilot lockfiles.

Both files set the workerd host config without checking any flag, env var, or option object. The CLI's flag parser receives --host, but the value never reaches the workerd config builder.

Workaround (current)

A sed-patch on the bundle, applied post-npm install:

# Pattern 1: @shopify/cli's workerd loader
find node_modules/@shopify/cli/dist -maxdepth 2 -type f -name "workerd-*.js" \
  | xargs -r sed -i 's|host: "localhost"|host: "0.0.0.0"|g'

# Pattern 2: @shopify/cli-hydrogen's mini-oxygen workerd (older layout)
sed -i 's|host: "localhost"|host: "0.0.0.0"|g' \
  node_modules/@shopify/cli-hydrogen/dist/lib/mini-oxygen/workerd.js \
  2>/dev/null || true

Brittle because the bundle filename changes per release. We pin to specific CLI versions to keep the patch reliable; an upstream fix would let us drop the pinning.

Documented externally: https://docs.weaverse.io/docker-deployment#1-dockerfile

Suggested fix

Plumb the existing --host flag value into the workerd config builder. Default to "localhost" when the flag is absent, so existing behavior is unchanged for local development.

Rough sketch (untested, but illustrates the surface area):

// inside the place that builds the workerd config
const host = flags.host === true ? '0.0.0.0'
           : typeof flags.host === 'string' ? flags.host
           : 'localhost'

workerdConfig.sockets.push({
  name: 'entry',
  address: `${host}:${port}`,  // was: `localhost:${port}`
  // ...
})

The --host flag is already declared as a boolean in the CLI's dev command's flags; this fix consumes its value rather than ignoring it.

Why this matters beyond our deployment

Anyone running Hydrogen dev mode inside a container hits this:

  • Docker on a developer's laptop with port-forward to a non-loopback interface
  • Fly.io, Render, Railway, Cloud Run, ECS Fargate
  • GitHub Codespaces (the user-facing URL is a proxied origin, not loopback)
  • Remote dev environments (Coder, Gitpod, etc.)

In all these the listener needs to be on 0.0.0.0 (or ::) for traffic to reach it, and --host is the documented knob. The current behavior is "the knob is wired but disconnected from the thing it claims to control".

Happy to send a PR if it helps — the change is small and the test could be a unit test that asserts the generated workerd config contains the requested host.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Begin with the dev command’s --host declaration and trace where its value should enter the workerd config builder; compare the generated paths in @shopify/cli/dist/workerd-*.js and @shopify/cli-hydrogen/dist/lib/mini-oxygen/workerd.js. Done means the requested host appears in generated workerd socket config while an absent flag keeps localhost; add or run a unit test for that config.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
backend, cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.