A folder of HTML, CSS and JavaScript, a Vite project, or a static site generator deploys with no server, no Dockerfile and no configuration. Nimbus Land builds the site when there is something to build, publishes the files, and serves them from its shared static gateway. A static site runs no instances, and each new release goes live in seconds once it is published.
What is detected as a static site#
A static site is detected only when nothing else claims the source. Highest first:
- A root
Dockerfilealways makes a container app. See Dockerfile. - Node server frameworks (Next.js, Nuxt, Express, Nest and similar) are Node apps, even when they bundle a client with Vite. See Node.js.
- A root
vite.config.js,.ts,.mjsor.mts: Static site (Vite). Dependencies are installed, thebuildscript runs, anddist/is published. - A
package.jsonthat builds a site rather than runs a server: Static site (npm build). It has no server dependency and no server-stylestartscript (either none, or a development watcher such asvite,nodemonorastro dev), and one of:- a root
index.htmland no runtimedependencies; - a
buildscript that runs a static generator (astro build,eleventy,gatsby build,vitepress build,vuepress build,hugo,jekyll build, orvite build); - a committed
dist/,public/orbuild/folder holdingindex.html.
- a root
- A root
index.htmlwith nopackage.json: Static site. Nothing is built.
To see what Nimbus Land would choose, run nimbus detect in your project:
$ nimbus detect
detected: Static site (Vite)
Choosing static or container yourself#
Detection is a guess, so every deploy can override it:
- Deploy wizard. Deploy as offers Auto-detect, Static site and Container. It is shown expanded when detection is less sure (the npm build case above). Choosing Static site adds an optional Publish directory field.
- CLI.
nimbus deploy --static, optionally with--publish-dir <dir>.--builder containerforces a container build even for a Vite project. In.nimbus.yml, usebuilder: staticandpublish_dir:underbuild:. - Build API.
POST /api/v1/buildsaccepts"builder": "static"or"container", and an optionalstatic_publish_dir.
Static site outranks every detection, a root Dockerfile included. If the root package.json has a build script, dependencies are installed and the script runs; otherwise the files are published as they are.
The publish directory must be a relative path with no .. and no leading /, at most 128 characters, and must contain an index.html. Leave it blank to use the default order described below.
Your choice is saved with the build, so rebuilds and pushes to GitHub keep it.
Container is useful when a repository has an incidental index.html (a demo page next to a real server).
How a site is built#
Sites with a build script are built with Node 22. Dependencies are installed with the package manager your lockfile names:
| Committed lockfile | Install command |
|---|---|
pnpm-lock.yaml | pnpm install --frozen-lockfile |
yarn.lock (Yarn 2 and later) | yarn install --immutable |
yarn.lock (Yarn 1) | yarn install --frozen-lockfile |
package-lock.json or npm-shrinkwrap.json | npm ci |
| none | npm install |
The build script then runs with the same package manager. A packageManager field in package.json pins the Yarn or pnpm version. engines.node is not read, and node_modules is not cached between builds, so commit a lockfile to make installs reproducible.
What gets published#
One directory is published, the first that applies:
- the publish directory you set;
- the output directory of a known generator in your
buildscript; dist/for Vite; the first ofdist/,build/,out/,public/for npm builds;public/for plain sites;- the repository root.
build script runs | Published from |
|---|---|
astro build, vite build | dist/ |
eleventy, jekyll build | _site/ |
gatsby build, hugo | public/ |
vitepress build | .vitepress/dist/ |
vuepress build | .vuepress/dist/ |
A generator configured to write somewhere else needs a publish directory. For Vite, a custom build.outDir is not read.
Everything in the published directory is public, except:
- Files and folders whose names start with
.(.env,.npmrc,.git/) are never published, at any depth, and the build log lists what it left out.node_modules/is never published either. .well-known/at the top of the site is the exception, so/.well-known/security.txtkeeps working.
Publishing the repository root. If nothing else applies, the whole repository is published, and every file in it (README.md, package.json, src/) is public. When the root looks like source code, the build log warns:
Nimbus: publishing the whole repository (no public/, build output or publish directory) — source files are public. Add public/, a build script, or set a publish directory to publish only the site.
Move the site into public/, add a build script that writes to dist/, build/ or out/, or set a publish directory.
The entry page. The published directory must have an index.html (or index.htm) at its top level. Without one, the build fails before anything goes live:
Nimbus: public/ has no index.html, so the site would answer 404. Check the build script's output directory or set the publish directory.
Caching#
HTML is revalidated on every visit, so a new release is visible as soon as it is live. A Vite site's hashed files under assets/ (such as assets/index-a1b2c3.js) are cached for a year, since their names change with their content. Other files are cached for an hour, so give a changed file a new name if visitors must see it at once.
Single-page apps and 404s#
| Request | Static site | Static site (npm build) | Static site (Vite) |
|---|---|---|---|
/about (no such file) | 404 | 404 | index.html |
/assets/app-a1b2c3.js (missing) | 404 | 404 | 404 |
/docs/ | docs/index.html | docs/index.html | docs/index.html |
A Vite project is treated as a single-page app: links without a file extension serve index.html so your client-side router can take over. Missing files that look like assets still return 404. Other static sites return real 404s, so use directory-style pages (about/index.html) for clean URLs.
Releases and rollback#
Every deploy creates a release, listed on the app's Deployments tab. Releases are published to their own location and never changed, so switching to a new release is all-or-nothing: visitors never see a half-updated site. Rolling back switches to an earlier release without rebuilding, in seconds.
The five most recent releases of each environment can be rolled back to. Older releases stay in the history but their files are removed.
The Overview reports a site as healthy while its current release is published. If its files are missing, the Overview says the site is no longer published and offers a redeploy.
Domains and analytics#
The nimbusapps.ca address, custom domains and deploying on every push to GitHub work exactly as for other apps.
When Measure visitors in the browser is on in the app's Analytics tab, the measurement script is added to every HTML page as it is served, without republishing. See Traffic and analytics.
Build-time variables are public#
A static site has no server. Variables you set on the app are passed to the build, and Vite inlines every VITE_* variable into the JavaScript it produces, where every visitor can read it. Never put an API key, database URL or other secret in a static site's variables. Anything secret belongs behind an app with a server (see Secret environment variables).
Changing a variable needs a new build: choose Rebuild or push a commit.
Limits#
- No
_redirectsor_headersfiles: custom redirects and headers are not supported. Single-page fallback is the only routing rule, and it applies to Vite sites. - Static sites have no instances, scaling, shell, one-off commands or volumes.
- An app deployed as a container stays a container on rebuild. To serve it as a static site, deploy it under a new app name.
Troubleshooting#
A variable change did nothing. Variables are built into the site. Rebuild, or push a commit.
/about returns 404 but /about.html works. Only Vite sites get single-page fallback. Use about/index.html, or build the site with Vite if it uses client-side routing.
Vite sites are published from dist/ - check that the build writes there. Your Vite config writes elsewhere, or the build script doesn't run vite build. Keep the default dist/ or set a publish directory.
… has no index.html, so the site would answer 404. The build published the wrong folder, usually because your build script writes somewhere the default order doesn't look. Set the publish directory to where the script writes.
Detected as a Node app, but it's a static site. If the app keeps restarting because its start script is a development watcher, the deploy wizard warns before you deploy. Choose Deploy as → Static site, with a publish directory if your output isn't in dist/, build/, out/, public/ or the root.
Also available as Markdown.