Deploy a Vite + React app

A Vite single-page app, built to static files and served on a live HTTPS URL.

A Vite app compiles to plain static files in dist/. Dockhold recognises a Vite project on every account and does exactly this for you: npm ci, npm run build, then serve dist/ on the assigned port. The short Dockerfile below is the same recipe, written down, for when you want to pin exactly how the app builds and starts. Either path deploys the same way every time.

In a hurry? Start from the ready-made vite-react-starter template: click Use this template on GitHub, then deploy it. Or follow the steps below to add this to a project you already have.

1. Add a start command that serves the build

Add the serve static server and a start script. Vite already gives you a build script:

npm install --save-dev serve

Then in package.json:

{
  "scripts": {
    "build": "vite build",
    "start": "serve -s dist -l $PORT"
  }
}

serve -s dist serves the built app with single-page-app fallback (so client-side routes work), and -l $PORT binds the port Dockhold gives you, on all interfaces.

Your app must listen on 0.0.0.0 and read its port from the PORT environment variable. Never localhost, never a hardcoded port. Dockhold assigns PORT at runtime; an app that ignores it can't receive traffic.

2. Add a Dockerfile

Put this Dockerfile at the repo root:

# Build the app to static files
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Serve the build on the assigned port
FROM node:22-alpine
WORKDIR /app
RUN npm install -g serve@14
COPY --from=build /app/dist ./dist
# Shell form so $PORT expands at runtime
CMD serve -s dist -l $PORT

A Dockerfile at the repo root takes priority and tells Dockhold exactly how to build and run the app. Without one, Dockhold builds a Vite project with npm run build and serves dist/ on the assigned port. A custom outDir in vite.config is the one thing it does not follow; keep the default or include the Dockerfile.

3. Connect the repo and deploy

  1. Push the project (with the Dockerfile) to a GitHub repository.
  2. Open the dashboard, connect GitHub, and pick the repo.
  3. Dockhold builds from your Dockerfile and runs it.

Your app goes live at https://<your-app>.dockhold.app with HTTPS handled for you. Every later push to your main branch redeploys automatically.

4. Configuration

A static SPA has no server, so it can't read your dashboard variables directly. To configure it from the dashboard (an API URL, for example), use runtime config: have the container write your dashboard values into the page when it starts, and read them from window.__APP_CONFIG__. You set and change those values in the dashboard with no rebuild. The full-stack recipe and the fullstack-web template show the exact setup.

Don't bake config into the build with a committed .env.production, and never put a secret in browser code. Anything shipped to the browser is public, readable by every visitor.

Troubleshooting

  • Blank page, and the browser is fetching /src/main.jsx: the un-built source is being served instead of dist/. Make sure the Dockerfile is at the repo root so the app is built before it's served.
  • 404 on refresh of a client route: make sure you used the -s flag (serve -s dist). It enables the SPA fallback that sends every route to index.html.
  • Config is missing in the browser: a static build can't read dashboard variables. Write them into the page at startup and read window.__APP_CONFIG__ (see the full-stack recipe).

Next

Suggested guides
↑↓ to move ↵ to open Esc to close From the Dockhold docs