From 82226aa6dd8526eb537db287f1d25a5c369a639c Mon Sep 17 00:00:00 2001 From: Andy Jeffries Date: Tue, 7 Jul 2026 10:36:46 +0100 Subject: [PATCH] Only ensure the run directory on the primary host Every command that takes the deploy lock first runs `mkdir -p` of the run directory (`.kamal`) on *all* configured hosts, via `ensure_run_directory`. But the run directory only needs to exist where something actually reads or writes it, and the only consumer that depends on this preamble is the deploy lock, which is acquired and released solely on the primary host. This is most visible on single-host operations such as `kamal accessory reboot `: rebooting an accessory pinned to one host still opens an SSH connection to every server in the deploy just to create an empty `.kamal` directory it never uses. `ensure_run_directory` has exactly two callers -- `acquire_lock` and `Lock#acquire` -- and in both it is only a precondition for the lock. The lock's directory is created with a plain, non-`-p` `mkdir` (an atomic lock primitive), so its parent must pre-exist; that is the whole reason for the preamble. Since the lock lives only on the primary host, the run directory only needs pre-creating there. Everything else under the run directory self-creates with `mkdir -p` on the hosts where it runs: env files and secrets (`app`/`accessory.ensure_env_directory`), proxy config, and the audit log (`Auditor#record` prepends its own `mkdir -p`). None of them rely on this preamble, so narrowing it to the primary host changes no other behaviour. --- lib/kamal/cli/base.rb | 2 +- test/cli/lock_test.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/kamal/cli/base.rb b/lib/kamal/cli/base.rb index 9d99f759e..84863c4e7 100644 --- a/lib/kamal/cli/base.rb +++ b/lib/kamal/cli/base.rb @@ -293,7 +293,7 @@ def reset_invocation(cli_class) end def ensure_run_directory - on(KAMAL.hosts) do + on(KAMAL.primary_host) do execute(*KAMAL.server.ensure_run_directory) end end diff --git a/test/cli/lock_test.rb b/test/cli/lock_test.rb index e3db32018..31c1f9bb9 100644 --- a/test/cli/lock_test.rb +++ b/test/cli/lock_test.rb @@ -13,6 +13,15 @@ class CliLockTest < CliTestCase end end + test "acquire ensures the run directory only on the primary host" do + run_command("acquire", "-m", "hello").tap do |output| + assert_match "Running /usr/bin/env mkdir -p .kamal on 1.1.1.1", output + assert_no_match(/mkdir -p \.kamal on 1\.1\.1\.2/, output) + assert_no_match(/mkdir -p \.kamal on 1\.1\.1\.3/, output) + assert_no_match(/mkdir -p \.kamal on 1\.1\.1\.4/, output) + end + end + test "status when there is no deploy lock" do SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_debug) .raises(RuntimeError, "cat: .kamal/lock-app/details: No such file or directory")