# Project Configuration (`.locusbuild`) — Locus Build

> Companion guide to [`SKILL.md`](./SKILL.md). Recommended for all projects — single-service or multi-service.

## When To Load

Load this file only when the task needs `.locusbuild`, `from-repo`, `from-locusbuild`, verification, or monorepo setup details.

## Table of Contents

- [When to Use .locusbuild](#when-to-use-locusbuild)
- [.locusbuild File Format](#locusbuild-file-format)
- [One-Call Setup: from-repo](#one-call-setup-post-v1projectsfrom-repo)
- [from-locusbuild — Passing Config Directly](#from-locusbuild--passing-config-directly)
- [Verify Before Deploying](#verify-before-deploying-post-v1projectsverify-locusbuild)
- [Auto-Detection on Push](#auto-detection-on-push)
- [Agent Workflow](#agent-workflow)

## When to Use `.locusbuild`

**Always recommended.** A `.locusbuild` file at your repo root is the best way to configure any Locus project:

- **Explicit** — port, env vars, health check (optional), and start command are version-controlled and visible to your team
- **Auto-detected** — `from-repo` reads it automatically; `git push` and GitHub webhooks detect it on every push
- **Works for any project** — single-service repos, monorepos, repos with addons

**Only skip `.locusbuild` for pre-built Docker images** deployed via `source.type: "image"`, since there's no repo to put it in.

## `.locusbuild` File Format

Add a `.locusbuild` file at the repo root. It tells Locus how to set up services and addons for your project.

**Single-service project (simplest):**

```json
{
  "services": {
    "web": {
      "path": ".",
      "port": 8080
    }
  }
}
```

`healthCheck` is optional — if omitted, Locus only checks that the container process stays running. Set it explicitly for HTTP health checks (e.g., `"healthCheck": "/health"`).

**Multi-service project** (monorepo with `backend/` and `frontend/` directories):

```json
{
  "services": {
    "backend": {
      "path": "backend",
      "port": 8080,
      "env": {
        "DATABASE_URL": "${{main-db.DATABASE_URL}}"
      }
    },
    "frontend": {
      "path": "frontend",
      "port": 8080,
      "env": {
        "API_URL": "${{backend.URL}}"
      }
    }
  },
  "addons": {
    "main-db": {
      "type": "postgres"
    }
  }
}
```

**Complete example** showing all optional fields:

```json
{
  "region": "sa-east-1",
  "services": {
    "api": {
      "path": "services/api",
      "port": 8080,
      "startCommand": "npx prisma migrate deploy && npm start",
      "runtime": { "cpu": 512, "memory": 1024 },
      "env": { "LOG_LEVEL": "info", "DATABASE_URL": "${{db.DATABASE_URL}}" }
    }
  },
  "addons": {
    "db": { "type": "postgres" }
  }
}
```

**Top-level fields:**
- `region` (optional) — deployment region. Valid values: `us-east-1` (default), `sa-east-1` (Sao Paulo). Sets the region for the project created by `from-repo` or `from-locusbuild`. Omit to use the default (`us-east-1`).

**Service fields:** Only `path` is required. All others have defaults or are optional:
- `path` (required) — subdirectory containing the service code
- `port` (default `8080`) — container port the service listens on
- `healthCheck` (optional) — HTTP health check endpoint path. When omitted, Locus only checks that the process stays running
- `startCommand` (optional) — override the default start command (e.g., run migrations before starting)
- `runtime` (optional) — `cpu` (default 256, in CPU units) and `memory` (default 512, in MB)
- `env` (optional) — environment variables for this service

**Not supported in `.locusbuild`:** `buildConfig` fields (`method`, `dockerfile`, `buildArgs`), `errorPatterns`, and `autoDeploy` are only available via the direct `POST /v1/services` API — they cannot be set in a `.locusbuild` file.

**Addon fields:** `type` (required: `postgres` or `redis`)

**Addon references:** Addons are environment-level. To connect a service to an addon, add explicit env references in `.locusbuild`:
```json
"backend": {
  "env": {
    "DATABASE_URL": "${{db.DATABASE_URL}}"
  }
}
```
Only services with explicit references receive addon variables. Available fields — Postgres: `DATABASE_URL`, `HOST`, `PORT`, `USERNAME`, `DATABASE`. Redis: `REDIS_URL`, `HOST`, `PORT`.

**Service reference templates:** Use `${{serviceName.URL}}` to reference sibling service URLs. This uses the existing variable resolution system — see [Service-to-Service References](./SKILL.md#service-to-service-references).

## One-Call Setup: `POST /v1/projects/from-repo`

Use `from-repo` for **all GitHub repos** — monorepos and single-service repos alike. **One API call** creates project + environment + all services + addons + triggers first deployments. If the repo has a `.locusbuild` file, it creates services from that config. If not, it creates a single `web` service with sensible defaults (root dir, port 8080, process-alive health check).

> **Billing:** Each service and addon created costs **$0.25/month** from the workspace credit balance. A monorepo with 2 services + 1 addon = $0.75 deducted on setup. New workspaces start with $1.00. Returns `402` if the workspace cannot afford all requested services — see [billing.md](./billing.md).

```bash
RESULT=$(curl -s -X POST https://api.buildwithlocus.com/v1/projects/from-repo \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-monorepo",
    "repo": "my-org/my-repo",
    "branch": "main"
  }')

echo $RESULT | jq
```

Response (201):
```json
{
  "project": { "id": "proj_xxx", "name": "my-monorepo" },
  "environment": { "id": "env_xxx", "name": "production" },
  "services": [
    { "id": "svc_xxx", "name": "backend", "url": "https://svc-xxx.buildwithlocus.com" },
    { "id": "svc_yyy", "name": "frontend", "url": "https://svc-yyy.buildwithlocus.com" }
  ],
  "addons": [
    { "id": "addon_xxx", "name": "main-db", "type": "postgres", "status": "provisioning" }
  ],
  "deployments": [
    { "serviceId": "svc_xxx", "serviceName": "backend", "deploymentId": "deploy_xxx", "status": "queued" },
    { "serviceId": "svc_yyy", "serviceName": "frontend", "deploymentId": "deploy_yyy", "status": "queued" }
  ],
  "locusbuild": true
}
```

## `from-locusbuild` — Passing Config Directly

`POST /v1/projects/from-locusbuild` works like `from-repo` but takes `.locusbuild` content in the request body instead of reading it from the repo.

**Key difference from `from-repo`:**
- `from-repo`: Reads `.locusbuild` from the GitHub repo automatically
- `from-locusbuild`: Takes `locusbuild` JSON in the body, but **still requires a real `repo` value** — source code is cloned from that GitHub repo

```bash
curl -s -X POST https://api.buildwithlocus.com/v1/projects/from-locusbuild \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-app",
    "repo": "my-org/my-repo",
    "branch": "main",
    "locusbuild": {
      "services": {
        "backend": { "path": "backend", "port": 8080 }
      }
    }
  }'
```

> **If you don't have a GitHub repo:** Do NOT use `from-locusbuild` with a fake repo value. Instead, use the manual setup workflow: create project → environment → services (with `source.type: "s3"` and `rootDir`) → provision addons → `git push`. See [git-deploy.md](./git-deploy.md) for the full local code workflow.

## Verify Before Deploying: `POST /v1/projects/verify-locusbuild`

**IMPORTANT:** Always verify your `.locusbuild` file before deploying. This endpoint validates the structure and returns the proposed deployment plan so you can confirm it matches expectations — without creating any resources.

```bash
curl -s -X POST https://api.buildwithlocus.com/v1/projects/verify-locusbuild \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "locusbuild": {
      "services": {
        "backend": { "path": "backend", "port": 8080, "env": {"DATABASE_URL": "${{main-db.DATABASE_URL}}"} },
        "frontend": { "path": "frontend", "port": 8080, "env": {"API_URL": "${{backend.URL}}"} }
      },
      "addons": { "main-db": { "type": "postgres" } }
    }
  }'
```

Response when valid (200):
```json
{
  "valid": true,
  "errors": [],
  "plan": {
    "services": [
      { "name": "backend", "path": "backend", "port": 8080, "env": {"DATABASE_URL": "${{main-db.DATABASE_URL}}"} },
      { "name": "frontend", "path": "frontend", "port": 8080, "env": {"API_URL": "${{backend.URL}}"} }
    ],
    "addons": [
      { "name": "main-db", "type": "postgres" }
    ]
  }
}
```

Response when invalid (400):
```json
{
  "valid": false,
  "errors": ["Service names must be DNS-safe (lowercase alphanumeric and hyphens)"],
  "plan": null
}
```

Review the `plan` to confirm the services, paths, ports, and addons match what you expect before calling `from-repo` or `from-locusbuild`.

## Auto-Detection on Push

`.locusbuild` is auto-detected in **all** deploy flows:

- **GitHub push webhook:** When a push arrives, Locus fetches `.locusbuild` from the repo and creates any new services/addons before deploying all services.
- **Git remote push:** When code is pushed via `git push`, Locus extracts `.locusbuild` from the archive and syncs services/addons before deploying.

New services added to `.locusbuild` are created automatically on the next push. Removed services are **not** auto-deleted (safety — delete manually if needed).

## Agent Workflow

**IMPORTANT:** When you create or modify a `.locusbuild` file, ALWAYS call `verify-locusbuild` first to validate the structure and review the deployment plan before deploying.

```bash
# 1. Exchange API key for token
TOKEN=$(curl -s -X POST https://api.buildwithlocus.com/v1/auth/exchange \
  -H "Content-Type: application/json" \
  -d '{"apiKey":"YOUR_KEY"}' | jq -r '.token')

# 2. Verify the .locusbuild structure first
VERIFY=$(curl -s -X POST https://api.buildwithlocus.com/v1/projects/verify-locusbuild \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"locusbuild": { ... }}')

# Check valid=true and review the plan before proceeding
echo $VERIFY | jq '.valid, .plan'

# 3. If valid, deploy (one call sets up everything)
curl -s -X POST https://api.buildwithlocus.com/v1/projects/from-repo \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "repo": "owner/repo", "branch": "main"}'

# 4. Done. Subsequent deploys happen automatically via GitHub push or git push.
```
