Commit 74b01ee3dc

74b01ee3dcfb2bf35cebcc49d605ccbd2994b64b

parent: 270e3df2d5

Verified · cmc ci/build: success ci/test: failure

cmc <hello@cleberg.net> · 2026-09-06 23:38 UTC

runner: podman with the cgroupfs manager, not systemd

The runner is a system service, so there is no user session slice for
podman's systemd cgroup manager to create a scope under and crun fails
creating the container's cgroup. The service's own cgroup is delegated,
which is what cgroupfs needs.

Ref #144

Layout: unified · split

cmd/gitbay-runner/env_test.go +16
@@ -88,3 +88,19 @@ func TestStepEnvHomeIsNotTheWorkspace(t *testing.T) {
8888 }
8989 }
9090}
91
92// podman runs from a system service, where the systemd cgroup manager
93// has no user slice to work in. Every invocation must say so, or crun
94// fails creating the container's scope (#144).
95func TestPodmanUsesCgroupfs(t *testing.T) {
96 got := podmanGlobal()
97 found := false
98 for _, f := range got {
99 if f == "--cgroup-manager=cgroupfs" {
100 found = true
101 }
102 }
103 if !found {
104 t.Errorf("podmanGlobal() = %v, missing the cgroupfs manager", got)
105 }
106}
cmd/gitbay-runner/isolate.go +17 −4
@@ -104,13 +104,13 @@ func (r *runner) runStepsPodman(j job, dir string, env []string, sink io.Writer,
104104 name := fmt.Sprintf("gitbay-build-%d", j.ID)
105105 // --rm so a container cannot outlive its build; the explicit rm below
106106 // covers the case where the daemon-less run itself fails.
107 start := exec.Command(podman, "run", "--detach", "--rm",
107 start := exec.Command(podman, append(podmanGlobal(), "run", "--detach", "--rm",
108108 "--name", name,
109109 "--env-file", envFile,
110110 "--volume", dir+":/workspace:rw",
111111 "--workdir", "/workspace",
112112 "--entrypoint", "sh",
113 image, "-c", "sleep infinity")
113 image, "-c", "sleep infinity")...)
114114 start.Env = []string{"PATH=" + os.Getenv("PATH"), "HOME=" + r.podmanHome()}
115115 if out, err := start.CombinedOutput(); err != nil {
116116 // A pull failure lands here. Fail the build with what podman
@@ -118,11 +118,11 @@ func (r *runner) runStepsPodman(j job, dir string, env []string, sink io.Writer,
118118 fmt.Fprintf(sink, "starting the build container from %s failed:\n%s\n", image, strings.TrimSpace(string(out)))
119119 return false
120120 }
121 defer exec.Command(podman, "rm", "--force", name).Run()
121 defer exec.Command(podman, append(podmanGlobal(), "rm", "--force", name)...).Run()
122122
123123 for _, step := range j.Steps {
124124 fmt.Fprintf(sink, "$ %s\n", step)
125 cmd := exec.Command(podman, "exec", "--workdir", "/workspace", name, "sh", "-c", step)
125 cmd := exec.Command(podman, append(podmanGlobal(), "exec", "--workdir", "/workspace", name, "sh", "-c", step)...)
126126 cmd.Env = []string{"PATH=" + os.Getenv("PATH"), "HOME=" + r.podmanHome()}
127127 cmd.Stdout, cmd.Stderr = sink, sink
128128 if ok, why := runStep(cmd, deadline); !ok {
@@ -133,6 +133,19 @@ func (r *runner) runStepsPodman(j job, dir string, env []string, sink io.Writer,
133133 return true
134134}
135135
136// podmanGlobal are the flags every podman invocation needs, before the
137// subcommand.
138//
139// The cgroup manager is cgroupfs, not systemd: the runner is a *system*
140// service, so there is no user session and no user@<uid>.service slice
141// for podman to create a scope under. With the systemd manager crun
142// fails with "create directory .../libpod-<id>.scope/container: No such
143// file or directory". The service's own cgroup is delegated
144// (Delegate=yes in the drop-in), which is what cgroupfs needs (#144).
145func podmanGlobal() []string {
146 return []string{"--cgroup-manager=cgroupfs"}
147}
148
136149// podmanHome is where podman keeps its own storage: the runner's home,
137150// not a build's. The container store is the runner's business, and a
138151// build never sees this path.