Skip to content

Build Configuration

The build block in pilum.yaml says how to compile a service on the host before (or instead of) the recipe’s packaging step.

Basic Structure

build:
language: go
version: "1.23"
cmd: "go build -o ./dist/app ."
env_vars:
CGO_ENABLED: "0"

Fields

FieldTypeDefaultDescription
languagestring-Build language. Picks pilum init defaults, the memory estimate for worker planning, and the toolchain for automatic cache warming. Does not generate a build command
versionstring-Language version. Informational; written by pilum init
cmdstring-Shell command that builds the service. Empty means no host build
env_varsmap-Environment variables for this service’s steps
flagsmap of lists-Flags appended to the end of cmd (see Build Flags)
version_varstring-Homebrew only. Go variable that receives the release tag via -X
resources.memoryint (MB)language defaultPeak build memory, used to cap parallel builds
resources.cpuint (cores)2Cores one build uses, used to size parallel builds
warmstring or falseautomaticCommand that fills the shared build cache once per run, or false to opt out
warm_dirstringproject rootDirectory to run warm in, relative to where you run Pilum
output_dirstringdistCloudflare Pages only. Directory to deploy, relative to the service directory

output and platforms appear in older examples and in the JSON schema, but Pilum ignores them. The Homebrew recipe always builds darwin/linux × amd64/arm64.

How cmd Runs

cmd runs through /bin/sh -c, so pipes, && and environment variable expansion work.

RecipeStep that runs cmdWorking directory
gcp-cloud-run, gcp-cloud-run-job, gcp-artifact-registry-image, gcp-mig-container, azure-container-appsbuild binaryService directory
npmbuild packageService directory
cloudflare-pagesbuild siteProject root (so pnpm --filter works)
aws-lambda— (runs sam build)—
homebrew— (runs a fixed Go cross-compile)—

If cmd is empty, the build step is skipped for that service. For container recipes this means a Dockerfile-only build: the image is built entirely by docker build with your _templates/<template> Dockerfile. That’s the right choice when the Dockerfile compiles the code itself. Keep cmd when the Dockerfile copies a binary built on the host (for example from dist/).

Languages

Pilum knows 12 languages. Each has a template used by pilum init and a default memory estimate:

languageAliasespilum init default cmdDefault memory
gogolanggo build -o ./dist (plus ldflags)1024 MB
nodenodejs, javascript, js, typescript, tsnpm run build1536 MB
bunbun run build1536 MB
pythonpip install -r requirements.txt512 MB
rustcargo build --release (plus target flag)2048 MB
java./mvnw -B package -DskipTests3072 MB
kotlin./gradlew build -x test3072 MB
dotnetcsharp, c#, fsharp, f#, .netdotnet publish -c Release -o ./dist2048 MB
ccmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel1024 MB
cppc++, cxxsame as c2048 MB
phpcomposer install --no-dev --optimize-autoloader --no-interaction1024 MB
rubyrbbundle install1024 MB

An unknown language is allowed and gets 1024 MB. The language never changes what cmd does.

Build Flags

flags is a map from flag name to a list of values. Pilum renders each entry as -name='value1 value2' and appends it to the end of cmd, sorted by flag name:

build:
language: go
cmd: "go build -o ./dist/app"
flags:
ldflags:
- "-s"
- "-w"

runs:

Terminal window
go build -o ./dist/app -ldflags='-s -w'

For language: rust, flags get a double dash, so target: ["x86_64-unknown-linux-musl"] becomes --target='x86_64-unknown-linux-musl'.

Flags are only appended for container recipes. The npm and Cloudflare Pages recipes run cmd as written.

Version Injection

Pilum does not substitute ${TAG} or any other placeholder in cmd or flags. A -X main.version=${TAG} flag passes the literal text (or an empty string) to the linker.

  • Homebrew: set version_var and the recipe adds -X <version_var>=<tag> to its cross-compile:

    build:
    language: go
    version_var: github.com/my-org/my-cli/cmd.version
  • Other recipes: export the version in your CI environment and reference it from cmd. Steps inherit the shell environment, and /bin/sh -c expands it:

    build:
    language: go
    cmd: 'go build -ldflags "-s -w -X main.version=$VERSION" -o ./dist/app .'
    Terminal window
    VERSION=v1.2.3 pilum deploy --tag=v1.2.3

Environment Variables

build:
env_vars:
CGO_ENABLED: "0"
GOOS: linux
GOARCH: amd64
  • Values must be strings; quote numbers and booleans.
  • The variables are added to Pilum’s own environment for every step of that service, not only the build command. A recipe step’s own variables override them.
  • $NAME and ${NAME} in values are expanded from Pilum’s environment, so GIT_SHA: "${GITHUB_SHA}" works. There is no escape, so a value cannot contain a literal $.
  • Pilum sets GOMAXPROCS per task from its worker plan unless you set it here or in your shell.
  • Build variables also distinguish warm groups: services with different GOOS/GOARCH warm separately.

Runtime variables for the deployed service go in the top-level env_vars, not here. See Service Configuration.

Resources

Pilum runs builds in parallel and sizes the pool from the machine:

  • Workers for CPU-bound steps = CPU cores ÷ the largest resources.cpu across services (default 2).
  • That number is capped so the largest build memory estimate times the worker count fits in 75% of RAM. The estimate is resources.memory, or the language default from the table above.
  • Steps that call Docker are sized from the Docker daemon’s CPUs and memory (the Docker Desktop VM on macOS).
  • Push, deploy and execute steps use up to 16 workers.
  • --max-workers N overrides all of this.

Set resources when a build needs much more or less than its language default:

build:
language: rust
cmd: "cargo build --release"
resources:
memory: 4096 # MB
cpu: 4 # cores

Build Cache Warming

Before the builds start, Pilum can fill the language’s shared cache (Go build cache, pnpm store, Cargo registry, …) once, so parallel builds don’t all download and compile the same dependencies.

build:
language: node
warm: pnpm install --frozen-lockfile # explicit command
warm_dir: apps # optional, relative to where you run Pilum
cmd: pnpm build
  • Unset: automatic. Pilum detects the toolchain (Go, Node, Python uv/pip, Cargo, Gradle, Maven, .NET, Composer) and warms when two or more services with a host cmd share it.
  • A string: run this command once. Services with the same warm, warm_dir and build env_vars share one run.
  • false: never warm this service. --no-warm disables warming for the whole run.

Warming runs only when a build step will run, and a failed warm falls back to cold builds. See Build Cache Warming for the commands Pilum generates per toolchain.

Language Examples

Go

build:
language: go
version: "1.23"
cmd: "go build -o ./dist/app"
env_vars:
CGO_ENABLED: "0"
GOOS: linux
GOARCH: amd64
flags:
ldflags:
- "-s"
- "-w"

Node.js

build:
language: node
version: "20"
cmd: "npm run build"
env_vars:
NODE_ENV: production

For a pnpm workspace, build a package and its workspace dependencies in order with the ... suffix:

build:
language: node
cmd: "pnpm --filter @scope/package... build"

Which install command runs before the build is set by the recipe, not by build:

  • npm recipe: npm.package_manager — npm (default), pnpm, yarn or bun. See npm.
  • Cloudflare Pages recipe: cloudflare.package_manager — npm (default) or pnpm. See Cloudflare Pages.

Python

build:
language: python
version: "3.12"
cmd: "pip install -r requirements.txt"
env_vars:
PYTHONDONTWRITEBYTECODE: "1"
PYTHONUNBUFFERED: "1"

Rust

build:
language: rust
version: "1.75"
cmd: "cargo build --release"
flags:
target:
- "x86_64-unknown-linux-musl" # rendered as --target='...'
env_vars:
CARGO_NET_GIT_FETCH_WITH_CLI: "true"

Java

build:
language: java
version: "21"
cmd: "./mvnw -B package -DskipTests"

Dockerfile-only

type: gcp-cloud-run
template: node # _templates/node does the whole build
build:
language: node # still used for memory planning

Custom commands

build:
language: go
cmd: "make build"
build:
language: go
cmd: "./scripts/build.sh"

CLI Help

pilum build --help prints build examples by language.

Next Steps