feat(services): builder selection, resource reservations, static builder (C3)

services.builder picks the build strategy per service: auto detects from
repo contents (Dockerfile → railpack → nixpacks), explicit overrides force
a builder, and static builds assets in a node stage then serves them from
an nginx image (static_build_cmd/static_dir, port 80 default).

- cpu_reserve/memory_reserve → Docker CPU shares + MemoryReservation;
  cpu/memory stay hard caps (NanoCPUs/Memory). Empty string clears.
- app_settings.default_cpu/default_memory override service-create
  fallbacks; capacity_policy=block fails deploys whose requested memory
  exceeds node capacity (default warn logs via slog).
- Resource specs are validated — "512mb" fails instead of silently
  dropping the limit.
- Fixes: deployments.go hardcoded nixpacks (auto-detect was dead code);
  DockerfileBuilder passed the Dockerfile *path* as build-context content;
  BuildImage never drained the daemon stream so builds could abort and
  errors went unseen — stream is now consumed and errorDetail surfaced.
- CLI: --builder/--cpu-reserve/--memory-reserve/--static-cmd/--static-dir
  on create+update; MCP args on services create/update; UI Build &
  Resources card; OpenAPI + llms.txt + CLI.md.

Verified live: static build of a real repo → nginx image → deployed;
reserves reach HostConfig (shares=256, MemoryReservation=128Mi);
validation rejects bad specs; builder persists + clears.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-10-08 17:08:35 +02:00
1 parent e995c98d05
commit 912f6f5142
22 files changed
+798 -59

No files matched your search

+48
View File
@@ -5173,6 +5173,22 @@ components:
items:
type: string
description: Basic-auth usernames gating the route (hashes never returned)
builder:
type: string
enum: [auto, railpack, nixpacks, dockerfile, static]
description: Build strategy; auto detects from repo contents
cpu_reserve:
type: string
description: Soft CPU reservation (fraction, e.g. 0.25 → ~256 CPU shares)
memory_reserve:
type: string
description: Soft memory reservation (e.g. 128Mi)
static_build_cmd:
type: string
description: Build command for the static builder (default "npm ci && npm run build")
static_dir:
type: string
description: Output directory for the static builder (default dist)
public_url:
type: string
description: Computed public URL (published port or domain)
@@ -5248,6 +5264,22 @@ components:
items:
$ref: '#/components/schemas/ServiceVolume'
description: Volume/bind mounts applied to every replica
builder:
type: string
enum: [auto, railpack, nixpacks, dockerfile, static]
description: Build strategy (default auto)
cpu_reserve:
type: string
description: Soft CPU reservation (fraction, e.g. 0.25)
memory_reserve:
type: string
description: Soft memory reservation (e.g. 128Mi)
static_build_cmd:
type: string
description: Build command for the static builder
static_dir:
type: string
description: Output directory for the static builder (default dist)
UpdateServiceRequest:
type: object
@@ -5312,6 +5344,22 @@ components:
items:
$ref: '#/components/schemas/BasicAuthCred'
description: Replaces all basic-auth credentials; [] clears the gate
builder:
type: string
enum: [auto, railpack, nixpacks, dockerfile, static]
description: Build strategy
cpu_reserve:
type: string
description: Soft CPU reservation (empty string clears)
memory_reserve:
type: string
description: Soft memory reservation (empty string clears)
static_build_cmd:
type: string
description: Build command for the static builder (empty string clears)
static_dir:
type: string
description: Output directory for the static builder (empty string clears)
ServiceDomain:
type: object
+28
View File
@@ -115,6 +115,34 @@ Maintenance mode redirects all traffic to `<BASE_URL>/api/v1/maintenance`
taken offline with empty responses. Basic-auth credentials are stored
bcrypt-hashed (htpasswd) and gate every request.
## Build strategy and resources
Source-built services pick a builder per service:
```bash
containr services update <id> --builder railpack # auto|railpack|nixpacks|dockerfile|static
containr services update <id> --builder static --static-cmd "npm run build" --static-dir dist
```
`auto` detects from repo contents (Dockerfile → railpack → nixpacks).
`static` builds assets in a node stage and serves them from nginx —
`--static-cmd` runs inside the build stage (default
`npm ci && npm run build`), `--static-dir` is copied into the nginx image
(default `dist`, exposed on port 80).
Resource reservations complement the hard `cpu`/`memory` limits:
```bash
containr services update <id> --cpu-reserve 0.25 --memory-reserve 128Mi
```
`cpu`/`memory` are hard caps (NanoCPUs/Memory); `cpu_reserve`/
`memory_reserve` are soft hints (CPU shares, MemoryReservation). Empty
string clears. `app_settings.default_cpu`/`default_memory` change the
instance-level defaults used at service creation;
`app_settings.capacity_policy=block` fails deployments whose requested
memory exceeds node capacity (default `warn` logs only).
## MCP server
`containr-mcp` (`go build ./cmd/mcp`) exposes the same API as MCP tools