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/. The most reliable path is a short Dockerfile that builds those files and then serves them on the port Dockhold assigns. It pins exactly how the app builds and starts, so it deploys the same way every time. This recipe is the whole path.
In a hurry? Start from the ready-madevite-react-starter template: click Use this template on GitHub, thendeploy 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 serveThen 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 thePORT 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 $PORTA Dockerfile at the repo root takes priority and tells Dockhold exactly how to build and run the app. Without one, the build system may try to serve the project with its own static server, which won't bind the assigned port, so include the Dockerfile.
3. Connect the repo and deploy
- Push the project (with the
Dockerfile) to a GitHub repository. - Open the dashboard, connect GitHub, and pick the repo.
- Dockhold builds from your Dockerfile and runs it.
Your app goes live at https://<your-app>.dockhold.appwith 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 fromwindow.__APP_CONFIG__. You set and change those values in the dashboard with no rebuild. Thefull-stack recipe and thefullstack-web templateshow 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 ofdist/. Make sure theDockerfileis 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
-sflag (serve -s dist). It enables the SPA fallback that sends every route toindex.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
- Add a backend API
- Agent rules: make your AI tool generate Dockhold-ready apps.
- All recipes