Skip to main content
Ingot is Sevalla’s own builder: it reads your repository, detects the language, framework, language version and package manager, writes a production Dockerfile for the application, and Sevalla builds that Dockerfile with BuildKit. It’s the default build strategy for new applications from Git. It needs no configuration for most applications, and you can override what it detects with an ingot.json file or INGOT_* environment variables.

How Ingot builds your application

Every deployment that builds runs two steps in the build log:
  1. Generate. Ingot reads the files in the build path and writes a Dockerfile and a .dockerignore for them. The build log prints 📦 Generating Dockerfile using ingot..., then the build plan: each decision and the file it came from. See Read the build plan.
  2. Build. Sevalla builds the generated Dockerfile with the build path as the context and pushes the image. The build log prints 📦 Building and pushing docker image using the generated Dockerfile..., then the Docker build.
Ingot generates the Dockerfile again on every deployment that builds, so a change to your repository or to an INGOT_* variable takes effect with the next deployment. Restarts and rollbacks reuse an existing image and don’t run Ingot. What makes Ingot different from the other builders:
  • The same input gives the same Dockerfile. Ingot never runs your code and never uses the network while it plans. Language versions come from a catalog built into each Ingot release, so the same commit always produces the same Dockerfile.
  • Official images, maintained lines. Builds use the official image of each language, such as node:24-trixie-slim or python:3.14-slim-trixie (FrankenPHP’s for PHP). Images name a version line, not a patch release, so each new build picks up the line’s latest security fixes.
  • Dependencies from your lock file. Installs hold to the lock file, as npm ci and frozen pnpm, Yarn and Bun installs do.
  • Small, unprivileged images. The final image has no compilers or build tools, runs as user 1000:1000 instead of root, and starts under tini. Build-time variables reach the build as a BuildKit secret, so they never land in an image layer.
Your repository’s own Dockerfile isn’t used or changed: Ingot writes its files outside the repository. To build your own Dockerfile, use the Dockerfile build strategy.

Supported languages

Ingot tries the languages in a fixed order and builds with the first one that recognizes the application. If it detects the wrong one, set INGOT_PROVIDER (see Configure the build). If it recognizes none, the build fails; use a Dockerfile instead.

Use Ingot

1

Open the build strategy

In the sidebar, click Applications, then click the application. On the Settings tab, in the Build strategy section, click Update build strategy.
2

Choose Ingot

Click Ingot (the first option). Set the Build path if the application isn’t at the repository root. For a workspace in a monorepo, leave it at . and read Build an application in a monorepo.
3

Save and deploy

Click Update build strategy, then deploy the application.
The Update build strategy sheet with Ingot selected first, Cache enabled and the build path set to the repository rootThe Update build strategy sheet with Ingot selected first, Cache enabled and the build path set to the repository root
The Builder row in the Build strategy section shows Ingot, and the next deployment’s build log shows Ingot’s build plan.

Read the build plan

Before it builds, Ingot prints its plan in the build log: what it detected, the file each decision came from, and the image it builds. For a Node.js application with a package-lock.json and no Node.js version set:
Build log
  • The rows at the top are the decisions, each with its source: a file (package-lock.json), a field in it (package.json scripts.start), a variable you set, or default when nothing in the repository decided it.
  • The Image rows are the image Ingot builds in and the image the application runs in, the user it runs as, its init process and its port.
  • The last line names the Ingot version Sevalla ran.
Lines that start with ! after the plan are warnings: the build continues, but something deserves a look. For example, composer.lock is missing, so Composer chooses dependency versions during the build; commit composer.lock to deploy what you tested. When Ingot can’t plan the build, the build log shows a line starting with ✗ and what to do about it on the next line, and the deployment fails. For example:
Build log

See the Dockerfile Ingot generated

Sevalla keeps every Dockerfile Ingot generates, with its .dockerignore. On a deployment’s page, the Dockerfile tab (next to Build log) shows the Dockerfile the deployment’s image was built from and its version. When it differs from the previous deployment’s, it’s marked Changed and Changes shows what changed. Click Full file to read it whole, or History to open the application’s Dockerfile history and compare any two versions. The tab fills in when the build is done. A new Ingot release can change the Dockerfile without a change to your code: each version shows the Ingot version that generated it.
A deployment's Dockerfile tab with version 4 marked Changed and three lines added since version 3A deployment's Dockerfile tab with version 4 marked Changed and three lines added since version 3

Set the language version

Ingot takes the version from the first source in this order:
  1. INGOT_<LANGUAGE>_VERSION, such as INGOT_NODE_VERSION, set as an environment variable marked Available during buildtime.
  2. versions in ingot.json, such as "versions": { "node": "22" }.
  3. The files in this table, in the order listed.
  4. The default line of the Ingot release Sevalla runs. The plan shows default as the source.
Version files are read from the build path, then from each folder above it up to the repository root, the nearest first, as version managers read them. Write versions the way each ecosystem does: 22 or ^22 for Node.js, >=3.12 for Python, ~8.3.0 for PHP. How Ingot treats the version you set:
  • A line follows its patch releases. 22 or 3.12 builds on the line’s image tag, such as node:22-trixie-slim, which moves to each new patch release, so rebuilding picks up security fixes.
  • An exact release stays on that release. 22.11.0 or python-3.12.8 builds on that release only, with a warning that its image won’t get security rebuilds. If the catalog has no image for that patch release, such as python-3.8.10, Ingot builds on the newest patch release of the same minor release and warns about it.
  • End-of-life lines still build, with a warning. Ranges such as >=18 prefer maintained lines.
  • A version nothing satisfies fails the build. Ingot doesn’t fall back to another line. The error names the newest version the release knows, for example no python version in this Ingot release satisfies ">=3.16" (newest known: 3.15.2).
For PHP, Ingot also picks a version every package in composer.lock supports, and says which package limited it. For Python with no version set, if requirements.txt pins a release that has no Linux wheels for the default Python, such as psycopg2-binary==2.9.10 on 3.14, Ingot builds on the newest Python line those pins have wheels for and says which pin chose it.

Configure the build

Most applications build with no configuration. To override what Ingot detects, add an ingot.json file to the build path. Comments are allowed:
ingot.json
Each field has an environment variable. The variable wins over the file. Ingot reads only variables whose names start with INGOT_ and that are marked Available during buildtime. Commands in install and build run with /bin/sh in the build image, with your build-time variables set.

Set the start command

Ingot takes the start command from the first of these:
  1. The web process’s start command on the Processes tab. Sevalla passes it to Ingot as INGOT_START_CMD, so it also wins over an INGOT_START_CMD variable you set.
  2. An INGOT_START_CMD environment variable.
  3. start in ingot.json.
  4. The web entry of a Procfile in the build path.
  5. What Ingot detects: the framework’s production server (for example Gunicorn for Django and Flask, Uvicorn for FastAPI, FrankenPHP for PHP), or the start script in package.json.
The start command runs with /bin/sh, from the application’s folder in /app, as user 1000:1000. When the application has nothing that serves HTTP and the Procfile has a single other process, such as worker: python bot.py, Ingot runs that process and warns that you should deploy the application as a background worker.

Build an application in a monorepo

There are two ways to build one application of a repository that holds several. Use the one that matches how the application installs its dependencies. With INGOT_APP, Ingot installs the workspace’s dependencies at the root with its lock file, builds the application and the workspace packages it depends on (with Turborepo or Nx when the workspace uses them), and starts the application from its own folder. An ingot.json in the application’s folder configures that application on top of the root’s. Create one Sevalla application per application in the repository, each with its own INGOT_APP.

Environment variables during the build

The build gets every environment variable marked Available during buildtime, and the variables Sevalla sets, such as SVL_DEPLOYMENT_COMMIT_SHA. Sevalla passes them to the Docker build as a BuildKit secret named ingot_env, and the generated Dockerfile reads it in the steps that install dependencies and build the application.
  • Variables never land in an image layer or the image’s history. The running application gets its variables from Sevalla at runtime, not from the image.
  • Only variables whose names are valid shell names reach the build: letters, digits and underscores, not starting with a digit. A variable such as spring.profiles.active is left out of the build, but still set at runtime.
  • PORT isn’t set during the build, only at runtime.
Variables that are only Available during runtime don’t reach the build. If your framework reads a variable while it builds, for example NEXT_PUBLIC_* in Next.js or VITE_* in Vite, mark it Available during buildtime and deploy again after you change it.

The image Ingot builds

Ingot’s .dockerignore keeps files the image doesn’t need out of the build, such as .git, node_modules, logs, editor folders and .env.local, plus every pattern in your own .dockerignore.

Run workers and jobs

Ingot builds one image for the application. Sevalla runs its start command as the web process; every other process runs from the same image with its own start command. Create them on the Processes tab, for example: Ingot doesn’t create processes, and doesn’t run a Procfile’s release entry. Get Dockerfile returns the processes Ingot recommends for the application in processes, such as a queue worker or a migration job; create the ones you need.

Build cache

With Cache enabled, Sevalla stores the build’s layers in the application’s registry and reuses them on the next build. A step whose inputs didn’t change, such as installing dependencies from an unchanged lock file, comes from the cache. The steps that build your application run on every deployment, because they read the build-time variables, which include the deployment’s own SVL_* variables. To force a clean build, redeploy without the build cache.

Move to Ingot

Ingot builds most applications from the same repository with no changes. It doesn’t read nixpacks.toml, railpack.json, project.toml or NIXPACKS_* and RAILPACK_* variables. Before you switch a production application:
  1. Pin your language version in the repository, for example with engines.node, .python-version or require.php, so it doesn’t depend on any builder’s default.
  2. Translate the settings you rely on:
  3. Check what your application writes to disk while it runs. It runs as user 1000:1000, not root: it can write to /app and /tmp.
  4. Switch a staging copy first, or watch the first deployment’s build log. If it fails, switch back and deploy again.

Troubleshoot Ingot builds

Ingot’s messages in the build log say what failed and how to fix it. The common ones: For anything else, Troubleshoot builds and deployments covers failures every builder shares.

API and CLI

Each Dockerfile version in the API has source ingot, the ingot_version that generated it, and what Ingot detected: language (such as node or php), facts (the decisions of the build plan, each with its source) and processes (the processes it recommends). A deployment’s dockerfile_id names the version its image was built from.