Skip to content

Commit f6682d3

Browse files
committed
feat: cap controller container resources
Add cpus, mem_limit, memswap_limit, and pids_limit defaults to the docker-git-api controller in docker-compose.yml and docker-compose.api.yml. Each value is parameterized via a DOCKER_GIT_CONTROLLER_* env var so operators can tune them. Per-project containers already resolve a default 30% CPU/RAM cap through resolveComposeResourceLimits, but the privileged controller that orchestrates them had no caps and could consume the entire host. This closes that gap so the whole system's resource footprint stays bounded. Closes #260
1 parent 1327326 commit f6682d3

6 files changed

Lines changed: 72 additions & 1 deletion

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@prover-coder-ai/docker-git": patch
3+
---
4+
5+
feat: cap controller container CPU, memory, and PID consumption
6+
7+
Adds default `cpus`, `mem_limit`, `memswap_limit`, and `pids_limit` to the
8+
`docker-git-api` controller in `docker-compose.yml` and
9+
`docker-compose.api.yml`. Each value is parameterized so operators can
10+
override it via `DOCKER_GIT_CONTROLLER_CPUS`, `DOCKER_GIT_CONTROLLER_MEMORY`,
11+
and `DOCKER_GIT_CONTROLLER_PIDS`. Defaults: 2 CPUs, 4 GiB RAM/swap, 4096 PIDs.
12+
This complements the existing per-project caps so a runaway controller
13+
cannot consume the entire host.

.gitkeep

Lines changed: 0 additions & 1 deletion
This file was deleted.

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,21 @@ When the CLI cannot acquire Docker access it now prints a message that
113113
names the specific failure mode, restates the host-Docker contract, and
114114
lists remediation steps for that exact mode. Implementation lives in
115115
`packages/app/src/docker-git/controller-docker-diagnostics.ts`.
116+
117+
## Resource limits
118+
119+
`docker-git` caps host resource consumption at two layers so a runaway
120+
project (or the controller itself) cannot consume the entire system.
121+
122+
- **Per-project containers** ship with a default limit of `30%` CPU and
123+
`30%` RAM (resolved against the host on `apply`). Override via
124+
`--cpu` / `--ram` (or per-project `docker-git.json`).
125+
- **Controller container** (`docker-git-api`) is capped in
126+
`docker-compose.yml` and `docker-compose.api.yml`. Override via
127+
environment variables before `./ctl up`:
128+
129+
| Variable | Default | Purpose |
130+
| ------------------------------ | ------- | ------------------------------------ |
131+
| `DOCKER_GIT_CONTROLLER_CPUS` | `2.0` | Maximum CPU cores for the controller |
132+
| `DOCKER_GIT_CONTROLLER_MEMORY` | `4g` | Memory + swap cap (matched values) |
133+
| `DOCKER_GIT_CONTROLLER_PIDS` | `4096` | Maximum PIDs inside the controller |

docker-compose.api.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ services:
3737
cgroup: host
3838
init: true
3939
restart: unless-stopped
40+
cpus: ${DOCKER_GIT_CONTROLLER_CPUS:-2.0}
41+
mem_limit: ${DOCKER_GIT_CONTROLLER_MEMORY:-4g}
42+
memswap_limit: ${DOCKER_GIT_CONTROLLER_MEMORY:-4g}
43+
pids_limit: ${DOCKER_GIT_CONTROLLER_PIDS:-4096}
4044

4145
volumes:
4246
docker_git_projects:

docker-compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ services:
3939
cgroup: host
4040
init: true
4141
restart: unless-stopped
42+
cpus: ${DOCKER_GIT_CONTROLLER_CPUS:-2.0}
43+
mem_limit: ${DOCKER_GIT_CONTROLLER_MEMORY:-4g}
44+
memswap_limit: ${DOCKER_GIT_CONTROLLER_MEMORY:-4g}
45+
pids_limit: ${DOCKER_GIT_CONTROLLER_PIDS:-4096}
4246

4347
volumes:
4448
docker_git_projects:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { readFileSync } from "node:fs"
2+
import path from "node:path"
3+
import { fileURLToPath } from "node:url"
4+
5+
import { describe, expect, it } from "@effect/vitest"
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
8+
const repoRoot = path.resolve(__dirname, "..", "..", "..", "..")
9+
10+
const readComposeFile = (relativePath: string): string => readFileSync(path.join(repoRoot, relativePath), "utf8")
11+
12+
const composeFiles = ["docker-compose.yml", "docker-compose.api.yml"] as const
13+
14+
describe("controller compose resource limits", () => {
15+
for (const composeFile of composeFiles) {
16+
describe(composeFile, () => {
17+
const contents = readComposeFile(composeFile)
18+
19+
it("caps controller CPU usage", () => {
20+
expect(contents).toMatch(/cpus: \$\{DOCKER_GIT_CONTROLLER_CPUS:-\d+(?:\.\d+)?\}/u)
21+
})
22+
23+
it("caps controller memory and swap together", () => {
24+
expect(contents).toMatch(/mem_limit: \$\{DOCKER_GIT_CONTROLLER_MEMORY:-\d+[a-zA-Z]+\}/u)
25+
expect(contents).toMatch(/memswap_limit: \$\{DOCKER_GIT_CONTROLLER_MEMORY:-\d+[a-zA-Z]+\}/u)
26+
})
27+
28+
it("caps controller PIDs to prevent fork bombs", () => {
29+
expect(contents).toMatch(/pids_limit: \$\{DOCKER_GIT_CONTROLLER_PIDS:-\d+\}/u)
30+
})
31+
})
32+
}
33+
})

0 commit comments

Comments
 (0)