BTSBTS

Resource Limits & Quotas

Two levels of limits. Per-project limits cap each environment's containers. Account quotas cap your total usage across every environment. Both are checked before every deploy.

Per-project limits

Set them in the dashboard or in nexos.yaml. Enforced by containerd via Linux cgroups.

# nexos.yaml
app:
  cpu: 1            # vCPU (0.1–16)
  memory: 512       # vRAM in MB (64–65536)

buildResources:
  cpu: 2            # vCPU during build
  memory: 2048      # vRAM in MB during build
  timeout: 600      # max build seconds (60–3600)

# With containers:, each container carries its own limits
# and the app: limits above size nothing.
containers:
  - name: web
    cpu: 0.5
    memory: 512
  - name: worker
    cpu: 0.25
    memory: 256
  • vCPU is throttled via CFS bandwidth — the app slows down past the limit but is not killed.
  • vRAM exceeded → container is OOM-killed and restarted.
  • Build timeout — hung builds get killed, freeing the node.

Account quotas

Quotas prevent one user from monopolizing the network. Determined by your plan — each tier is a bundle of the hard caps below. There is no usage metering:

QuotaFreeProTeamEnterprise
Max projects1310Custom
Total environments51550Custom
Active at once2515Custom
Total vCPU3616Custom
Total vRAM4 GB12 GB24 GBCustom
Build minutes / month3001,5005,000Custom
Members115 (+$5/seat)Custom
Private nodes———Custom
Auto-pause—YesYesYes
Create teams——YesYes

Each plan's vCPU and vRAM are sized so its “active at once” environments fit at the default limits, with one of them building. On Free, one running environment (1 vCPU) plus a second one's first build (2 vCPU) is exactly the 3 vCPU pool.

Inviting members

A team owner or admin invites someone by email address from the workspace page. BTS sends no email: it gives you an invite link, valid for 7 days, to pass on yourself. A pending invite holds a member seat.

Only someone signed in with the invited address can accept, and BTS must know that address is theirs. BTS can't check an address by mail, so it relies on GitHub: an address counts as verified when it came from Sign in with GitHub, or is a verified address on the GitHub account linked to yours. If you signed up with a username and password, link GitHub when the invite page asks (with the address verified on that GitHub account), then accept again. Changing your email in account settings un-verifies it, unless the new address is one your linked GitHub account has verified. An invite for a different address than your account's can't be accepted as it is: change your email to the invited address (verified on your linked GitHub account), or ask for an invite to your own.

How your vCPU and vRAM are counted

Your usage is worked out live from what is running, not from a meter:

  • Every active environment counts what its containers are given: the app limits or, when the project has containers:, the sum of each long-running container's cpu and memory. type: job containers only run during a deploy and don't count.
  • While an environment builds, it counts the larger of that and its buildResources, not both. A deployment counts one build however many images it builds.
  • Running workflow (CI) jobs count their own cpu and memory.

Before each build starts:

  • It fits → the build starts.
  • It fits once your other builds and CI jobs finish → the deploy is queued and starts automatically, oldest first.
  • It can't fit even then, because the other active environments already hold too much → the deploy fails at once, with a message saying what it needed.

Multi-container environments add up quickly. Three containers at the default 1 vCPU each hold 3 vCPU, which is the whole Free pool. A preview rarely needs a full vCPU per container: something like cpu: 0.5 for the web container and 0.25 for the others leaves room for more environments and their builds.

Counting containers individually is being phased in. Until it is enforced, a deploy that the earlier accounting (one app allowance per environment) would have started still starts. Your workspace's quota card already shows the per-container figure.

Private nodes only add room for projects placed on them. If your workspace owns private nodes, their capacity (or the override you set on a node) is added to your pool, but projects on the shared pool are still limited to your plan's vCPU and vRAM: their environments, builds and CI jobs must fit the plan alone. A deploy held back by that says so (“Waiting for shared-pool capacity”, or “can never fit on the shared pool”).

Resuming a paused environment needs room too. Its containers count against your pool again the moment it starts, so a resume with no room is refused and the environment stays paused. If builds or CI jobs in flight hold the room, try again once they finish; if running environments alone fill it, pause one first.

Out of room? Pause or destroy environments you don't need, lower the containers' limits, or ask an administrator to raise your ceilings.

Resource add-ons

If a tier fits but you need a bit more compute, you don't have to jump to the next plan. Paid tiers support resource add-ons: each add-on raises your account ceilings by +1 vCPU and +2 GB vRAM. There is no cap. Add-ons apply to your total account quota — the per-container limits above are unchanged.

How the scheduler picks nodes

  1. Filter — exclude nodes without enough free capacity for the project's requested resources.
  2. Score — prefer nodes with cache affinity (built this project before) and geographic proximity.
  3. Reserve — capacity is reserved for the environment's lifetime, so accurate limits matter. Requesting 4 vCPU when your app uses 0.5 wastes capacity others could use.

Check your quota via API

curl https://api.test.buildingswell.com/quotas/me \
  -H "Authorization: Bearer nxs_your_api_key"

Response (trimmed). buildResourcePool is your vCPU/vRAM ceiling plus any private nodes you own; buildResourceUsage is what your environments, builds and CI jobs hold right now, counted as above. sharedPoolCeiling is your plan's ceiling alone and sharedPoolUsage the part of your usage on the shared pool, which has to fit it. Without private nodes the two pairs match:

{
  "quota": {
    "limits": {
      "maxProjects": 10,
      "maxConcurrentActive": 15,
      "maxCpuCoresTotal": 16,
      "maxMemoryMbTotal": 24576,
      "maxBuildMinutesPerMonth": 5000,
      "maxConcurrentBuilds": null
    },
    "usage": {
      "usedConcurrentActive": 6,
      "usedBuildMinutesThisMonth": 1240
    },
    "buildResourcePool": { "cpuCores": 16, "memoryMb": 24576 },
    "buildResourceUsage": { "cpuCores": 7.5, "memoryMb": 5120 },
    "sharedPoolCeiling": { "cpuCores": 16, "memoryMb": 24576 },
    "sharedPoolUsage": { "cpuCores": 7.5, "memoryMb": 5120 }
  }
}