{"record":{"id":"5f2a6a7175716e77","repo":"basecamp/kamal","slug":"must-retain-at-least-1-container","errorCode":null,"errorMessage":"Must retain at least 1 container","messagePattern":"Must retain at least 1 container","errorType":"validation","errorClass":"Kamal::ConfigurationError","httpStatus":null,"severity":"error","filePath":"lib/kamal/configuration.rb","lineNumber":378,"sourceCode":"      true\n    end\n\n    def ensure_valid_service_name\n      raise Kamal::ConfigurationError, \"Service name can only include alphanumeric characters, hyphens, and underscores\" unless raw_config[:service] =~ /^[a-z0-9_-]+$/i\n\n      true\n    end\n\n    def ensure_valid_kamal_version\n      if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Kamal::VERSION)\n        raise Kamal::ConfigurationError, \"Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}\"\n      end\n\n      true\n    end\n\n    def ensure_retain_containers_valid\n      raise Kamal::ConfigurationError, \"Must retain at least 1 container\" if retain_containers < 1\n\n      true\n    end\n\n    def ensure_no_traefik_reboot_hooks\n      hooks = %w[ pre-traefik-reboot post-traefik-reboot ].select { |hook_file| File.exist?(File.join(hooks_path, hook_file)) }\n\n      if hooks.any?\n        raise Kamal::ConfigurationError, \"Found #{hooks.join(\", \")}, these should be renamed to (pre|post)-proxy-reboot\"\n      end\n\n      true\n    end\n\n    def ensure_one_host_for_ssl_roles\n      roles.each(&:ensure_one_host_for_ssl)\n\n      true","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/configuration.rb#L360-L396","documentation":"Kamal::Configuration#ensure_retain_containers_valid rejects configs where fewer than one old container would be kept. retain_containers (default 5) controls how many old app container versions remain on each host after deploys so you can roll back; zero or negative values would leave no rollback target, so Kamal refuses the configuration.","triggerScenarios":"Setting `retain_containers: 0` in config/deploy.yml (often to save disk space); computing retain_containers from ERB/ENV that resolves to 0 or a negative number; a typo like `retain_containers: -1`.","commonSituations":"Aggressive disk cleanup on small VPS hosts; per-environment overrides where staging sets 0; templated values from a variables file defaulting incorrectly.","solutions":["Set `retain_containers` to at least 1 (e.g. `retain_containers: 2`) — small values still bound disk usage.","If disk pressure is the motive, use a modest value like 2-3 rather than 0, since rollback requires a retained container.","If ERB computes the value, clamp it: `[ ENV[\"RETAIN\"].to_i, 1 ].max`."],"exampleFix":"# config/deploy.yml — before\nretain_containers: 0\n\n# after\nretain_containers: 2","handlingStrategy":"validation","validationCode":"def valid_retain_containers?(raw)\n  v = raw[\"retain_containers\"]\n  v.nil? || (v.is_a?(Integer) && v >= 1)\nend","typeGuard":null,"tryCatchPattern":"begin\n  config = Kamal::Configuration.new(create_config_files: false)\nrescue Kamal::ConfigurationError => e\n  puts \"Deploy config invalid: #{e.message}\"\n  exit 1\nend","preventionTips":["Never set retain_containers below 1 — rollback needs a retained container.","Clamp ERB-sourced values: `[ ENV[\"RETAIN\"].to_i, 1 ].max`.","Prefer lowering to 2 rather than 0 when disk space is tight."],"tags":["kamal","configuration","retention","containers"],"backgroundTag":"value-out-of-range","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}