Skip to main content

Deploying Weaverse Themes with Docker

For production storefronts, use Shopify Oxygen — it is the officially supported target for Hydrogen. Docker is a good fit for development, testing, review apps, and infrastructure you must control yourself.
For other self-hosting targets, see the Shopify documentation on self-hosting Hydrogen. For a real-world containerized theme, the Naturelle Demo Repository shows a working Docker setup.

Quick Flow

1

Add a Dockerfile and .dockerignore

Multi-stage build; .dockerignore must exclude .env*.
2

Build the image with no secrets in it

docker build -t my-storefront .
3

Supply configuration at run time

docker run --env-file .env -p 3000:3000 my-storefront
4

Deploy

fly secrets set ... then fly deploy (or the equivalent for your platform).

How it works

The container runs npm run start, which is shopify hydrogen preview — a Node-based simulation of the Oxygen Workers runtime. Two consequences matter:
  1. Runtime differences. Worker-specific behavior, caching, and performance characteristics are simulated, not identical to Oxygen. Validate anything runtime-sensitive before relying on it.
  2. Network binding. The preview server binds to localhost by default, which is unreachable from outside the container. It has to listen on 0.0.0.0 for the container to serve traffic.

Security rules

Never COPY a .env file into the image, and never bake credentials into a build layer. Docker image layers are readable by anyone who can pull the image — docker history and layer extraction expose them, and deleting the file in a later layer does not remove it from the earlier one. Secrets belong in the runtime environment, not the image.
Concretely:
  • Add .env* to .dockerignore so env files never enter the build context.
  • Do not use ENV or build args for tokens, API keys, or SESSION_SECRET.
  • Pass configuration at container start: --env-file, -e, your orchestrator’s secret store, or fly secrets.
  • Rotate any credential that has already been committed to an image or to Git.

Prerequisites

  • Docker (latest stable version)
  • Node.js — the Pilot theme requires >=22.12.0
  • Fly.io CLI if you are following the Fly example
  • A Weaverse theme project that builds locally (npm run build succeeds)

Configuration Files

1. .dockerignore

Create this first, so the build context never contains secrets or junk:
.dockerignore
.env.example is safe to keep out too — the pattern .env.* excludes it, and the image does not need it.

2. Dockerfile

Dockerfile
npm run start runs shopify hydrogen preview, which lives in @shopify/cli — a dev dependency. That is why the production stage copies the full node_modules from the build stage rather than running npm ci --omit=dev.

3. Environment variables

Keep a local .env for development (it is already in the theme’s .gitignore), and use .env.example as the reference list of variables your theme reads:
.env
Run the container with those values injected at start time. Before deploying, verify that the installed Shopify CLI version exposes the process environment as Hydrogen worker bindings:
Exercise a route that reads the required bindings after the container starts. If your CLI version does not forward the process environment, write a .env file at container start from the injected values via an entrypoint script — never at build time. The file then exists only in the running container’s writable layer, not in the distributable image.

Cloud Deployment (Fly.io)

1. fly.toml

fly.toml
Do not put tokens or SESSION_SECRET in the [env] section of fly.toml — that file is committed to Git. Use fly secrets instead.

2. Set secrets

Fly secrets are encrypted at rest and injected into the machine’s environment at run time:
Setting a secret triggers a rolling restart so the new values take effect.

3. Deploy

The same pattern applies elsewhere: ECS task definition secrets, Cloud Run --set-secrets, Kubernetes Secret + envFrom, Render/Railway environment settings. All of them inject variables into the process environment, which is exactly what the container expects.

Troubleshooting

Container starts but nothing responds on the mapped port The host binding patch did not apply. Check the build log for the WARNING: no workerd host binding patched line and re-check the CLI file layout for your @shopify/cli version. Build failures Confirm the Node version in the base image satisfies the theme’s engines.node (>=22.12.0), that npm run build succeeds locally, and that Docker has enough memory allocated. Missing configuration at run time Check the values actually reached the container: docker exec <container> printenv | sort, or fly ssh console -C printenv. Variable names are case-sensitive. Weaverse Studio preview does not load Verify WEAVERSE_PROJECT_ID is set in the running environment and that the deployed URL is registered on your Weaverse project. See Preview Errors.

Limitations and Considerations

  1. Runtime simulation — shopify hydrogen preview approximates the Workers runtime in Node. Not every Worker API or performance characteristic matches Oxygen.
  2. Image size — the production stage ships the full node_modules because the preview server is part of the Shopify CLI.
  3. Operational ownership — scaling, TLS, log retention, and monitoring are yours to configure. Oxygen handles these for you.