Custom Builds
Temps builds apps with autopack, its built-in builder, and doesn't run Heroku or Paketo buildpacks. To change how an app builds, add a nixpacks.toml (system packages, runtime versions, install, build and start commands), set build commands in .temps.yaml, or bring your own Dockerfile.
How build detection works
Temps picks a preset from the files in your repository: a Compose file first, then a Dockerfile, then framework config files, then language manifests, and finally a nixpacks.toml. Some stacks, including Express, Rails and Laravel, aren't detected and need a preset chosen on the project. The full order and the preset each framework needs are in How framework detection works.
nixpacks.toml
The nixpacks-* presets keep their names, but since August 2026 Temps builds them with autopack, not the Nixpacks binary. Autopack still reads a nixpacks.toml in the app directory, when the project builds with autopack or a nixpacks-* preset, and translates the parts that have an equivalent:
nixpacks.toml
[phases.setup]
nixPkgs = ["nodejs_22"]
aptPkgs = ["ffmpeg", "imagemagick"]
[phases.install]
cmds = ["npm ci"]
[phases.build]
cmds = ["npm run build"]
[start]
cmd = "node dist/server.js"
| Setting | What autopack does with it |
|---|---|
aptPkgs | Installs the Debian packages |
nixPkgs | Runtimes only (nodejs_22, python311, go_1_23, …) set the language version; other Nix packages aren't installed and are reported in the build log |
[phases.install] and [phases.build] cmds | Replace the install and build commands |
| Other phases | Not run; reported in the build log |
[start] cmd | Replaces the start command |
[variables] | Set in the running container |
providers | The first one forces the language provider |
Use aptPkgs, not nixPkgs, for system packages such as ffmpeg or imagemagick. A web: line in a Procfile also sets the start command.
Build commands in .temps.yaml
.temps.yaml in the app directory can override the install command, the build command and, for static builds, the output directory:
.temps.yaml
build:
install_command: pnpm install --frozen-lockfile
build_command: pnpm run build
output_dir: dist/my-app/browser
Dockerfile
A Dockerfile gives you complete control over the build and the runtime image. Multi-stage builds, build arguments and layer caching are covered in Deploy Docker Containers:
Build caching
Temps builds with BuildKit, which keeps Docker layers between builds, and autopack builds add cache mounts for package-manager downloads (for example the Cargo registry and target/ for Rust). Order your own Dockerfiles so dependency installs come before COPY . ., as shown in Build caching.
There is no per-project button to clear the cache. To force a clean build, run docker builder prune on the server, which clears the BuildKit cache for every project, then deploy again.
Build resources
Temps does not pin a fixed per-build CPU/memory default. Out of the box each docker build is allowed to grab up to 50% of the host's CPU and RAM (the legacy heuristic). Operators can replace that heuristic with explicit caps via the control-plane Build Limits plugin — see below.
Control-plane build limits
Operators can cap how many builds run at once on the control plane, and how much CPU/memory each build is allowed to consume. This stops several simultaneous docker build runs from saturating the host — without these limits, each build grabs up to 50% of host CPU/RAM, so a few concurrent deploys can pin a busy server.
Configure these in the console under Settings → Infrastructure → Build Limits (route /settings/build-limits). Three knobs are available:
| Setting | Field | Default | Range | Effect |
|---|---|---|---|---|
| Max concurrent builds | max_concurrent | 2 | 1–32 | How many docker build operations run at once. Extra builds queue rather than fail. |
| CPU per build (cores) | cpu_limit_cores | 0.0 | 0–64 | Cores allowed per build (e.g. 2.0 = 2 cores). 0 = use the legacy 50%-of-host default. |
| Memory per build (MB) | memory_limit_mb | 0 | 0–262144 | Hard cap per build; builds that exceed it are OOM-killed. 0 = use the legacy 50%-of-host default. |
A 0 on either resource dimension means that dimension keeps the legacy 50%-of-host heuristic. Resource caps only take effect when both cpu_limit_cores and memory_limit_mb are greater than 0; if either is 0, the override is dropped and both fall back to the heuristic. With the defaults (2 concurrent, 0/0 resources), existing installs see no behavior change until an operator sets real values.
Queuing, not failing. A process-wide semaphore inside the build runtime gates every build to max_concurrent permits. When all slots are in use, additional builds wait for a free slot rather than erroring, and the build log stream shows:
[BUILD QUEUED] Waiting for an available build slot...
Docker's build API stores the per-build memory cap as a 32-bit byte count (≈ 2 GiB), so caps above ~2048 MB are silently clamped. This is an upstream Docker limitation, not a Temps one.
When changes take effect
- Concurrency (
max_concurrent) takes effect on the next plugin restart — i.e. the nexttemps servestart. - Per-build CPU/memory caps apply to the very next build, no restart needed.
Worker nodes are not limited
These limits apply to the control plane only. Worker nodes (those joined and run via temps cli agent) skip the build-limits plugin entirely and keep unbounded build behavior — by design, since each worker is dedicated hardware with its own headroom.