TypeScript

Vite entry module (index.html): fix .dockerignore

Reproduce a Docker build failure caused by excluded HTML, then repair the context with a single ignore-file exception.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

For a normal Vite web app, Could not resolve entry module (index.html) means the build cannot resolve its default HTML entry from the project root. When the same app builds locally but fails in Docker, inspect the files immediately after COPY . .: the host having index.html does not prove the build stage received it. If .dockerignore excludes that file, remove the exclusion or add !index.html after the matching rule, then rebuild using the same context and configuration.

The original Stack Overflow report came from an OpenShift deployment using Vite 2.9.9. Its build printed Could not resolve entry module (index.html). One answer reports an excluded HTML file in .dockerignore. That is a useful diagnosis to test, rather than proof of what happened on the asker's cluster.

The local Docker reproduction below verifies this exclusion mechanism with Vite 8.3.4 on Node 24.21.0, on 9 October 2026. It does not reproduce the hosted OpenShift deployment. The current diagnostic, including Docker's progress prefix, was:

text
#12 0.717 [UNRESOLVED_ENTRY] Cannot resolve entry module index.html.

The wording changed while the default HTML-entry requirement remains documented. The important comparison is between files available in the working tree and files available to Docker's source-copy instruction.

Why it happens#

Vite's production-build documentation defines the default app entry as the project's root index.html. The file supplies the document Vite processes, including module-script references to your application code. A JavaScript source directory on its own does not satisfy that default entry.

This explains why copying every file under src is insufficient. Vite needs the HTML first so that it can follow the module script into the application. In a standard app, the HTML belongs beside the package manifest. Vite's HTML and project-root guide explains that HTML is source code and part of the module graph, rather than an unprocessed file tucked inside public.

Docker adds another boundary. Its build-context documentation explains that matching files are removed from the context before it reaches the builder. Consequently, COPY . . copies the available context; it cannot recover something excluded by .dockerignore. The Dockerfile can look correct while the input set is incomplete.

In a multi-stage Dockerfile, check the stage that runs the compiler. The final web-server stage receives generated files only after the build succeeds. Changing that stage's web-server settings cannot put an excluded source document into the earlier stage. Likewise, permissions applied to files already copied into the image cannot restore a file that was never sent in the context.

Fix#

1. Compare the working tree with the copied build inputs#

From the directory containing the app's package manifest, check the local source HTML:

bash
pwd
test -f index.html

A failing file check means there is no HTML at that location. Restore the app's intended entry from source control, or correct the directory you are building. Do not create an empty placeholder just to silence the error: it would not connect Vite to your application module.

For Docker, temporarily insert this instruction immediately after the source-copy instruction and before the build:

dockerfile
RUN pwd && ls -la && test -f index.html

The check now inspects the build stage rather than the host. If the host check succeeds but this instruction fails, compare the build context and ignore rules before changing Vite. A successful dependency installation does not prove the HTML was copied: package files may be included while a separate rule excludes the app document.

ObservationWhat to inspect next
The host has HTML; the source-copy stage does notContext directory, ignore rules and source-copy paths
Neither location has HTMLThe checkout and the app's intended source document
The build stage has HTML at the expected locationThe configured entry, rather than an exclusion of that file

For the last case, Vite's root option identifies the HTML directory and defaults to the current working directory. Establish that location before deciding which file is missing. The Docker exclusion repair below applies when the required document is absent from the copied inputs.

2. Include the HTML in the Docker context#

This ignore file creates the missing-entry problem in the reproduction:

dockerignore
node_modules
dist
index.html

Keep the dependency and generated-output exclusions. Change only the treatment of the source HTML. The following version adds one exception:

dockerignore
node_modules
dist
index.html
!index.html

Docker documents that the last matching rule decides whether a file is included. Place the exception after the exclusion; a later matching exclusion would undo it. Removing the unnecessary index.html exclusion entirely is also a direct correction.

If your ignore file begins by excluding everything, an exception for the HTML solves only the HTML exclusion. The package manifest, lockfile, source directory and required configuration must also reach the builder. Reinclude those actual inputs as appropriate to your repository. Do not replace a carefully maintained allowlist with a blanket inclusion without checking what the context should contain.

There is a second ignore-file trap worth checking: a Dockerfile-specific ignore file takes precedence over the context-root .dockerignore. If CI selects a Dockerfile with its own adjacent ignore file, editing the root ignore file may leave the effective exclusion unchanged. Docker documents the naming and precedence on the same context page.

3. Rebuild from the intended context directory#

The final directory argument to a Docker build selects its context. When the shell is already in the app directory, this command selects that directory:

bash
docker build --progress=plain -t vite-entry-test .

Selecting a Dockerfile elsewhere does not, by itself, make that Dockerfile's folder the context. In a monorepo, check the command CI actually runs and the paths its COPY instructions expect. The applicable root ignore file belongs to the context directory, so looking only beside the selected Dockerfile can miss it. If a Dockerfile-specific ignore file exists, check that too.

An explicit instruction to copy the excluded file is not an alternative to correcting the context: Docker still cannot copy files removed from its input set. Verify the exception or remove the exclusion, then inspect the rebuilt stage. Avoid changing dependencies at the same time; otherwise a successful rebuild would not identify which change repaired the missing document.

Verify the fix#

The controlled reproduction used a minimal browser app, without a React plugin or TypeScript compilation, to isolate HTML entry resolution. The installed Vite version was 8.3.4, matching the npm registry release observed for this test. The official Node Alpine image supplied Node 24.21.0. These are the observed test versions, not a requirement to upgrade an existing app before investigating its missing file.

The package manifest was:

json
{
  "name": "vite-html-entry-repro",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "vite build"
  },
  "devDependencies": {
    "vite": "8.3.4"
  }
}

Generate the lockfile from that manifest before building:

bash
npm install --package-lock-only --ignore-scripts

Save this document as root index.html:

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vite HTML entry reproduction</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

Create the source directory, then save the following JavaScript as src/main.js:

bash
mkdir -p src
js
document.querySelector('#app').textContent = 'The HTML entry is included.';

Use this Dockerfile with the failing ignore file shown earlier:

dockerfile
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --include=dev
COPY . .
RUN node --version && npm --version && npm exec vite -- --version
RUN if test -f index.html; then echo 'ENTRY_PRESENT'; else echo 'ENTRY_MISSING'; fi
RUN npm run build

The first Docker build printed the missing-entry marker and failed at the Vite build with the diagnostic shown above.

Only adding !index.html changed the outcome: the marker became present, the build completed, and it generated HTML plus a bundled JavaScript asset under dist. The package manifest, lockfile, source module, Dockerfile and Vite version stayed the same. That comparison identifies the missing context file as sufficient to produce this failure and the inclusion exception as sufficient to repair this fixture.

For your own rebuilt test image, inspect the result without publishing a port:

bash
docker run --rm vite-entry-test sh -c 'test -f index.html && echo ENTRY_PRESENT; test -f src/main.js && echo SOURCE_PRESENT; test -s dist/index.html && echo DIST_HTML_PRESENT'

The reproduced image returned:

text
ENTRY_PRESENT
SOURCE_PRESENT
DIST_HTML_PRESENT

Success means the source entry survived the copy and Vite produced a non-empty output document. For a larger app, also inspect that output's asset references and run your existing browser checks. Merely finding the source HTML does not prove that every subsequent application import or runtime behaviour is correct.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

When the missing file is public HTML or a source module#

Check which tool emits the message before applying file-layout advice. Create React App expects its own HTML under public, whereas a standard Vite app processes root HTML. If the command is react-scripts start and Compose reports a missing public HTML file, follow the Create React App Docker bind-mount diagnosis. That page covers a runtime mount hiding files that were present in the image. A Dockerfile build losing root HTML through ignore rules is a different failure point.

For a React-and-TypeScript script that runs tsc before Vite, also read the actual stopping point. If the command fails with tsc: not found, it has not reached HTML entry resolution. The TypeScript compiler Docker-build diagnosis explains how missing build dependencies or an absent linked binary stop compilation first. Restoring HTML cannot supply a missing compiler.

Once Vite can resolve the HTML, it may reveal the next missing file: the module named in its script tag. If the error changes to a failed import of /src/main.tsx, continue with the Vite HTML-to-source path diagnosis. The HTML entry was found in that case; check the script reference against the root and copied source layout. Adding another HTML exception will not repair a wrong module path.

Finally, Vite's library mode documentation describes bundling a library for distribution. The original question includes an answer suggesting that mode when there is no HTML page. For a web application that already has an HTML entry, keep the application build and restore its input. Change to a library build only when a distributable library is the intended output; it is not the replacement for an app document accidentally removed from the Docker context.

Further reading: Fix: No loader is configured for ".node" files (Vite). Also see Vite failed to scan dependencies: unwanted HTML entries.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote