Skip to main content
Builds define how to produce artifacts. Services and crons reference builds.
specific.hcl
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 to specific.hcl.
Optional fields:
  • command - build command to run after dependencies are installed (for example, npm run build or go build -o api). Only valid with base, not with dockerfile.
  • root - working directory for build commands, relative to specific.hcl. Defaults to ".". Sets the WORKDIR in the generated Dockerfile; services that reference this build inherit its root, and dependency detection looks here.
  • context - Docker build context scope, relative to specific.hcl. Defaults to ".". Controls what files are available to COPY and 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:
specific.hcl
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.
specific.hcl
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:
specific.hcl
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
# 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:
specific.hcl
build "api" {
  dockerfile = "Dockerfile"
}
When using a custom Dockerfile:
  • command is not allowed - define build steps in the Dockerfile itself.
  • context still controls the Docker build context.
  • env vars are passed as Docker build args; consume them with ARG instructions.
  • The service command still overrides the container CMD at runtime.
specific.hcl
build "web" {
  dockerfile = "docker/Dockerfile.prod"
  context = "."

  env = {
    NEXT_PUBLIC_API_URL = service.api.public_url
    NODE_ENV            = "production"
  }
}
Dockerfile
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.
specific.hcl
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:
specific.hcl
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.