From bb6d793cb984cca3165cb36005a0675bd75b45a0 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 5 Nov 2026 13:50:46 +0000 Subject: [PATCH] fix: make container.sweep opt-in (#1389) Sweeping removed every container a job started through its Docker proxy, which silently tore down services that jobs deploy to the runner's own Docker daemon. Deploying from a runner on the target host is common, so `container.sweep` now defaults to `false` and CI-only runners can opt in with `container.sweep: true`. Fixes https://gitea.com/gitea/runner/issues/1385 Reviewed-on: https://gitea.com/gitea/runner/pulls/1389 Reviewed-by: bircni Co-authored-by: silverwind --- README.md | 2 +- act/runner/docker_proxy_integration_test.go | 1 + act/runner/run_context.go | 2 +- act/runner/run_context_test.go | 2 +- act/runner/runner.go | 2 +- internal/app/run/runner.go | 2 +- internal/pkg/config/config.example.yaml | 4 ++-- internal/pkg/config/config.go | 2 +- 8 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2b3a0a09..3da6adc3 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ volumes: - ${GITEA_DOCKER_WORKSPACE:-.}/data:/app/data ``` -In most Linux container jobs, the containers, networks and volumes a job creates are removed when it ends, `docker compose` services included. Set `container.sweep: false` if jobs deploy services to the runner's Docker daemon. +Set `container.sweep: true` to remove the containers, networks and volumes most Linux container jobs create when they end, `docker compose` services included. Leave it off if jobs deploy services to the runner's Docker daemon. ### Built-in actions (`uses: builtin:`) diff --git a/act/runner/docker_proxy_integration_test.go b/act/runner/docker_proxy_integration_test.go index f44f8459..8a0687da 100644 --- a/act/runner/docker_proxy_integration_test.go +++ b/act/runner/docker_proxy_integration_test.go @@ -49,6 +49,7 @@ func TestDockerProxyMountedJob(t *testing.T) { ContainerNamePrefix: resourceName, ContainerDaemonSocket: dockerClient.DaemonHost(), ContainerMaxLifetime: 2 * time.Minute, + Sweep: true, ContainerOptions: "-v /usr/local/bin/docker:/usr/local/bin/docker:ro -v /usr/local/libexec/docker/cli-plugins:/usr/local/libexec/docker/cli-plugins:ro", ValidVolumes: []string{"/usr/local/bin/docker", "/usr/local/libexec/docker/cli-plugins"}, Env: map[string]string{"PROXY_TEST_RESOURCE": resourceName, "PROXY_TEST_MODE": mode, "PROXY_TEST_IMAGE": baseImage, "COMPOSE_PROJECT_NAME": resourceName}, diff --git a/act/runner/run_context.go b/act/runner/run_context.go index cb4aa923..e038ab4c 100644 --- a/act/runner/run_context.go +++ b/act/runner/run_context.go @@ -810,7 +810,7 @@ func (rc *RunContext) cleanupJobResources(networkName string, createAndDeleteNet errs = append(errs, runAll(ctx, containers)) ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), jobCleanupTimeout) defer cancel() - if !common.Dryrun(ctx) && !rc.Config.NoSweep && (preclean || rc.hadDockerProxy) { + if !common.Dryrun(ctx) && rc.Config.Sweep && (preclean || rc.hadDockerProxy) { errs = append(errs, container.RemoveDockerJobResources(ctx, rc.jobContainerName())) // after the containers, whose endpoints would block removing the job networks } var rest []common.Executor diff --git a/act/runner/run_context_test.go b/act/runner/run_context_test.go index f76e069c..c81b3ec9 100644 --- a/act/runner/run_context_test.go +++ b/act/runner/run_context_test.go @@ -725,7 +725,7 @@ func TestCleanupJobResources(t *testing.T) { service.On("Remove").Return(removeAlongside(serviceRemoving, jobRemoving, removeError)).Once() service.On("Close").Return(func(context.Context) error { return closeError }).Once() rc := &RunContext{ - Config: &Config{CacheContainer: "cache-container", NoSweep: name == "kept proxy"}, + Config: &Config{CacheContainer: "cache-container", Sweep: name != "kept proxy"}, Run: &model.Run{Workflow: &model.Workflow{Name: "wf"}, JobID: "job"}, JobContainer: job, serviceContainers: []*serviceContainer{{name: "svc", container: service}}, diff --git a/act/runner/runner.go b/act/runner/runner.go index 824bd0a5..18e60476 100644 --- a/act/runner/runner.go +++ b/act/runner/runner.go @@ -91,7 +91,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 + Sweep bool // remove 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 diff --git a/internal/app/run/runner.go b/internal/app/run/runner.go index 0f267aca..55553d83 100644 --- a/internal/app/run/runner.go +++ b/internal/app/run/runner.go @@ -612,7 +612,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report. }, ContainerOptions: r.cfg.Container.Options, ServiceReadyTimeout: r.cfg.Container.ServiceReadyTimeout, - NoSweep: r.cfg.Container.Sweep != nil && !*r.cfg.Container.Sweep, + Sweep: r.cfg.Container.Sweep, ContainerDaemonSocket: r.cfg.Container.DockerHost, Privileged: r.cfg.Container.Privileged, DefaultActionInstance: r.getDefaultActionsURL(task), diff --git a/internal/pkg/config/config.example.yaml b/internal/pkg/config/config.example.yaml index 5fc3f149..39df3766 100644 --- a/internal/pkg/config/config.example.yaml +++ b/internal/pkg/config/config.example.yaml @@ -306,8 +306,8 @@ container: # healthy. A negative value (e.g. -1s) starts the steps without waiting. #service_ready_timeout: 5m # Remove the containers, networks and volumes a job creates through its Docker proxy when - # the job ends. Set to false when jobs deploy services to the runner's Docker daemon. - #sweep: true + # the job ends. Leave off when jobs deploy services to the runner's Docker daemon. + #sweep: false host: # The parent directory of a job's working directory. diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 4191c794..e6a39ade 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -190,7 +190,7 @@ type Container struct { DockerTimeout time.Duration `yaml:"docker_timeout"` // Timeout to wait for the docker daemon to be reachable, if docker is required by require_docker or runner BindWorkdir bool `yaml:"bind_workdir"` // BindWorkdir mounts the workspace from a host directory instead of a Docker volume. ServiceReadyTimeout time.Duration `yaml:"service_ready_timeout"` // ServiceReadyTimeout bounds how long a job waits for a service container that declares a healthcheck to report healthy. Negative disables waiting. - Sweep *bool `yaml:"sweep"` // Sweep removes the containers, networks and volumes a job creates through its Docker proxy when the job ends. Unset means enabled. + Sweep bool `yaml:"sweep"` // Sweep removes the containers, networks and volumes a job creates through its Docker proxy when the job ends. } // Values of Runner.ToolCacheMode: the runner mounts no tool cache, or one that every job reuses.