Custom Buildpacks
Temps uses Nixpacks to build most apps automatically. For cases where auto-detection isn't enough, you can provide a Dockerfile or a nixpacks.toml for full control.
How Build Detection Works
Temps inspects your repository root in this order:
Dockerfile— if present, used directly; Nixpacks is skippednixpacks.toml— if present, overrides Nixpacks auto-detection- Auto-detection — Nixpacks infers the runtime from
package.json,requirements.txt,go.mod,Gemfile, etc.
nixpacks.toml
Override specific phases without writing a full Dockerfile:
[phases.setup]
nixPkgs = ["ffmpeg", "imagemagick"]
[phases.install]
cmds = ["npm ci"]
[phases.build]
cmds = ["npm run build"]
[start]
cmd = "node dist/server.js"
Common use cases:
- Add system packages (
nixPkgs) not available from the package manager - Change the start command without touching the Dockerfile
- Pin a specific Node.js or Python version
[phases.setup]
nixPkgs = ["nodejs_20", "python311"]
Dockerfile
A Dockerfile at the repo root gives you complete control. Temps builds it with docker buildx and runs the resulting image.
Multi-stage builds
Multi-stage builds keep the final image small by separating the build environment from the runtime:
# --- Build stage ---
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# --- Runtime stage ---
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
For a Next.js app this typically cuts the image from ~1.5 GB to ~150 MB.
Build arguments
Declare build-time variables in your Dockerfile with ARG, then promote them to runtime environment variables with ENV if your app needs them at runtime:
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
Build arguments are baked into the image layer and visible in docker history. Use environment variables (not build args) for secrets.
Build Caching
Temps caches Docker layer and Nixpacks artifacts between builds. To get the most out of caching:
For Node.js — copy package.json and install before copying source:
COPY package*.json ./
RUN npm ci # cached if package.json unchanged
COPY . . # cache busted here when source changes
RUN npm run build
For Python — same pattern with requirements.txt:
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
To force a clean build (clear cache), go to Project → Settings → Build → Clear Cache and trigger a new deploy.
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.