Guidedeploymill://guides/stack/static
Working on a static site (deploymill)
Reference for agents modifying a deploymill-managed static site scaffolded from the static template. The scaffold is the simplest of the three stacks: nginx serves HTML files baked into the image — no app server, no env at runtime, no persistence.
What deploymill scaffolds
index.html— single-file landing page using the{{PROJECT_NAME}}placeholder.Dockerfile—FROM nginx:alpine, copies the whole repo root (COPY . /usr/share/nginx/html/) into nginx's default doc root, exposes port 80..dockerignore— keeps build/meta files (Dockerfile,.deploymill/,.git/,node_modules,dist) out of the image.
The platform contract
- Port 80. nginx defaults to 80. The platform routes the published port — don't change it without also updating
EXPOSEand the nginx config. - Files baked into the image. Static-stack has no runtime configuration. Anything you want served must be
COPY'd in at build time. - Assets live at the repo ROOT — not in
public/orsrc/. The scaffold's Dockerfile doesCOPY . /usr/share/nginx/html/, so a file at the repo root is served at/<file>. If you putapp.jsinpublic/, it's served at/public/app.js(so<script src="app.js">404s), and if the Dockerfile were a narrower pattern it'd be skipped entirely. Do not create apublic/directory — putindex.html, CSS, JS, and images directly at the repo root.
Common modifications
- Add CSS/JS/images: drop them at the repo root and reference them with root-relative or relative paths (
<link href="style.css">,<script src="app.js">). The scaffold already copies the whole root, so no Dockerfile edit is needed — just commit + push. Don't put them inpublic/(see the platform contract above); that's the most common reason an asset 404s. - Subdirectory routing (
/about/index.html, etc.): nginx's default config already serves directories. Add the subdir to the repo root (e.g.about/index.html) and push — it's served at/about/. This is fine; the pitfall is a single top-levelpublic//src/wrapper that shifts everything under one path. - Custom nginx config (SPA fallback, redirects, headers): drop an
nginx.confin the repo, addCOPY nginx.conf /etc/nginx/conf.d/default.confto the Dockerfile. The defaultnginx:alpineconfig lives at/etc/nginx/conf.d/default.conf. - SPA history routing: in a custom nginx config use
try_files $uri $uri/ /index.html;so client-side routes don't 404. - Switch to a build step (Vite, Next static export): add a build stage to the Dockerfile that produces a
dist/, thenFROM nginx:alpine+COPY --from=build /app/dist /usr/share/nginx/html/.
What this stack does NOT support
- No env vars at runtime. Static sites can't read env. If you need config that changes per deploy, you need a build step that templates HTML/JS before nginx starts, or switch to the
node/pythonstack. - No database.
database: { provider: "neon" }does nothing useful here — there's no app process to readDATABASE_URL. - No mounts (in practice). Mounts work mechanically, but nothing is writing data. Pages served are whatever's baked into the image.
- No
/healthzendpoint. It would 404. Use the root path (/) for liveness checks.
What NOT to do
- Don't expect runtime mutation. The image is your deploy unit. To "change a page", commit + push and let the platform rebuild.
- Don't put assets in a
public/(orsrc/) subdirectory. The scaffold serves the repo root, so apublic/wrapper either makes every path wrong (/public/...) or, with a narrower COPY pattern, drops the files entirely. Keep everything at the root. - Don't remove the scaffold's
.dockerignore. Because the Dockerfile doesCOPY . /usr/share/nginx/html/, dropping the.dockerignorewould publish your.deploymill/project.json,.git/, theDockerfile, and any secrets sitting in the repo. If you add a build step, extend the ignore list rather than deleting it.
Debugging
404on a known file → first check whether the file is in apublic//src/subdirectory. If it is, it's being served under that path (/public/app.js) instead of where your HTML references it — move it to the repo root. If it's already at the root, confirm the Dockerfile still doesCOPY . /usr/share/nginx/html/and that.dockerignoreisn't excluding it.- Blank page → the file is being served but is empty; verify with
get_filethat the committed content is what you expect. - nginx errors are in the container's stdout, available via the platform's logs view.