Skip to main content
This page is written for a coding agent. It describes what Modelence Cloud runs, the modelence.config.json file that tells it how, and the steps to set up an existing Node.js project. If you are a person, hand it to your agent:

What Modelence Cloud runs

  • A Docker image built from the repository on an official Node.js image, 18 or newer (default node-22-slim, set with image). The build commands run in order in the resource’s root; only npm is preinstalled.
  • One web process, started with the resource’s start commands. The last one must keep running and listen on the port in the PORT environment variable — a hardcoded port never receives traffic.
  • Optional static directories (the resource’s static) served in front of that process by the platform. Requests that match no file fall back to the directory’s index.html, so single-page apps route on the client.
  • Optional managed PostgreSQL databases and Redis caches, declared in resources next to the service. A deploy creates each one in the environment’s region, and the app gets its connection details through the variables env ties to it. They need a paid Modelence Cloud plan.
  • Optional telemetry, a resources entry of type "telemetry". It gives the environment a private OpenTelemetry endpoint for the app’s traces and metrics, kept for 30 days and shown on the environment’s Monitoring page. It needs a Modelence Cloud plan with monitoring (Pro and up). Logs need no setup: the app’s output is collected and shown on the Logs page.
  • Environment variables injected at start:
    • MONGODB_URI and MONGO_URL — a MongoDB Atlas database provisioned for this environment (both hold the same connection string).
    • SITE_URL and ROOT_URL — the environment’s public URL (both hold the same value).
    • PORT — the port the web process must listen on.
    • Any variables the user defines in the dashboard under Environment variables.
    • The variables env ties to a managed resource, such as DATABASE_URL from a PostgreSQL database’s url, or OTEL_EXPORTER_OTLP_ENDPOINT from telemetry’s endpoint.
    Names starting with MODELENCE_, and PORT, are reserved and cannot be defined by the user.

modelence.config.json reference

modelence.config.json sits at the project root and is the whole contract between the project and Modelence Cloud. Comments (//, /* */) and trailing commas are allowed. Apart from a resource’s type, every key is optional; a missing key takes the default. The file has two top-level sections: resources (what the app is made of) and env (the variables it expects). A service entry of resources takes: A managed resource entry of resources takes: A deploy creates every managed resource the file declares that the environment doesn’t have yet, and the new version starts only once they are ready. Resources are matched by name, so the same file works in every environment:
  • A resource that already exists is kept as it is, with its data. plan is read only when the resource is created, so changing it later has no effect.
  • A resource removed from the file is not deleted. Delete it in the dashboard under Resources when it is no longer needed; a database or cache is billed until then.
  • Renaming a resource creates a new, empty one. The old one stays until it is deleted.
An environment can have up to 10 managed resources, telemetry included, and at most one of them can be telemetry. Each entry of env declares one variable: A value set in the dashboard overrides a value written here, so a literal in the file is the committed default rather than a fixed constant. A variable with "type": "resource" takes its value from one output of a managed resource in the same file, and takes only these keys: The url output is a full connection string: postgresql://...?sslmode=require for PostgreSQL and rediss://... (TLS) for Redis. The other outputs are its parts, for clients that read them separately, such as PGHOST and PGPASSWORD. These variables reach the running app only, not the build commands. They can’t be set or changed in the dashboard, where they are listed with the resource they come from. Telemetry’s outputs are the values of the standard OpenTelemetry variables: endpoint for OTEL_EXPORTER_OTLP_ENDPOINT and headers for OTEL_EXPORTER_OTLP_HEADERS, which holds the environment’s credentials. The endpoint takes OTLP over HTTP and is private: only the running app reaches it, not the build or a local machine.
Apps built on the Modelence framework need no file: npx modelence@latest deploy recognizes the modelence dependency and keeps building them locally, as it always has. To build one on Modelence Cloud instead, describe it like any other Node.js app: "build": { "commands": ["npm ci", "npm run build"] } and "start": { "commands": ["npm start"] }.
Missing, empty and none: a missing build runs npm install; "build": { "commands": [] } means there is no build step. A missing start means there is no process. An empty command string "" is invalid and is rejected.

Examples

Express API only — no build step, the server serves everything:
modelence.config.json
Vite static site — built once, served by the platform, no process:
modelence.config.json
Express API + Vite frontend — the frontend in client/ is built into client/dist and served at /; requests that match no file reach the API. VITE_API_URL is scoped to the build as well because Vite inlines it while building:
modelence.config.json
Express API with PostgreSQL and Redis - the deploy creates the database and the cache, and the app reads them from DATABASE_URL and REDIS_URL:
modelence.config.json
Express API with telemetry - the OpenTelemetry SDK reads the two variables, so requests and database calls reach the environment without code changes. Logs are already collected from the app’s output, so OTEL_LOGS_EXPORTER turns off the SDK’s own log export. The project depends on @opentelemetry/auto-instrumentations-node, which --require loads before the server:
modelence.config.json

Setting up a project

Follow these steps in order. Do not skip the verification step.
  1. Inspect package.json. Read scripts, engines.node, type and the dependencies. Note which framework builds the frontend (Vite, Next.js, Create React App, Angular, Astro…) and what runs the server (Express, Fastify, Koa, Hono, NestJS, a framework’s own server…).
  2. Pick the install command from the lockfile.
    • package-lock.json in sync with package.json → npm ci. If it is stale or missing, use npm install.
    • pnpm-lock.yaml → corepack enable && pnpm install --frozen-lockfile.
    • yarn.lock → corepack enable && yarn install --frozen-lockfile (Yarn 1) or corepack enable && yarn install --immutable (Yarn 2+).
    Only npm is preinstalled in the build image; corepack enable makes pnpm and Yarn available at the version packageManager in package.json names.
  3. Find the build output. Vite writes dist/, Create React App and Angular write build/ or dist/<project>/browser, Astro writes dist/, Next.js needs next build and its own server (next start), not a static mount, unless output: 'export' is set. Check the framework config for a changed outDir.
  4. Check the start script. vite, vite preview, react-scripts start, next dev, nodemon, ts-node-dev and tsx watch are development servers. Do not use them. Replace them with a production start (node dist/server.js after a TypeScript build, next start, node server.js) or, for a pure frontend, a static mount and no start. Do not add database migrations to start.commands: every container runs them on every start, several at once during a rollout, and a slow one is killed by the health check partway through. Leave migrations out of the file and tell the user to run them against the environment’s database before deploying. A step may go first in start.commands only if it is fast and safe to run in parallel, such as prisma generate.
  5. Make the server read PORT. The listen call must use process.env.PORT, for example app.listen(Number(process.env.PORT) || 3000). Bind to all interfaces (the default), not to 127.0.0.1 only. If the frontend and the API are served together, the server must be the one process — the static mount handles the frontend files, the server handles everything else.
  6. Use the provided databases. If the project needs MongoDB, read the connection string from process.env.MONGODB_URI (or MONGO_URL) instead of a hardcoded one. If it needs PostgreSQL or Redis that it doesn’t already have hosted somewhere, declare a managed resource for it in step 7 and tie the variable the code reads (DATABASE_URL, REDIS_URL) to its url output. A database hosted elsewhere (Supabase, Neon, PlanetScale and so on) works as it is: keep the connection code and read its URL and keys from environment variables. Other databases, such as MySQL, are not provisioned. See Compatibility.
  7. Write modelence.config.json at the repository root with the values found above, as a single service entry in resources ("type": "service"). Pick image from engines.node (default node-22-slim; use slim unless the project already builds on Alpine). Set root when the app lives in a subdirectory. Add any managed resources from step 6 next to the service, with their variables in env. Add a telemetry resource only when the user asks for traces or metrics, or the project already uses OpenTelemetry: a deploy fails on a plan without monitoring. Tie OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS to its outputs instead of a hardcoded collector. Add "$schema": "https://cloud.modelence.com/schema/modelence.config.json?version=1".
  8. Verify locally. Run the build commands, then the start commands with PORT=3000 set, in order — exactly as written in the file — and confirm the app answers on http://localhost:3000 (for a static site, confirm the dir exists after the build and holds index.html). Fix the file or the code until this works; a deploy runs the same commands.
  9. Commit modelence.config.json together with any code changes (the PORT change, the production start script).
  10. Run npx modelence@latest deploy in the project directory yourself, with a timeout of at least 15 minutes. It opens the browser, where the user signs in, picks the organization, application and environment (or creates a new application or environment there) and enters the values of any required variables the environment does not have yet. Tell the user to confirm there, then wait for the command to finish: it uploads the project and follows the build until the app is live. If you know the target, pass --app <app> --env <env> to preselect it; the user still confirms it in the browser.

Runtime values and secrets

When the app needs a value that must not be committed — an API key, an OAuth secret, a third-party URL — declare it by name in the file’s env section, without a value and with "type": "secret" for anything sensitive. The deploy asks the user for it in the browser; it can be changed later in the Modelence Cloud dashboard under Environment variables. A value only the build needs (a VITE_* or NEXT_PUBLIC_* variable that is inlined into the frontend) gets "scopes": ["build", "runtime"]; never give a build scope to anything the browser must not see. Mark a variable the app runs without "required": false. Read the values from process.env as usual. Non-secret settings that belong with the code (a base path, a public API path) go in the file’s env section instead, with a value, so they are versioned with the repository. Give anything a bundler inlines "scopes": ["build", "runtime"] so it reaches the build as well as the running app.

When the project is not compatible

Report the project as not compatible with Modelence Cloud, and say why, when:
  • It is not a Node.js application — Java, Python, Go, PHP, Ruby, .NET and so on, even with a Node.js frontend in front. The image runs Node.js only.
  • It requires a second long-running process: a separate worker or queue consumer, a scheduler process, or a database other than the provided ones (MySQL, Elasticsearch…) that would have to run alongside the app. One web process is run, and MongoDB, PostgreSQL and Redis are the only databases provisioned.
  • It needs Docker Compose or a custom Dockerfile to run.
A database hosted elsewhere is not a reason to report the project as not compatible. An app that connects over the network to Supabase, Neon, PlanetScale, a managed Redis or any other hosted database deploys as usual: keep its connection code, and tell the user to add the URL and keys in the dashboard under Environment variables, marked as secret. A key the frontend bundler inlines, such as VITE_SUPABASE_URL, also needs to be available at build time, as described in Runtime values and secrets. Then suggest what the user can do instead: replace the database with one that is provided (MongoDB through MONGODB_URI, or a managed PostgreSQL or Redis resource), or keep the incompatible parts on another host and deploy only the Node.js web app. Do not move a worker, queue consumer or scheduled job into the web process. The web process can run as several replicas, and each one would run every job — emails sent twice, charges made twice. If the user wants to do it anyway, tell them that each job needs a lock held in the database, so that only one replica runs it at a time.

Meteor

Early support — the commands below work but Meteor is not yet a first-class runtime.
Meteor bundles its own server; build it into a directory outside the source tree and start the bundle with Node.js. ROOT_URL and MONGO_URL, which Meteor reads at start, are already injected. Match image to the Meteor release: Meteor 3 runs on Node 22.
modelence.config.json