Deploy a static site
Plain HTML, CSS, and JS on a live HTTPS URL.
If your project is just files (an index.html with some CSS and
JavaScript) you only need a small server to hand those files out on the
port Dockhold assigns. This recipe is the whole path.
In a hurry? Start from the ready-made static-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 one-line static server
Add the serve package and a start script. If you
don't have a package.json yet, create one with
npm init -y first.
npm install --save-dev serve
Then in package.json:
{
"scripts": {
"start": "serve -l $PORT ."
}
}
serve -l $PORT . serves the current folder (where your
index.html lives) on the assigned port, on all interfaces. If
your files live in a subfolder, point at it: serve -l $PORT public.
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. Connect the repo and deploy
- Push the project to a GitHub repository.
- Open the dashboard, connect GitHub, and pick the repo.
-
Dockhold recognises a static site on every account. An
index.htmlat the root is enough; apackage.jsonwhose only job is a local preview withserveis fine too. It serves the folder's web files on the assigned port: pages, styles, scripts, JSON, text, Markdown, CSV, calendar files, 3D models, images, fonts, audio and video, plus everything in.well-known/at the root. Other dotfiles, config, keys and server code (.env,.yml,.pem,.sql,.phpand the like) are never served, and a folder without anindex.htmlshows no file list. Nothing else to do. -
A PHP file at the root makes it a PHP project, not a static site. PHP
builds automatically on accounts with compute added, and from your own
Dockerfileon any account. -
Prefer to control the build yourself? Add the
Dockerfilebelow at the root of your repo and Dockhold uses it as-is, on any account.
# Dockerfile
# Web file types and .well-known only; dotfiles, config, keys and server code stay out
FROM node:22-alpine AS files
WORKDIR /src
COPY . .
RUN mkdir /site && find . \( -name '.*' ! -name . ! -path ./.well-known \) -prune \
-o -path ./package.json -prune -o -path ./package-lock.json -prune \
-o -type f \( -path './.well-known/*' \
-o -iname '*.html' -o -iname '*.htm' -o -iname '*.xhtml' -o -iname '*.css' -o -iname '*.js' \
-o -iname '*.mjs' -o -iname '*.map' -o -iname '*.json' -o -iname '*.webmanifest' \
-o -iname '*.txt' -o -iname '*.md' -o -iname '*.csv' -o -iname '*.ics' -o -iname '*.xml' \
-o -iname '*.wasm' -o -iname '*.pdf' -o -iname '*.glb' -o -iname '*.gltf' \
-o -iname '*.png' -o -iname '*.apng' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.gif' \
-o -iname '*.webp' -o -iname '*.avif' -o -iname '*.jxl' -o -iname '*.svg' -o -iname '*.ico' \
-o -iname '*.bmp' -o -iname '*.woff' -o -iname '*.woff2' -o -iname '*.ttf' -o -iname '*.otf' \
-o -iname '*.eot' -o -iname '*.mp4' -o -iname '*.webm' -o -iname '*.ogg' -o -iname '*.ogv' \
-o -iname '*.opus' -o -iname '*.mp3' -o -iname '*.wav' -o -iname '*.m4a' -o -iname '*.flac' \
-o -iname '*.vtt' \
\) -exec cp --parents -t /site {} +
# Serve them on the assigned port, with no folder listings
FROM node:22-alpine
RUN npm install -g serve@14 \
&& echo '{"directoryListing": false}' > /etc/serve.json
WORKDIR /site
COPY --from=files /site/ ./
CMD serve -c /etc/serve.json -l $PORT .
Your site goes live at https://<your-app>.dockhold.app
with HTTPS handled for you. Every later push to your main branch redeploys.
Troubleshooting
-
404 at the root: the folder you pass to
servemust contain anindex.html. Useserve -l $PORT publicif it's in a subfolder. -
Build fails with "no start script": confirm the
startscript is inpackage.jsonand spelled exactly as above. -
One file 404s while the rest load: only web file types
are served (see step 2). Give the file one of those types, or ship your
own
Dockerfile. -
A page loads but assets 404: use relative asset paths
(
./style.css), not absolute ones tied to your local folder.
Next
- Upgrade to a Vite + React app
- Agent rules: make your AI tool generate Dockhold-ready apps.
- All recipes