enhance(config): add container.sweep option (#1275)

The Docker proxy labels every container, network and volume a job creates and removes them when the job ends, which breaks jobs that deploy long-running services to the runner's own Docker daemon. Document that behavior and add `container.sweep` (default `true`) so operators who deploy to the same daemon can opt out. Kept resources stay labeled with `com.gitea.runner.job`.

Fixes: https://gitea.com/gitea/runner/issues/1243
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1275
Reviewed-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
This commit is contained in:
bircniandsilverwind authored and silverwind committed 2026-10-08 17:53:10 +00:00
1 parent f963e9cfe8
commit 179e435cd0
7 files changed
+13 -6

No files matched your search

+1 -1
View File
@@ -725,7 +725,7 @@ func (rc *RunContext) cleanupJobResources(networkName string, createAndDeleteNet
logger.Infof("Cleaning up services for job %s", rc.JobName)
errs = append(errs, rc.stopServiceContainers()(ctx))
}
if !common.Dryrun(ctx) && (preclean || rc.hadDockerProxy) {
if !common.Dryrun(ctx) && !rc.Config.NoSweep && (preclean || rc.hadDockerProxy) {
errs = append(errs, container.RemoveDockerJobResources(ctx, rc.jobContainerName()))
}
if preclean || rc.deferVolumeCleanup == nil {
+5 -4
View File
@@ -778,9 +778,10 @@ func TestRemoveDockerActionImagesByTag(t *testing.T) {
func TestCleanupJobResourcesContinuesAfterFailure(t *testing.T) {
t.Setenv("TMPDIR", "/tmp")
proxyDir := t.TempDir()
for _, name := range []string{"synchronous", "proxy", "closed proxy", "deferred", "preclean deferred", "canceled"} {
for _, name := range []string{"synchronous", "proxy", "closed proxy", "kept proxy", "deferred", "preclean deferred", "canceled"} {
t.Run(name, func(t *testing.T) {
proxy, preclean, deferred := strings.HasSuffix(name, "proxy"), strings.HasPrefix(name, "preclean"), strings.HasSuffix(name, "deferred")
removesLabelled := (proxy || preclean) && name != "kept proxy"
if proxy && runtime.GOOS == "windows" {
t.Skip("Unix socket ownership is unavailable on Windows")
}
@@ -790,7 +791,7 @@ func TestCleanupJobResourcesContinuesAfterFailure(t *testing.T) {
service.On("Remove").Return(func(context.Context) error { return removeError }).Once()
service.On("Close").Return(func(context.Context) error { return closeError }).Once()
rc := &RunContext{
Config: &Config{CacheContainer: "cache-container"},
Config: &Config{CacheContainer: "cache-container", NoSweep: name == "kept proxy"},
Run: &model.Run{Workflow: &model.Workflow{Name: "wf"}, JobID: "job"},
JobContainer: job,
serviceContainers: []*serviceContainer{{name: "svc", container: service}},
@@ -846,8 +847,8 @@ func TestCleanupJobResourcesContinuesAfterFailure(t *testing.T) {
}
assert.Equal(t, 2, volumeRemovals)
}
assert.Equal(t, proxy || preclean, strings.Contains(err.Error(), "labelled GET /containers/json"))
if proxy || preclean {
assert.Equal(t, removesLabelled, strings.Contains(err.Error(), "labelled GET /containers/json"))
if removesLabelled {
require.ErrorContains(t, err, "labelled GET /networks")
require.ErrorContains(t, err, "labelled GET /volumes")
}
+1
View File
@@ -87,6 +87,7 @@ type Config struct {
MaxParallel int // max parallel jobs to run across all workflows (0 = no limit, uses CPU count)
AllocatePTY bool // allocate a pseudo-TTY for each step's process
ServiceReadyTimeout time.Duration // how long a job waits for its service containers to report healthy (0 uses the default)
NoSweep bool // keep the containers, networks and volumes a job creates through its docker proxy
RunnerName string // name this runner registered with, reported as `runner.name`, defaults to the hostname
JobStartedHook string // script run inside the job environment before the job's first step; ACTIONS_RUNNER_HOOK_JOB_STARTED is read from Env when empty
JobCompletedHook string // script run inside the job environment after the job's last step; ACTIONS_RUNNER_HOOK_JOB_COMPLETED is read from Env when empty