Full-Stack Apps
Full-stack apps
A full-stack app is one app that carries both a static front end — served from the Ngris edge like a Static App — and a backend server that Ngris runs for you in-cluster. There is nothing to wire up between them: Ngris routes each incoming request by its path. Requests under your configured backend paths reverse-proxy to your backend container; every other request serves a file from your built front end.
Create the app with type fullstack — that is the difference from a managed (backend-only) app and a static (front-end-only) app. Both the front end and the backend build from a single source (an upload or a connected git repo), exactly like the other app types.
/api hit your server. No CORS, no second domain, no separate deploy. If you only ship a website, use a Static App; if you only ship a server (no static bundle), use a backend app.
Full-stack apps require the application_hosting entitlement (the same one static hosting uses) plus managed-backend availability on your plan — the backend runs a real, long-running process on the edge, so it needs a plan that offers managed backends. There is no per-request serverless / FaaS runtime.
Path routing — backend paths
The whole design of a full-stack app is one rule: Ngris routes by request path. Set Backend paths (backend_paths) to the URL prefixes your server owns — for example /api, /auth, /graphql. The default is ["/api"].
- A request whose path matches a backend prefix — the exact prefix or a
/prefix/…segment under it — reverse-proxies to your backend container. So/apiand/api/usersboth hit the backend when/apiis a backend path, but/apidocsdoes not (it is not a segment boundary). - Every other request serves a static file from your built front end — identical to a Static App:
publish_dir,index_document, SPA fallback and a custom 404 all apply. - Paths are normalized before the decision, so a crafted
/api/%2e%2e/admincollapses to/adminand is treated as a front-end path — it can never sneak past a path rule into the backend.
You can list up to 20 backend prefixes. A special case: a prefix of / routes everything to the backend — that turns the app into a backend-only app (see backend apps) even though it is typed fullstack.
index.html. If your front end is a single-page app with client-side routing (/dashboard, /settings/billing…), those deep links are front-end paths — there is no file at /dashboard on disk. Turn on SPA fallback (spa_fallback) so any unmatched non-backend path serves your index.html and the client router takes over. Without it those routes 404.
A related trap: if your app ships no static bundle at all (a pure API), then / has no file to serve and returns a 404 — unless a backend path covers it. Either add a front end, or make it a backend-only app (backend paths = /) so the server answers / too.
How it builds & runs
A full-stack app builds through the same pipeline as a backend app, and additionally publishes the static assets to the edge. From your one source, Ngris:
- Builds the backend from your
Dockerfile(or an auto-detected Go / Node / Python stack) in a sandboxed job, then signs the image, scans it for vulnerabilities, and pins it to its digest. The running backend is isolated in its own per-app pod and reached only through Ngris's routed path. - Publishes the static front end to the edge store, exactly like a Static App — so front-end requests are served from the edge at static-hosting speed, never through the backend.
The build-context rules are the backend app's rules — rather than repeat them, see Backend apps for the details on the zip root, keeping the context self-contained, vendoring local modules, the 100 MB context cap, build-time vs runtime environment variables, the listen port, and the read-only root filesystem with a writable /tmp.
Backend runtime settings — port, health-check path, CPU/memory, replicas, and the backend_paths above — live on the app's Runtime panel (or PUT /v1/applications/{uuid}/runtime).
Preview URLs
Every deploy can get its own public preview URL of the form <deploy-uuid>.<your-plan-domain>, so you can test the exact build before you point a real domain at it — or share a work-in-progress. For a full-stack app the preview is fully live: backend paths hit the running backend and everything else serves that deploy's static front end, just like production.
Preview URL returns 404? Enable the preview.
If a preview URL returns 404, the almost-certain cause is that the preview is not enabled for that deploy (preview_enabled is off — the default). Turn it on with the deploy's Enable preview toggle on the Deploys page, or via the API:
curl -X POST "https://api.ngris.com/v1/applications/{uuid}/deploys/{deploy_uuid}/preview" \
-H "X-API-KEY: <your_api_key>"
The response returns preview_enabled: true and the live preview_url; disable it again with the same route as DELETE. Two more things to check when a preview 404s:
- The host must use the deploy UUID, not the app UUID — each deploy has its own preview host.
- The deploy must be
ready(orsuperseded— a former-live version). A still-building or failed deploy has no preview.
Add a database
Because a full-stack app has a backend, it can attach a fully-managed, MySQL-compatible database — Ngris provisions an isolated schema and a least-privilege user and injects the connection into your running backend as environment variables (DATABASE_URL and friends). There is nothing to install or operate. See App databases for the full walkthrough, the injected variables, and per-app isolation.
Deploy a full-stack app
The end-to-end path, in the dashboard:
- Create the app. Applications → New, choose type Full-stack.
- Configure backend paths. On the Runtime panel, set Backend paths to the prefixes your server owns (default
/api). Set the listen Port and a health-check path while you are here. If your front end is a SPA, turn on SPA fallback in the serving settings. - Set environment variables. Add config, tokens, and connection strings under Environment variables; mark secrets as Secret (write-only, encrypted). Build-time front-end vars (e.g.
VITE_API_URL) and runtime backend vars both live here. - (Optional) Add a database. From the app's Database tab → Add database; Ngris injects
DATABASE_URLand redeploys. - Upload or connect source. Upload a
.zipor connect a GitHub/GitLab repo. Ngris builds the backend image and publishes the static front end. - Watch the build. Open the deploy's Log — it tails live — and wait for the deploy to reach
ready. - Enable a preview and test. Toggle Enable preview on that deploy and open its
<deploy-uuid>.<plan-domain>URL — you are testing the exact build, backend and front end together, before it goes live. - Promote / point a domain. Activate the deploy to make it live, and add a custom domain whenever you are ready.
By API, the shape is the same: create the app (type: "fullstack"), set the runtime with your backend_paths via PUT /v1/applications/{uuid}/runtime, add env vars, push a deploy, poll it to ready, then POST /v1/applications/{uuid}/deploys/{deploy_uuid}/preview to test the exact deploy before activating it.