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

  1. Push the project to a GitHub repository.
  2. Open the dashboard, connect GitHub, and pick the repo.
  3. Dockhold recognises a static site on every account. An index.html at the root is enough; a package.json whose only job is a local preview with serve is 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, .php and the like) are never served, and a folder without an index.html shows no file list. Nothing else to do.
  4. 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 Dockerfile on any account.
  5. Prefer to control the build yourself? Add the Dockerfile below 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 serve must contain an index.html. Use serve -l $PORT public if it's in a subfolder.
  • Build fails with "no start script": confirm the start script is in package.json and 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

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