diff --git a/README.md b/README.md index ea1d66d0f0..c3188335f6 100644 --- a/README.md +++ b/README.md @@ -208,14 +208,14 @@ docker compose up -d Go to http://localhost - default credentials: `admin@windmill.dev` / `changeme` > [!NOTE] -> To run `# docker` scripts (bash scripts with the `# docker` annotation), add a -> dedicated worker group with the rootless **podman** container runtime: each docker -> job runs in its own ephemeral rootless podman, torn down with the job — no -> privileged daemon and no host Docker socket, with your scripts unchanged. Use a -> `*-full` image (ships podman), set `CONTAINER_RUNTIME=podman`, and tag your docker -> scripts to route them to that group. See the commented `windmill_worker_docker` -> service in [docker-compose.yml](./docker-compose.yml), or the **Container runtime** -> toggle on a worker group in the UI (Workers → worker group config). +> To run `# docker` scripts (bash scripts with the `# docker` annotation): on a worker +> with **no Docker daemon provided** (no `DOCKER_HOST`, no mounted `/var/run/docker.sock`), +> Windmill automatically runs each docker job in its own ephemeral **rootless podman**, +> torn down with the job — no privileged daemon, no host socket, scripts unchanged. Just +> use a `*-full` image (ships podman) and tag your docker scripts to a dedicated worker +> group; see the commented `windmill_worker_docker` service in +> [docker-compose.yml](./docker-compose.yml). To use an external/host Docker daemon +> instead (legacy), provide `DOCKER_HOST` or mount `/var/run/docker.sock`. **Using an external database**: Set `DATABASE_URL` in `.env` to point to your managed Postgres (AWS RDS, GCP Cloud SQL, Azure, Neon, etc.) and set db replicas to 0. diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 14403533a7..13f52037e7 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -315,9 +315,6 @@ pub async fn initial_load( additional_python_paths: None, pip_local_dependencies: None, native_mode, - container_runtime: std::env::var("CONTAINER_RUNTIME") - .ok() - .filter(|x| !x.is_empty()), })); } } diff --git a/backend/windmill-api-configs/src/lib.rs b/backend/windmill-api-configs/src/lib.rs index 769ceda18b..9102691125 100644 --- a/backend/windmill-api-configs/src/lib.rs +++ b/backend/windmill-api-configs/src/lib.rs @@ -143,14 +143,12 @@ async fn update_config( #[cfg(not(feature = "enterprise"))] let config = if name.starts_with("worker__") { - // In CE, only allow setting worker_tags, cache_clear, init_bash, native_mode, - // and container_runtime + // In CE, only allow setting worker_tags, cache_clear, init_bash, and native_mode serde_json::json!({ "worker_tags": config.get("worker_tags"), "cache_clear": config.get("cache_clear"), "init_bash": config.get("init_bash"), - "native_mode": config.get("native_mode"), - "container_runtime": config.get("container_runtime") + "native_mode": config.get("native_mode") }) } else { config diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index 20ffe6921e..92ebf08477 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -234,7 +234,6 @@ lazy_static::lazy_static! { pip_local_dependencies: Default::default(), env_vars: Default::default(), native_mode: false, - container_runtime: None, }); pub static ref WORKER_PULL_QUERIES: arc_swap::ArcSwap> = arc_swap::ArcSwap::from_pointee(vec![]); @@ -1961,10 +1960,6 @@ pub async fn load_worker_config( .or_else(|| load_additional_python_paths_from_env()), env_vars: resolved_env_vars, native_mode, - container_runtime: config - .container_runtime - .or_else(|| std::env::var("CONTAINER_RUNTIME").ok()) - .and_then(|x| if x.is_empty() { None } else { Some(x) }), }) } @@ -2054,11 +2049,6 @@ pub struct WorkerConfigOpt { pub env_vars_static: Option>, pub env_vars_allowlist: Option>, pub native_mode: Option, - /// Container runtime for docker-mode jobs on this worker group. When set to - /// "podman", the worker starts a rootless podman service and points - /// DOCKER_HOST at it (see start_container_runtime). None = no managed runtime - /// (legacy dind/host-socket via DOCKER_HOST/socket still works if present). - pub container_runtime: Option, } impl Default for WorkerConfigOpt { @@ -2077,7 +2067,6 @@ impl Default for WorkerConfigOpt { env_vars_static: Default::default(), env_vars_allowlist: Default::default(), native_mode: Default::default(), - container_runtime: Default::default(), } } } @@ -2096,13 +2085,12 @@ pub struct WorkerConfig { pub pip_local_dependencies: Option>, pub env_vars: HashMap, pub native_mode: bool, - pub container_runtime: Option, } impl std::fmt::Debug for WorkerConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "WorkerConfig {{ worker_tags: {:?}, priority_tags_sorted: {:?}, dedicated_worker: {:?}, dedicated_workers: {:?}, init_bash: {:?}, periodic_script_bash: {:?}, periodic_script_interval_seconds: {:?}, cache_clear: {:?}, additional_python_paths: {:?}, pip_local_dependencies: {:?}, env_vars: {:?}, native_mode: {:?}, container_runtime: {:?} }}", - self.worker_tags, self.priority_tags_sorted, self.dedicated_worker, self.dedicated_workers, self.init_bash, self.periodic_script_bash, self.periodic_script_interval_seconds, self.cache_clear, self.additional_python_paths, self.pip_local_dependencies, self.env_vars.iter().map(|(k, v)| format!("{}: {}{} ({} chars)", k, &v[..3.min(v.len())], "***", v.len())).collect::>().join(", "), self.native_mode, self.container_runtime) + write!(f, "WorkerConfig {{ worker_tags: {:?}, priority_tags_sorted: {:?}, dedicated_worker: {:?}, dedicated_workers: {:?}, init_bash: {:?}, periodic_script_bash: {:?}, periodic_script_interval_seconds: {:?}, cache_clear: {:?}, additional_python_paths: {:?}, pip_local_dependencies: {:?}, env_vars: {:?}, native_mode: {:?} }}", + self.worker_tags, self.priority_tags_sorted, self.dedicated_worker, self.dedicated_workers, self.init_bash, self.periodic_script_bash, self.periodic_script_interval_seconds, self.cache_clear, self.additional_python_paths, self.pip_local_dependencies, self.env_vars.iter().map(|(k, v)| format!("{}: {}{} ({} chars)", k, &v[..3.min(v.len())], "***", v.len())).collect::>().join(", "), self.native_mode) } } diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index 4fdbc8f840..e809d87164 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -96,27 +96,6 @@ pub async fn handle_bash_job( let mut logs1 = "\n\n--- BASH CODE EXECUTION ---\n".to_string(); if annotation.docker { logs1.push_str("docker mode\n"); - // If neither DOCKER_HOST nor the host socket is available, the docker CLI - // in the script would fail with a generic "cannot connect to the Docker - // daemon" error. Surface a Windmill-specific hint instead. Skipped when - // container_runtime=podman is set: a per-job rootless podman is started - // below and provides the daemon. - if std::env::var("DOCKER_HOST").is_err() - && !std::path::Path::new("/var/run/docker.sock").exists() - && windmill_common::worker::WORKER_CONFIG - .load() - .container_runtime - .as_deref() - != Some("podman") - { - logs1.push_str( - "WARNING: docker mode is set but no Docker daemon is reachable from this worker \ - (DOCKER_HOST is unset and /var/run/docker.sock is not mounted). Give this worker \ - Docker access: with docker-compose, enable the dind sidecar (`docker compose \ - --profile dind up -d` and uncomment DOCKER_HOST in windmill_worker); with the Helm \ - chart, set `exposeHostDocker: true`; otherwise set DOCKER_HOST or mount a Docker socket.\n", - ); - } } if annotation.sandbox { logs1.push_str("sandbox mode (nsjail)\n"); @@ -214,20 +193,18 @@ exit $exit_status // Use nsjail if globally enabled OR if script has #sandbox annotation let nsjail = (is_sandboxing_enabled() || annotation.sandbox) && is_regular_job; - // Every docker job gets its OWN ephemeral rootless podman instance (started here, - // torn down when this guard drops at the end of the function) so no container it - // spawns can outlive the job, in any sandbox mode. Gated strictly on - // container_runtime=podman: legacy docker access (an externally-provided - // DOCKER_HOST or a mounted /var/run/docker.sock, without container_runtime=podman) - // is left exactly as before. + // Docker runtime selection: if a Docker daemon is already provided — DOCKER_HOST + // set, or the host socket mounted at /var/run/docker.sock — use it (backwards + // compatible, unchanged). Otherwise start a per-job rootless podman instance for + // this docker job (its own ephemeral daemon, torn down when this guard drops at + // the end of the function, so no container it spawns can outlive the job, in any + // sandbox mode). Requires podman in the image (the *-full images) — else + // start_per_job_podman returns a clear error. #[cfg(feature = "dind")] - let per_job_podman: Option = if annotation.docker - && windmill_common::worker::WORKER_CONFIG - .load() - .container_runtime - .as_deref() - == Some("podman") - { + let docker_daemon_provided = std::env::var("DOCKER_HOST").is_ok() + || std::path::Path::new("/var/run/docker.sock").exists(); + #[cfg(feature = "dind")] + let per_job_podman: Option = if annotation.docker && !docker_daemon_provided { Some(start_per_job_podman(job_dir).await?) } else { None @@ -254,7 +231,7 @@ exit $exit_status }; // Forward DOCKER_HOST to the bash script in docker mode: the per-job podman - // socket when container_runtime=podman, else the legacy DOCKER_HOST / docker socket. + // socket when one was started, else the provided DOCKER_HOST / docker socket. let docker_envs: Vec<(&str, String)> = if annotation.docker { if let Some(dh) = &docker_host_for_script { vec![("DOCKER_HOST", dh.clone())] @@ -543,8 +520,9 @@ async fn start_per_job_podman(job_dir: &str) -> Result { .spawn() .map_err(|e| { Error::ExecutionErr(format!( - "container_runtime=podman but failed to start the per-job podman service: {e}. \ - Use a windmill *-full image (ships podman)." + "no Docker daemon provided (DOCKER_HOST/socket) and failed to start the per-job \ + podman runtime: {e}. Use a windmill *-full image (ships podman), or provide a \ + Docker daemon via DOCKER_HOST or a mounted /var/run/docker.sock." )) })?; // Wait (up to ~10s) for the rootless podman service socket to appear. diff --git a/docker-compose.yml b/docker-compose.yml index c3242802be..a58e5172df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -106,7 +106,9 @@ services: # - MODE=worker # - WORKER_GROUP=docker # - WORKER_TAGS=docker # only serve jobs tagged "docker" (set that tag on your docker scripts) - # - CONTAINER_RUNTIME=podman + # # No DOCKER_HOST / docker socket is provided, so docker jobs automatically use + # # the bundled rootless podman (per job). Provide a DOCKER_HOST or mount + # # /var/run/docker.sock instead to use an external/host Docker daemon (legacy). # depends_on: # db: # condition: service_healthy diff --git a/frontend/src/lib/components/WorkerGroup.svelte b/frontend/src/lib/components/WorkerGroup.svelte index 9b63be84e9..3706fa5f42 100644 --- a/frontend/src/lib/components/WorkerGroup.svelte +++ b/frontend/src/lib/components/WorkerGroup.svelte @@ -100,7 +100,6 @@ min_alive_workers_alert_threshold?: number autoscaling?: AutoscalingConfig native_mode?: boolean - container_runtime?: string } = $state({}) function loadNConfig() { @@ -206,7 +205,6 @@ periodic_script_bash?: string periodic_script_interval_seconds?: number native_mode?: boolean - container_runtime?: string } activeWorkers: number customTags: string[] | undefined @@ -643,40 +641,6 @@ {/if} - {#if nconfig !== undefined} -
- - {/if} - {#if nconfig !== undefined}