Skip to content

feat: allow setting a cgroup parent for docker builds - #5394

Open
cl77 wants to merge 3 commits into
Dokploy:canaryfrom
cl77:feat/build-cgroup-parent
Open

cl77 wants to merge 3 commits into
Dokploy:canaryfrom
cl77:feat/build-cgroup-parent

Conversation

@cl77

@cl77 cl77 commented Sep 9, 2026 •

Copy link
Copy Markdown

What is this PR about?

Builds currently run unconstrained on the host. A single Next.js build saturates every core (Next.js scales its workers to os.cpus()), and with the systemd cgroup driver every BuildKit RUN step lands as a docker-<id>.scope directly in system.slice, indistinguishable from the running services. There is no common cgroup to put a quota on, so builds degrade production latency on the same machine. Related reports: #2914, #4461.

Docker already has the hook for this: docker build --cgroup-parent. This PR exposes it as a setting.

  • New nullable column buildCgroupParent on webServerSettings (local builds) and server (remote/build servers), with a migration.
  • getBuildCommand resolves the value for the host that runs the build (buildServerId || serverId) and passes --cgroup-parent to docker build for Dockerfile and static builds.
  • Two mutations mirroring the concurrent-builds ones: settings.updateBuildCgroupParent and server.updateBuildCgroupParent.
  • New "Build Resource Limits" card on Settings > Deployments, one input per server, next to Concurrent Builds.
  • Input is validated to [A-Za-z0-9_.:/-] and shell-quoted; empty clears the setting.

Typical use: enter an absolute cgroupfs path such as /builds. BuildKit then creates its step cgroups as /sys/fs/cgroup/builds/buildkit/<id>, and a limit written to /sys/fs/cgroup/builds/cpu.max (e.g. 2400000 100000 for 24 cores) or memory.max caps all builds on that host together, while running services stay unaffected. Verified on Docker 29 with the systemd cgroup driver. systemd slice notation (builds.slice:docker:) does not work with the built-in BuildKit executor: it runs runc with the cgroupfs manager, so the string is created as a literal directory under the daemon's cgroup instead of a scope under the slice.

Not covered: Nixpacks (no --cgroup-parent in the nixpacks CLI, only --cpu-quota/--memory), Heroku/Paketo (pack), Railpack (own buildx builder container) and Compose builds. The setting is a no-op for those. Preview deployments resolve the value for the deploy server, since they always build there. Not shown in cloud.

Verification: pnpm typecheck, biome on the changed files, and the vitest suite (new build-cgroup-parent.test.ts plus the existing suite). The four failures in application.real.test.ts fail identically on canary in my sandbox because network git clones are blocked there. openapi.json was not regenerated on purpose: generate:openapi currently produces a ~29k-line unrelated diff against the checked-in file.

Checklist

Issues related (if applicable)

Related to #2914 and #4461 (no full fix for the stuck-job part of #4461).

https://claude.ai/code/session_018Sb7ATyKb8pduVLBUZH4Ac

RetriggerConfidence Score: 5/5

The PR appears safe to merge after addressing the non-blocking unused import.

Summary

  • Adds persisted settings and validated API mutations for local and remote build hosts.
  • Passes the selected host’s cgroup parent to docker build.
  • Adds deployment-settings controls and documentation for configuring cgroupfs paths.
  • Correctly excludes unsupported Nixpacks, Buildpacks, Railpack, and Compose workflows.
  • Aligns preview-build cgroup resolution with the server that actually executes the build.

Reviews (2) · Last reviewed commit: "docs: recommend cgroupfs paths for the b..."

Adds a per-server (and local web server) setting that is passed to
docker build --cgroup-parent, so hosts can cap the CPU/memory that build
steps may take away from running services, e.g. via a systemd slice with
CPUQuota. Applies to Dockerfile, static and Nixpacks static builds.

Claude-Session: https://claude.ai/code/session_018Sb7ATyKb8pduVLBUZH4Ac
@cl77
cl77 requested a review from Siumauricio as a code owner September 9, 2026 07:52
}
docker rm ${buildContainerId}
${getStaticCommand(application)}
${getStaticCommand(application, options)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Nixpacks compilation remains unconstrained

For a Nixpacks application with publishDirectory, the resource-intensive source compilation runs in the earlier nixpacks build command. The cgroup option is forwarded only to the final Nginx packaging build, so compilation remains unconstrained and the setting does not provide the advertised resource isolation for Nixpacks-static builds.

Knowledge Base Used: Build and Compose workflows

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. nixpacks has no --cgroup-parent (only --cpu-quota/--memory), so limiting only the nginx packaging step was misleading. Dropped the pass-through for Nixpacks in 91cbfdd and narrowed the UI text and PR description to Dockerfile and static builds. Wiring nixpacks --cpu-quota/--memory could be a follow-up.

Comment on lines +50 to +52
const cgroupParent = await resolveBuildCgroupParent(
application.buildServerId || application.serverId,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Preview builds use wrong settings

When an application has a dedicated build server, preview builds still execute on application.serverId, but this code resolves the cgroup parent from buildServerId. The preview can therefore use a cgroup belonging to another host, causing Docker to reject the build if that cgroup is absent or leaving the actual build host's configured limit unused.

Knowledge Base Used:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. getBuildCommand now takes the executing server explicitly (buildServerId || serverId by default) and both preview paths pass application.serverId, matching where the command actually runs. Fixed in 91cbfdd.

Christian Leo added 2 commits September 9, 2026 09:57
…tual build host

Nixpacks has no --cgroup-parent, so passing it only to the final nginx
packaging build was misleading; drop it there and document the scope.
Preview deployments always build on the deploy server, so resolve the
cgroup parent for that host instead of the assigned build server.

Claude-Session: https://claude.ai/code/session_018Sb7ATyKb8pduVLBUZH4Ac
The built-in BuildKit executor runs runc with the cgroupfs manager, so
systemd slice notation is created as a literal directory instead of a
scope under the slice. An absolute path such as /builds ends up as
/sys/fs/cgroup/builds/buildkit/<id>, where a cpu.max on /builds caps all
builds together (verified on Docker with the systemd cgroup driver).

Claude-Session: https://claude.ai/code/session_018Sb7ATyKb8pduVLBUZH4Ac
@cl77

cl77 commented Sep 9, 2026

Copy link
Copy Markdown
Author

Corrected the usage guidance in cafacb1: the built-in BuildKit executor uses runc's cgroupfs manager, so systemd slice notation is created as a literal directory and does not inherit a slice's limits. An absolute cgroupfs path such as /builds works as intended (step cgroups end up as /sys/fs/cgroup/builds/buildkit/, verified on Docker 29 with the systemd cgroup driver). Placeholder, UI hint and PR description now recommend that form.

import { prepareEnvironmentVariablesForShell } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
import type { ApplicationNested } from ".";
import type { DockerBuildOptions } from "./docker-file";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused Nixpacks type import

Removing the Nixpacks cgroup-options parameter left DockerBuildOptions unused. The repository config treats unused imports as errors, so pnpm format-and-lint reports this file until the import is removed.

Suggested change
import type { DockerBuildOptions } from "./docker-file";

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant