Skip to main content
Sevalla runs JavaScript and TypeScript projects in two ways: as an application, a server built from your Git repository that runs on Node.js, Bun or Deno, or as a static site, the HTML, CSS and JavaScript files a build produces, served from Cloudflare’s edge. The guides in this section take one framework each from an empty folder to a running deployment.

Guides

Application or static site

Choose by whether the project needs a server when a request arrives.

How Sevalla builds an application

New applications built from Git use Ingot, Sevalla’s builder. Ingot reads package.json and the lock file, writes a Dockerfile for the project, and Sevalla builds it: The image runs with NODE_ENV=production, as user 1000:1000, from /app. Environment variables marked Available during buildtime reach the install and build steps; see Environment variables during the build. Before the build starts, the build log shows Ingot’s build plan: each decision and the file it came from. For the Express guide, it reads:
Build log
When a project builds to static files only, such as Astro without an adapter, a Vite single-page app or a Next.js static export, Ingot builds an image that serves the files with Caddy. A static site serves the same files from Cloudflare’s edge, free up to the monthly allowance, so prefer it unless the files need to sit next to an application. To change what Ingot detects, add an ingot.json file or set INGOT_* environment variables; see Configure the build. To build the image yourself, see Build with a Dockerfile.

Listen on the PORT environment variable

Sevalla sets the PORT environment variable in the web process to the process’s port, 8080 by default, and sends every request for the application’s domains to that port. Sevalla’s value replaces a PORT variable that you set yourself. Your server must:
  • Listen on the port in PORT.
  • Listen on all network interfaces (0.0.0.0), not on localhost or 127.0.0.1.
Framework servers do both on their own, and Ingot sets the variable some of them need to listen on all interfaces, such as HOST=0.0.0.0 for Nitro, SvelteKit’s adapter-node, Astro’s @astrojs/node and react-router-serve. A server you write yourself must pass the port in code: To use another port, update it on the application’s Networking tab: under Expose HTTP traffic, click Update port. Sevalla then sets PORT to the new value. See Networking.

Set the start command

The web process runs the start command of the image the build produced. With Ingot, that’s the start script in package.json, or the server of the framework Ingot detects; the build plan’s Start row shows which, and where it came from. To run something else without changing the repository, open the application’s Processes tab, open the web process’s menu, click Update process and fill in Custom start command. Ingot builds the next image with that command, so the change applies at the next deployment. See Set the start command for every way to set it.

Pin the Node.js version

Ingot builds on Node.js 24, its default line, unless the project asks for another version. Pin the version in package.json, so the build doesn’t change when a new Ingot release moves its default line:
Ingot takes the version from the first of these that’s set: the INGOT_NODE_VERSION environment variable, versions.node in ingot.json, .node-version, .nvmrc, .tool-versions, mise’s configuration, then devEngines.runtime or engines.node in package.json. The build plan’s Node.js row shows the version and where it came from.
  • A line, such as 24, 24.x or ^24, builds on that line’s newest release, and every new build picks up its security fixes.
  • A wider range, such as >=20, builds on Ingot’s default line when the range allows it, otherwise on the newest maintained line it allows.
  • An exact release, such as 24.11.0, stays on that release and gets no security fixes, and the build log warns about it.
See Set the language version for the lines each Ingot release knows. Static sites don’t read engines or .nvmrc. Sevalla installs the Node version chosen in the static site’s build settings, Latest LTS by default. See Node versions.

Build a static site

A static site build installs the dependencies with the package manager your lockfile names, runs the Build command in the Root directory, and publishes the Publish directory, which is relative to the root directory. See How a build runs. When you create a static site from a private repository, Sevalla reads package.json and fills in the build command and publish directory for the frameworks it recognizes, including Astro (dist), Next.js (out) and Nuxt (.output/public, built with npm run generate). See Framework detection for the full list.

Build with a Dockerfile

Every guide that deploys an application also gives a Dockerfile for it, in its Deploy with a Dockerfile section, for example Express or Next.js. Switch the application to it on the Settings tab with Update build strategy. The guides’ Dockerfiles do what Ingot does for you:
  • Base images. node:24-trixie-slim, the Node.js line that engines.node pins, with node:24-trixie as a separate build stage when the build needs compilers. Bun and Deno guides use oven/bun and denoland/deno. A line tag, where the image publishes one, picks up security fixes at every build.
  • Install from the lock file first. Copy package.json and the lock file, run npm ci, then copy the source, so a code change reuses the cached dependency layer.
  • Run as user 1000. The image’s node user, with the application’s files owned by root, so the server can’t change its own code.
  • Shut down cleanly. Run node with an exec-form CMD, so it gets SIGTERM when an instance stops. Node.js ignores SIGTERM when it runs as the container’s main process and has no handler for it, so servers you write yourself, such as Express, add one that closes the server. Ingot’s images run tini for the same reason.
  • Keep secrets out. .dockerignore excludes .env files. Build-time environment variables reach the build as build arguments; declare the ones the build needs, such as VITE_ or NEXT_PUBLIC_ variables, with ARG in the build stage only. See Use build arguments.
A background worker or job runs its Custom start command with /bin/sh -l -c. Start a long-running command with exec, such as exec node worker.js, so the worker itself gets SIGTERM. See Run background jobs with BullMQ.

Keep the application stateless

Each deployment starts new instances from a new image, so files written to an instance’s disk are gone after the next deployment, and instances don’t share memory when you run more than one. Keep state outside the process: When an application needs files on disk, attach persistent storage.