Builds
Define how your code is built: managed base environments or custom Dockerfiles.
Builds define how to produce artifacts. Services and crons reference builds.
build "api" {
base = "node"
command = "npm run build"
}
Configuration
One of the following is required (mutually exclusive):
base- base environment:"node","python","go","rust", or"java". For anything else, use a custom Dockerfile.dockerfile- path to a custom Dockerfile, relative tospecific.hcl.
Optional fields:
command- build command to run after dependencies are installed (for example,npm run buildorgo build -o api). Only valid withbase, not withdockerfile.root- working directory for build commands, relative tospecific.hcl. Defaults to".". Sets theWORKDIRin the generated Dockerfile; services that reference this build inherit its root, and dependency detection looks here.context- Docker build context scope, relative tospecific.hcl. Defaults to".". Controls what files are available toCOPYand what’s included in the deployment tarball.env- environment variables available during the build, passed as Docker build args. See Build-time environment variables.
Automatic dependency installation
Specific detects your dependency manager (from the lockfile) and installs dependencies before running your build command. Installs are cached, so rebuilds are fast when only source code changes. For projects that only need dependencies installed, omit command:
build "api" {
base = "node"
}
Run specific docs builds for the exact per-base detection and install behavior.
Build-time environment variables
Use env to pass variables during the build step. This is useful for frameworks like Next.js that inline environment variables at build time.
build "web" {
base = "node"
command = "npm run build"
env = {
NEXT_PUBLIC_API_URL = "https://${service.api.public_url}/v1"
NODE_ENV = "production"
}
}
Supported reference types:
- String literals (for example,
"production"). service.<name>.public_url- the public domain of another service.- String interpolation with
${service.<name>.public_url}.
Since public_url doesn’t include a scheme, use interpolation to compose full URLs. Other references (secrets, config, databases) are not available at build time.
Monorepos
Use root to set where build commands run. This applies to both the build command and dependency detection:
build "backend" {
base = "node"
root = "packages/backend"
command = "npm run build"
}
build "frontend" {
base = "node"
root = "packages/frontend"
command = "npm run build"
}
root vs context
The two are orthogonal:
root= where commands run (working directory).context= what files are available (Docker build context / tarball scope).
By default, context is the specific.hcl directory. Set it only to widen the scope, for example to include shared libraries in a parent directory:
# specific.hcl is in apps/myapp/, but shared libs are in libs/
build "api" {
base = "node"
root = "packages/api"
context = "../" # Include parent dir so shared libs are available
}
Custom Dockerfile
For full control over the build, provide your own Dockerfile instead of base:
build "api" {
dockerfile = "Dockerfile"
}
When using a custom Dockerfile:
commandis not allowed - define build steps in the Dockerfile itself.contextstill controls the Docker build context.envvars are passed as Docker build args; consume them withARGinstructions.- The service
commandstill overrides the containerCMDat runtime.
build "web" {
dockerfile = "docker/Dockerfile.prod"
context = "."
env = {
NEXT_PUBLIC_API_URL = service.api.public_url
NODE_ENV = "production"
}
}
FROM node:20-slim
ARG NEXT_PUBLIC_API_URL
ARG NODE_ENV
WORKDIR /app
COPY . .
RUN npm ci && npm run build
CMD ["npm", "start"]
Local development
The dev block overrides the build command during specific dev. If no dev block is defined, the build is skipped in development; most dev servers (like npm run dev) handle building themselves.
build "spa" {
base = "node"
command = "npm run build"
dev {
command = "npm run build:watch"
}
}
For compiled languages (Go, Rust, Java), a dev block is almost always needed; without it, no binary is produced locally. Use a tool that rebuilds on file changes:
build "api" {
base = "go"
command = "go build -o api ./cmd/api"
dev {
command = "go run ./cmd/api"
}
}
Production
During specific deploy, builds run in the cloud in parallel, after a local test run catches errors early. See Deployments for the full pipeline.