# Builds

Builds define how to produce artifacts. Services reference builds.

```hcl
build "api" {
  base = "node"
  command = "npm run build"
}
```

## Required fields

One of the following is required (mutually exclusive):

- `base` - Base environment. Supported: `"node"`, `"python"`, `"go"`, `"rust"`, `"java"`. If another base or customization is needed, use a custom Dockerfile instead.
- `dockerfile` - Path to a custom Dockerfile, relative to `specific.hcl`. See [Custom Dockerfile](#custom-dockerfile) below.

## Optional fields

- `command` - Build command to run after dependencies are installed (e.g., `npm run build`, `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. Dependency detection (e.g., `package.json`) also looks in this directory.
- `context` - Docker build context scope, relative to `specific.hcl`. Defaults to `"."`. Controls what files are available to `COPY` in the Dockerfile and what's included in the deployment tarball. Use this when you need to include files outside of `root` (e.g., shared libraries in a monorepo).
- `env` - Environment variables available during the build. Supports string literals, `service.<name>.public_url` references, and string interpolation with `${service.<name>.public_url}`. These are passed as Docker build args.

## Automatic dependency installation

Specific automatically installs dependencies before running your build command. Dependencies are cached in a separate Docker layer for faster builds when only source code changes.

| Base     | Detected Files      | Install Command                    |
| -------- | ------------------- | ---------------------------------- |
| `node`   | `package-lock.json` | `npm ci`                           |
| `node`   | `yarn.lock`         | `yarn install --frozen-lockfile`   |
| `node`   | `pnpm-lock.yaml`    | `pnpm install --frozen-lockfile`   |
| `node`   | `package.json` only | `npm install`                      |
| `python` | `requirements.txt`  | `pip install -r requirements.txt`  |
| `python` | `Pipfile.lock`      | `pipenv install --deploy --system` |
| `python` | `poetry.lock`       | `poetry install`                   |
| `python` | `pyproject.toml`    | `pip install .`                    |
| `go`     | `go.mod`            | `go mod download`                  |

For simple projects that only need dependencies installed, you can omit the `command` field:

```hcl
build "api" {
  base = "node"
}
```

## Base details

For **node** and **python**, the entire working directory is available at runtime.

For **go**, **rust**, and **java**, a multi-stage build is used:

- **go**: The entire working directory is copied to the runtime image.
- **rust**: Only `target/release` is copied to the working directory in the runtime image.
- **java**: JAR files from `target/*.jar` or `build/libs/*.jar` are copied to `app.jar` in the working directory.

## Build-time environment variables

Use `env` to pass environment variables during the build step. This is useful for frameworks like Next.js that inline environment variables at build time.

```hcl
build "web" {
  base    = "node"
  command = "npm run build"

  env = {
    NEXT_PUBLIC_API_URL = service.api.public_url
    NODE_ENV            = "production"
  }
}
```

Supported reference types:

- String literals (e.g., `"production"`)
- `service.<name>.public_url` - The public domain of another service
- String interpolation with `${service.<name>.public_url}` (e.g., `"https://${service.api.public_url}/v1"`)

Since `public_url` does not include a scheme, use interpolation to compose full URLs:

```hcl
build "web" {
  base    = "node"
  command = "npm run build"

  env = {
    NEXT_PUBLIC_API_URL = "https://${service.api.public_url}/v1"
    NODE_ENV            = "production"
  }
}
```

## Monorepo support

Use `root` to specify where build commands run in a monorepo. This sets the working directory for both the build command and dependency installation.

```hcl
build "backend" {
  base = "node"
  root = "packages/backend"
  command = "npm run build"
}

build "frontend" {
  base = "node"
  root = "packages/frontend"
  command = "npm run build"
}
```

Without `root`, you would need to prefix commands with `cd packages/backend &&`, and dependency detection would look for `package.json` in the wrong directory.

Services that reference a build inherit its `root` automatically, so service commands also run from the correct directory.

### `root` vs `context`

`root` and `context` 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), which includes all files in the project. You only need to set `context` when you want to widen the scope beyond the default, for example to include a parent directory:

```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 Docker build, provide your own Dockerfile instead of using `base`:

```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. Use `ARG` instructions in your Dockerfile to consume them.
- The service `command` still overrides the container `CMD` at runtime.

### Custom Dockerfile with build args

```hcl
build "web" {
  dockerfile = "docker/Dockerfile.prod"
  context = "."

  env = {
    NEXT_PUBLIC_API_URL = service.api.public_url
    NODE_ENV            = "production"
  }
}
```

In the Dockerfile:

```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"]
```

## Dev configuration

Override the build command for local development. If no `dev` block is defined, the build is skipped in development.

```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, the build is skipped in development and no binary is produced. Use a tool that rebuilds on file changes:

```hcl
build "api" {
  base = "go"
  command = "go build -o api ./cmd/api"

  dev {
    command = "go run ./cmd/api"
  }
}
```

---

Run `specific docs services` for service configuration.
