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-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:20-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:20-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, 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.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 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