kamal 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +48 -39
  3. data/lib/kamal/cli/app.rb +57 -48
  4. data/lib/kamal/cli/base.rb +99 -11
  5. data/lib/kamal/cli/build.rb +9 -6
  6. data/lib/kamal/cli/lock.rb +5 -16
  7. data/lib/kamal/cli/main.rb +59 -53
  8. data/lib/kamal/cli/proxy.rb +9 -9
  9. data/lib/kamal/cli/prune.rb +3 -3
  10. data/lib/kamal/cli/server.rb +24 -15
  11. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +1 -1
  12. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +1 -1
  13. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
  14. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +1 -1
  15. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +1 -1
  16. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
  17. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +1 -1
  18. data/lib/kamal/cli/templates/secrets +4 -0
  19. data/lib/kamal/commander.rb +54 -1
  20. data/lib/kamal/commands/accessory.rb +2 -2
  21. data/lib/kamal/commands/app/logging.rb +1 -1
  22. data/lib/kamal/commands/app.rb +1 -1
  23. data/lib/kamal/commands/builder/clone.rb +2 -1
  24. data/lib/kamal/configuration/accessory.rb +13 -5
  25. data/lib/kamal/configuration/docs/configuration.yml +18 -3
  26. data/lib/kamal/configuration/docs/env.yml +6 -4
  27. data/lib/kamal/configuration/docs/output.yml +25 -0
  28. data/lib/kamal/configuration/docs/role.yml +1 -0
  29. data/lib/kamal/configuration/docs/ssh.yml +8 -0
  30. data/lib/kamal/configuration/output.rb +34 -0
  31. data/lib/kamal/configuration/proxy/run.rb +9 -0
  32. data/lib/kamal/configuration/role.rb +18 -6
  33. data/lib/kamal/configuration/ssh.rb +5 -1
  34. data/lib/kamal/configuration/validator.rb +14 -2
  35. data/lib/kamal/configuration.rb +6 -1
  36. data/lib/kamal/git.rb +1 -1
  37. data/lib/kamal/otel_shipper.rb +176 -0
  38. data/lib/kamal/output/base_logger.rb +29 -0
  39. data/lib/kamal/output/file_logger.rb +51 -0
  40. data/lib/kamal/output/formatter.rb +36 -0
  41. data/lib/kamal/output/otel_logger.rb +70 -0
  42. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +10 -2
  43. data/lib/kamal/secrets/adapters/passbolt.rb +1 -1
  44. data/lib/kamal/secrets.rb +1 -1
  45. data/lib/kamal/sshkit_with_ext.rb +9 -4
  46. data/lib/kamal/version.rb +1 -1
  47. metadata +9 -2
@@ -4,7 +4,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
4
4
  option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
5
5
  def setup
6
6
  print_runtime do
7
- with_lock do
7
+ modify(lock: true) do
8
8
  invoke_options = deploy_options
9
9
 
10
10
  say "Ensure Docker is installed...", :magenta
@@ -19,88 +19,94 @@ class Kamal::Cli::Main < Kamal::Cli::Base
19
19
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
20
20
  option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
21
21
  def deploy(boot_accessories: false)
22
- runtime = print_runtime do
23
- invoke_options = deploy_options
22
+ modify do
23
+ runtime = print_runtime do
24
+ invoke_options = deploy_options
24
25
 
25
- if options[:skip_push]
26
- say "Pull app image...", :magenta
27
- invoke "kamal:cli:build:pull", [], invoke_options
28
- else
29
- say "Build and push app image...", :magenta
30
- invoke "kamal:cli:build:deliver", [], invoke_options
31
- end
26
+ if options[:skip_push]
27
+ say "Pull app image...", :magenta
28
+ invoke "kamal:cli:build:pull", [], invoke_options
29
+ else
30
+ say "Build and push app image...", :magenta
31
+ invoke "kamal:cli:build:deliver", [], invoke_options
32
+ end
32
33
 
33
- with_lock do
34
- run_hook "pre-deploy", secrets: true
34
+ modify(lock: true) do
35
+ run_hook "pre-deploy", secrets: true
35
36
 
36
- say "Ensure kamal-proxy is running...", :magenta
37
- invoke "kamal:cli:proxy:boot", [], invoke_options
37
+ say "Ensure kamal-proxy is running...", :magenta
38
+ invoke "kamal:cli:proxy:boot", [], invoke_options
38
39
 
39
- invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
40
+ invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
40
41
 
41
- say "Detect stale containers...", :magenta
42
- invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
42
+ say "Detect stale containers...", :magenta
43
+ invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
43
44
 
44
- invoke "kamal:cli:app:boot", [], invoke_options
45
+ invoke "kamal:cli:app:boot", [], invoke_options
45
46
 
46
- say "Prune old containers and images...", :magenta
47
- invoke "kamal:cli:prune:all", [], invoke_options
47
+ say "Prune old containers and images...", :magenta
48
+ invoke "kamal:cli:prune:all", [], invoke_options
49
+ end
48
50
  end
49
- end
50
51
 
51
- run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
52
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
53
+ end
52
54
  end
53
55
 
54
56
  desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy and pruning"
55
57
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
56
58
  option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
57
59
  def redeploy
58
- runtime = print_runtime do
59
- invoke_options = deploy_options
60
+ modify do
61
+ runtime = print_runtime do
62
+ invoke_options = deploy_options
60
63
 
61
- if options[:skip_push]
62
- say "Pull app image...", :magenta
63
- invoke "kamal:cli:build:pull", [], invoke_options
64
- else
65
- say "Build and push app image...", :magenta
66
- invoke "kamal:cli:build:deliver", [], invoke_options
67
- end
64
+ if options[:skip_push]
65
+ say "Pull app image...", :magenta
66
+ invoke "kamal:cli:build:pull", [], invoke_options
67
+ else
68
+ say "Build and push app image...", :magenta
69
+ invoke "kamal:cli:build:deliver", [], invoke_options
70
+ end
68
71
 
69
- with_lock do
70
- run_hook "pre-deploy", secrets: true
72
+ modify(lock: true) do
73
+ run_hook "pre-deploy", secrets: true
71
74
 
72
- say "Detect stale containers...", :magenta
73
- invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
75
+ say "Detect stale containers...", :magenta
76
+ invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
74
77
 
75
- invoke "kamal:cli:app:boot", [], invoke_options
78
+ invoke "kamal:cli:app:boot", [], invoke_options
79
+ end
76
80
  end
77
- end
78
81
 
79
- run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
82
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
83
+ end
80
84
  end
81
85
 
82
86
  desc "rollback [VERSION]", "Rollback app to VERSION"
83
87
  def rollback(version)
84
88
  rolled_back = false
85
- runtime = print_runtime do
86
- with_lock do
87
- invoke_options = deploy_options
88
89
 
89
- KAMAL.config.version = version
90
- old_version = nil
90
+ modify do
91
+ runtime = print_runtime do
92
+ modify(lock: true) do
93
+ invoke_options = deploy_options
91
94
 
92
- if container_available?(version)
93
- run_hook "pre-deploy", secrets: true
95
+ KAMAL.config.version = version
94
96
 
95
- invoke "kamal:cli:app:boot", [], invoke_options.merge(version: version)
96
- rolled_back = true
97
- else
98
- say "The app version '#{version}' is not available as a container (use 'kamal app containers' for available versions)", :red
97
+ if container_available?(version)
98
+ run_hook "pre-deploy", secrets: true
99
+
100
+ invoke "kamal:cli:app:boot", [], invoke_options.merge(version: version)
101
+ rolled_back = true
102
+ else
103
+ say "The app version '#{version}' is not available as a container (use 'kamal app containers' for available versions)", :red
104
+ end
99
105
  end
100
106
  end
101
- end
102
107
 
103
- run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s if rolled_back
108
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s if rolled_back
109
+ end
104
110
  end
105
111
 
106
112
  desc "details", "Show details about all containers"
@@ -182,7 +188,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
182
188
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
183
189
  def remove
184
190
  confirming "This will remove all containers and images. Are you sure?" do
185
- with_lock do
191
+ modify(lock: true) do
186
192
  invoke "kamal:cli:app:remove", [], options.without(:confirmed)
187
193
  invoke "kamal:cli:proxy:remove", [], options.without(:confirmed)
188
194
  invoke "kamal:cli:accessory:remove", [ "all" ], options
@@ -196,7 +202,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
196
202
  option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
197
203
  def upgrade
198
204
  confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
199
- with_lock do
205
+ modify(lock: true) do
200
206
  if options[:rolling]
201
207
  KAMAL.hosts.each do |host|
202
208
  KAMAL.with_specific_hosts(host) do
@@ -1,7 +1,7 @@
1
1
  class Kamal::Cli::Proxy < Kamal::Cli::Base
2
2
  desc "boot", "Boot proxy on servers"
3
3
  def boot
4
- with_lock do
4
+ modify(lock: true) do
5
5
  on(KAMAL.hosts) do |host|
6
6
  execute *KAMAL.docker.create_network
7
7
  rescue SSHKit::Command::Failed => e
@@ -108,7 +108,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
108
108
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
109
109
  def reboot
110
110
  confirming "This will cause a brief outage on each host. Are you sure?" do
111
- with_lock do
111
+ modify(lock: true) do
112
112
  host_groups = options[:rolling] ? KAMAL.proxy_hosts : [ KAMAL.proxy_hosts ]
113
113
  host_groups.each do |hosts|
114
114
  host_list = Array(hosts).join(",")
@@ -174,7 +174,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
174
174
 
175
175
  desc "start", "Start existing proxy container on servers"
176
176
  def start
177
- with_lock do
177
+ modify(lock: true) do
178
178
  on(KAMAL.proxy_hosts) do |host|
179
179
  execute *KAMAL.auditor.record("Started proxy"), verbosity: :debug
180
180
  execute *KAMAL.proxy(host).start
@@ -184,7 +184,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
184
184
 
185
185
  desc "stop", "Stop existing proxy container on servers"
186
186
  def stop
187
- with_lock do
187
+ modify(lock: true) do
188
188
  on(KAMAL.proxy_hosts) do |host|
189
189
  execute *KAMAL.auditor.record("Stopped proxy"), verbosity: :debug
190
190
  execute *KAMAL.proxy(host).stop, raise_on_non_zero_exit: false
@@ -194,7 +194,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
194
194
 
195
195
  desc "restart", "Restart existing proxy container on servers"
196
196
  def restart
197
- with_lock do
197
+ modify(lock: true) do
198
198
  stop
199
199
  start
200
200
  end
@@ -236,7 +236,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
236
236
  desc "remove", "Remove proxy container and image from servers"
237
237
  option :force, type: :boolean, default: false, desc: "Force removing proxy when apps are still installed"
238
238
  def remove
239
- with_lock do
239
+ modify(lock: true) do
240
240
  if removal_allowed?(options[:force])
241
241
  stop
242
242
  remove_container
@@ -248,7 +248,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
248
248
 
249
249
  desc "remove_container", "Remove proxy container from servers", hide: true
250
250
  def remove_container
251
- with_lock do
251
+ modify(lock: true) do
252
252
  on(KAMAL.proxy_hosts) do
253
253
  execute *KAMAL.auditor.record("Removed proxy container"), verbosity: :debug
254
254
  execute *KAMAL.proxy(host).remove_container
@@ -258,7 +258,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
258
258
 
259
259
  desc "remove_image", "Remove proxy image from servers", hide: true
260
260
  def remove_image
261
- with_lock do
261
+ modify(lock: true) do
262
262
  on(KAMAL.proxy_hosts) do
263
263
  execute *KAMAL.auditor.record("Removed proxy image"), verbosity: :debug
264
264
  execute *KAMAL.proxy(host).remove_image
@@ -268,7 +268,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
268
268
 
269
269
  desc "remove_proxy_directory", "Remove the proxy directory from servers", hide: true
270
270
  def remove_proxy_directory
271
- with_lock do
271
+ modify(lock: true) do
272
272
  on(KAMAL.proxy_hosts) do
273
273
  execute *KAMAL.proxy(host).remove_proxy_directory, raise_on_non_zero_exit: false
274
274
  end
@@ -1,7 +1,7 @@
1
1
  class Kamal::Cli::Prune < Kamal::Cli::Base
2
2
  desc "all", "Prune unused images and stopped containers"
3
3
  def all
4
- with_lock do
4
+ modify(lock: true) do
5
5
  containers
6
6
  images
7
7
  end
@@ -9,7 +9,7 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
9
9
 
10
10
  desc "images", "Prune unused images"
11
11
  def images
12
- with_lock do
12
+ modify(lock: true) do
13
13
  on(KAMAL.hosts) do
14
14
  execute *KAMAL.auditor.record("Pruned images"), verbosity: :debug
15
15
  execute *KAMAL.prune.dangling_images
@@ -24,7 +24,7 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
24
24
  retain = options.fetch(:retain, KAMAL.config.retain_containers)
25
25
  raise "retain must be at least 1" if retain < 1
26
26
 
27
- with_lock do
27
+ modify(lock: true) do
28
28
  on(KAMAL.hosts) do
29
29
  execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
30
30
  execute *KAMAL.prune.app_containers(retain: retain)
@@ -1,33 +1,42 @@
1
1
  class Kamal::Cli::Server < Kamal::Cli::Base
2
2
  desc "exec", "Run a custom command on the server (use --help to show options)"
3
3
  option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
4
+ option :raw, type: :boolean, default: false, desc: "Output raw, unmodified stdout"
4
5
  def exec(*cmd)
5
- pre_connect_if_required
6
+ raw = options[:raw]
6
7
 
7
- cmd = Kamal::Utils.join_commands(cmd)
8
- hosts = KAMAL.hosts
9
- quiet = options[:quiet]
8
+ if raw && options[:interactive]
9
+ raise ArgumentError, "Raw is not compatible with interactive"
10
+ end
11
+
12
+ with_raw_output(raw) do
13
+ pre_connect_if_required
10
14
 
11
- case
12
- when options[:interactive]
13
- host = KAMAL.primary_host
15
+ cmd = Kamal::Utils.join_commands(cmd)
16
+ hosts = KAMAL.hosts
17
+ quiet = options[:quiet]
14
18
 
15
- say "Running '#{cmd}' on #{host} interactively...", :magenta
19
+ case
20
+ when options[:interactive]
21
+ host = KAMAL.primary_host
16
22
 
17
- run_locally { exec KAMAL.server.run_over_ssh(cmd, host: host) }
18
- else
19
- say "Running '#{cmd}' on #{hosts.join(', ')}...", :magenta
23
+ say "Running '#{cmd}' on #{host} interactively...", :magenta
20
24
 
21
- on(hosts) do |host|
22
- execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{host}"), verbosity: :debug
23
- puts_by_host host, capture_with_info(cmd), quiet: quiet
25
+ run_locally { exec KAMAL.server.run_over_ssh(cmd, host: host) }
26
+ else
27
+ say "Running '#{cmd}' on #{hosts.join(', ')}...", :magenta
28
+
29
+ on(hosts) do |host|
30
+ execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{host}"), verbosity: :debug
31
+ puts_by_host host, capture_with_info(cmd, strip: !raw), quiet: quiet, raw: raw
32
+ end
24
33
  end
25
34
  end
26
35
  end
27
36
 
28
37
  desc "bootstrap", "Set up Docker to run Kamal apps"
29
38
  def bootstrap
30
- with_lock do
39
+ modify(lock: true) do
31
40
  missing = []
32
41
 
33
42
  on(KAMAL.hosts) do |host|
@@ -1,3 +1,3 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env sh
2
2
 
3
3
  echo "Docker set up on $KAMAL_HOSTS..."
@@ -1,3 +1,3 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env sh
2
2
 
3
3
  echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -1,4 +1,4 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env sh
2
2
 
3
3
  # A sample post-deploy hook
4
4
  #
@@ -1,3 +1,3 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env sh
2
2
 
3
3
  echo "Rebooted kamal-proxy on $KAMAL_HOSTS"
@@ -1,3 +1,3 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env sh
2
2
 
3
3
  echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -1,4 +1,4 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env sh
2
2
 
3
3
  # A sample pre-build hook
4
4
  #
@@ -1,3 +1,3 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env sh
2
2
 
3
3
  echo "Rebooting kamal-proxy on $KAMAL_HOSTS..."
@@ -1,6 +1,10 @@
1
1
  # Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
2
2
  # and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
3
3
  # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
4
+ #
5
+ # When deploying with destinations, shared secrets can go in .kamal/secrets-common and
6
+ # destination-specific secrets in .kamal/secrets.<destination>. This .kamal/secrets file is used
7
+ # only when no destination is selected.
4
8
 
5
9
  # Option 1: Read secrets from the environment
6
10
  # KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
@@ -1,9 +1,11 @@
1
1
  require "active_support/core_ext/enumerable"
2
2
  require "active_support/core_ext/module/delegation"
3
3
  require "active_support/core_ext/object/blank"
4
+ require "active_support/broadcast_logger"
5
+ require "active_support/notifications"
4
6
 
5
7
  class Kamal::Commander
6
- attr_accessor :verbosity, :holding_lock, :connected
8
+ attr_accessor :verbosity, :holding_lock, :connected, :logging, :lock_wait, :lock_wait_timeout, :lock_wait_interval
7
9
  attr_reader :specific_roles, :specific_hosts
8
10
  delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :app_hosts, :proxy_hosts, :accessory_hosts, to: :specifics
9
11
 
@@ -15,8 +17,14 @@ class Kamal::Commander
15
17
  self.verbosity = :info
16
18
  self.holding_lock = ENV["KAMAL_LOCK"] == "true"
17
19
  self.connected = false
20
+ self.logging = false
21
+ self.lock_wait = false
22
+ self.lock_wait_timeout = 900
23
+ self.lock_wait_interval = 15
24
+ @modify_depth = 0
18
25
  @specifics = @specific_roles = @specific_hosts = nil
19
26
  @config = @config_kwargs = nil
27
+ @output_logger = nil
20
28
  @commands = {}
21
29
  end
22
30
 
@@ -142,6 +150,22 @@ class Kamal::Commander
142
150
  SSHKit.config.output_verbosity = old_level
143
151
  end
144
152
 
153
+ def modify(command:, subcommand:)
154
+ @logging = true
155
+ if modify_started
156
+ ActiveSupport::Notifications.instrument("modify.kamal",
157
+ command: command, subcommand: subcommand, destination: config.destination, hosts: hosts) { yield }
158
+ else
159
+ yield
160
+ end
161
+ ensure
162
+ output_logger.close if modify_finished
163
+ end
164
+
165
+ def log(line)
166
+ output_logger << "#{line}\n" if logging
167
+ end
168
+
145
169
  def holding_lock?
146
170
  self.holding_lock
147
171
  end
@@ -151,6 +175,20 @@ class Kamal::Commander
151
175
  end
152
176
 
153
177
  private
178
+ def output_logger
179
+ @output_logger ||= ActiveSupport::BroadcastLogger.new
180
+ end
181
+
182
+ def modify_started
183
+ @modify_depth += 1
184
+ @modify_depth == 1
185
+ end
186
+
187
+ def modify_finished
188
+ @modify_depth -= 1
189
+ @modify_depth == 0
190
+ end
191
+
154
192
  # Lazy setup of SSHKit
155
193
  def configure_sshkit_with(config)
156
194
  SSHKit::Backend::Netssh.pool.idle_timeout = config.sshkit.pool_idle_timeout
@@ -161,6 +199,21 @@ class Kamal::Commander
161
199
  end
162
200
  SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
163
201
  SSHKit.config.output_verbosity = verbosity
202
+
203
+ configure_output_with(config)
204
+ end
205
+
206
+ def configure_output_with(config)
207
+ return unless config.output.enabled?
208
+
209
+ config.output.loggers.each { |logger| output_logger.broadcast_to(logger) }
210
+
211
+ SSHKit.config.output = Kamal::Output::Formatter.new($stdout, output_logger)
212
+
213
+ at_exit { @output_logger&.close }
214
+ rescue => e
215
+ $stderr.puts "Output logger setup failed: #{e.class}: #{e.message}"
216
+ $stderr.puts e.backtrace.join("\n") if ENV["VERBOSE"]
164
217
  end
165
218
 
166
219
  def specifics
@@ -4,7 +4,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
4
4
  attr_reader :accessory_config
5
5
  delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
6
6
  :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
7
- :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
7
+ :restart_policy, :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
8
8
  to: :accessory_config
9
9
 
10
10
  def initialize(config, name:)
@@ -16,7 +16,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
16
16
  docker :run,
17
17
  "--name", service_name,
18
18
  "--detach",
19
- "--restart", "unless-stopped",
19
+ "--restart", restart_policy,
20
20
  *network_args,
21
21
  *config.logging_args,
22
22
  *publish_args,
@@ -21,7 +21,7 @@ module Kamal::Commands::App::Logging
21
21
  def container_id_command(container_id)
22
22
  case container_id
23
23
  when Array then container_id
24
- when String, Symbol then "echo #{container_id}"
24
+ when String, Symbol then shell([ "echo #{container_id}" ])
25
25
  else current_running_container_id
26
26
  end
27
27
  end
@@ -16,7 +16,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
16
16
  def run(hostname: nil)
17
17
  docker :run,
18
18
  "--detach",
19
- "--restart unless-stopped",
19
+ "--restart", role.restart_policy,
20
20
  "--name", container_name,
21
21
  "--network", "kamal",
22
22
  *([ "--hostname", hostname ] if hostname),
@@ -9,7 +9,8 @@ module Kamal::Commands::Builder::Clone
9
9
  git(:fetch, :origin, path: escaped_build_directory),
10
10
  git(:reset, "--hard", Kamal::Git.revision, path: escaped_build_directory),
11
11
  git(:clean, "-fdx", path: escaped_build_directory),
12
- git(:submodule, :update, "--init", path: escaped_build_directory)
12
+ git(:submodule, :update, "--init", path: escaped_build_directory),
13
+ git(:gc, "--auto", "--quiet", path: escaped_build_directory)
13
14
  ]
14
15
  end
15
16
 
@@ -102,11 +102,11 @@ class Kamal::Configuration::Accessory
102
102
  end
103
103
 
104
104
  def option_args
105
- if args = accessory_config["options"]
106
- optionize args
107
- else
108
- []
109
- end
105
+ optionize docker_options.reject { |key, _| key.to_s == "restart" }
106
+ end
107
+
108
+ def restart_policy
109
+ restart_policy_option || "unless-stopped"
110
110
  end
111
111
 
112
112
  def cmd
@@ -173,6 +173,14 @@ class Kamal::Configuration::Accessory
173
173
  accessory_config["volumes"] || []
174
174
  end
175
175
 
176
+ def docker_options
177
+ accessory_config["options"] || {}
178
+ end
179
+
180
+ def restart_policy_option
181
+ docker_options.find { |key, _| key.to_s == "restart" }&.last
182
+ end
183
+
176
184
  def path_volumes(paths)
177
185
  paths.map do |local, config|
178
186
  Kamal::Configuration::Volume.new \
@@ -99,7 +99,6 @@ hooks_path: /user_home/kamal/hooks
99
99
  #
100
100
  # Global setting for all hooks:
101
101
  hooks_output: :verbose
102
-
103
102
  # Or per-hook settings:
104
103
  hooks_output:
105
104
  pre-deploy: :verbose
@@ -108,13 +107,16 @@ hooks_output:
108
107
  # Secrets path
109
108
  #
110
109
  # Path to secrets, defaults to `.kamal/secrets`.
111
- # Kamal will look for `<secrets_path>-common` and `<secrets_path>` (or `<secrets_path>.<destination>` when using destinations):
110
+ # Kamal looks for `<secrets_path>-common` first and then `<secrets_path>`.
111
+ # When using destinations, it instead looks for `<secrets_path>-common` first and then
112
+ # `<secrets_path>.<destination>`. Later files override earlier ones.
112
113
  secrets_path: /user_home/kamal/secrets
113
114
 
114
115
  # Error pages
115
116
  #
116
117
  # A directory relative to the app root to find error pages for the proxy to serve.
117
- # Any files in the format 4xx.html or 5xx.html will be copied to the hosts.
118
+ # Name each page after the HTTP status code it serves, e.g. 404.html, 500.html,
119
+ # 502.html, 503.html, and 504.html.
118
120
  error_pages_path: public
119
121
 
120
122
  # Require destinations
@@ -159,6 +161,13 @@ deploy_timeout: 10
159
161
  # How long to wait for a container to drain, default 30:
160
162
  drain_timeout: 10
161
163
 
164
+ # Stop timeout
165
+ #
166
+ # How long to wait for a container to stop after SIGTERM, default is
167
+ # the drain_timeout for non-proxied roles and 10s (Docker default) for proxied roles.
168
+ # Can be overridden per role:
169
+ stop_timeout: 30
170
+
162
171
  # Run directory
163
172
  #
164
173
  # Directory to store kamal runtime files in on the host, default `.kamal`:
@@ -206,6 +215,12 @@ boot:
206
215
  logging:
207
216
  ...
208
217
 
218
+ # Output
219
+ #
220
+ # Configure output loggers (OTel, file), see kamal docs output:
221
+ output:
222
+ ...
223
+
209
224
  # Aliases
210
225
  #
211
226
  # Alias configuration, see kamal docs alias:
@@ -14,12 +14,14 @@ env:
14
14
 
15
15
  # Secrets
16
16
  #
17
- # Kamal uses dotenv to automatically load environment variables set in the `.kamal/secrets` file.
17
+ # Kamal uses dotenv to automatically load environment variables from the configured secrets files.
18
18
  #
19
- # If you are using destinations, secrets will instead be read from `.kamal/secrets.<DESTINATION>` if
20
- # it exists.
19
+ # Common secrets across all destinations can be set in `.kamal/secrets-common`. Kamal looks for
20
+ # `.kamal/secrets-common` first, then `.kamal/secrets`, with later values overriding earlier ones.
21
21
  #
22
- # Common secrets across all destinations can be set in `.kamal/secrets-common`.
22
+ # If you are using destinations, Kamal looks for `.kamal/secrets-common` first, then
23
+ # `.kamal/secrets.<destination>`. The non-destination `.kamal/secrets` file is not read when a
24
+ # destination is selected.
23
25
  #
24
26
  # This file can be used to set variables like `KAMAL_REGISTRY_PASSWORD` or database passwords.
25
27
  # You can use variable or command substitution in the secrets file.