Skip to content

content(kamal): Q3 E4 - Kamal 2 multi-server deployment guide - #437

Merged
pftg merged 7 commits into
masterfrom
claude/content-calendar-next-post-hztfag
Aug 7, 2026
Merged

pftg merged 7 commits into
masterfrom
claude/content-calendar-next-post-hztfag

Conversation

@pftg

@pftg pftg commented Aug 7, 2026 •

Copy link
Copy Markdown
Member

What this is

Next post off the content calendar, plus the calendar defects found while picking it.

Post: "Kamal 2 Multi-Server Deployment Guide" (kamal-2-multi-server-deployment-complete-guide) - item E4 in the Technical Rails stream of 20.08 Q3 plan. Developer-targeted (Rails/DevOps engineers), not founder lead-gen. draft: false, all gates passed.

Why E4 and not the earlier-queued rows

Three items were overdue (E3 Jul 28, E4 Jul 30, F1 Aug 5). Two are traps:

Row Existing post that already owns the keyword Verdict
E3 solid-cache-vs-redis-production-benchmarks rails-8-solid-cache-performance-redis-migration (908 lines) Upgrade in place
F1 propshaft-migration-complete-guide-rails-8 propshaft-vs-sprockets-rails-8-asset-pipeline-migration (1,523 lines, pos 12.8 / 8,832 impr) Upgrade in place

Writing either as a new post splits the ranking - the exact Priority-3 cannibalization 20.08 itself warns about. F1's target is the page holding the impressions.

E4 splits nothing: all six existing Kamal posts are single-server and 2024-era.

The review loop caught five factual defects

Every claim was checked against basecamp/kamal @ eee0083 (v2.12.0). The standard 3-persona loop is tuned for voice and passed a draft with a bug that would have broken readers' deploys. Swapping the ICP-E founder persona for a source-verifying practitioner critic caught it:

  1. Migrations ran the old image. The post told readers to run db:migrate from a pre-deploy hook with bare kamal app exec. That resolves to the latest tag, but Kamal only moves latest after every host boots - so the hook runs the release being replaced. Silent no-op migrations on every deploy. Fixed with --version "$KAMAL_VERSION".
  2. Self-contradiction on --target (Kamal does pass it; it just never passes more than one).
  3. drain_timeout is 30s, not Docker's 10s.
  4. Sidekiq-Cron does not double-fire on a second job host - it coordinates through a Redis sorted set. The draft claimed the opposite.
  5. kamal-proxy round-robin landed April 2025 (PR refact: optimizes js #124), not late 2024.

Cleared as correct on re-verification rather than taken on trust: Solid Queue's --only-recurring/--skip-recurring both exist in 1.6.0, and recurring dedup really is unique_by: [:task_key, :run_at].

Three load-bearing claims were verified twice, independently, before drafting: kamal-proxy targets only its own local container (cli/app/boot.rb:54-57), ssl: true + 2 web hosts is a hard ConfigurationError (configuration/role.rb:161-163, custom certs are the escape hatch), and boot.limit percentages compute off all_hosts while slicing app_hosts.

Also added the section the post was missing and every reader hits in production: what happens when a deploy dies partway through the fleet.

Service links dropped from deep-technical posts

Paul's call. 20.08's bidirectional-funnel rule required a service-page link on every Rails post, but a fractional-CTO link at the end of a post about container ids and boot denominators reaches the wrong reader and costs more credibility with engineers than it returns. The closing line now points only at the two sibling technical posts.

Recorded as an explicit exemption in 20.08 and .okf/ rather than left as a silent violation. Founder-stream posts keep the funnel requirement in full.

Calendar defects fixed

  • blog-pipeline.md STEP 1 and GOAL-AT-A-GLANCE.md both still named the superseded 20.07 plan as plan-of-record, four months after 20.08 replaced it. Both repointed.
  • 20.08's funnel section named /services/startup-cto-consulting/, which does not exist. Corrected.
  • Added the cannibalization pre-check to STEP 1 so the next session doesn't repeat the trap.
  • Repointed the Falcon post's "Kamal 2 Multi-Server Deployment" anchor, which pointed at the GitHub Actions post.
  • .okf/ bundle + log synced in the same commits.

Gates

  • Research dossier, verified against a pinned upstream commit
  • Internal links - 8 verified slugs, no service link (see exemption above), deliberately no link to the Kamal 1/Traefik post
  • Practitioner + fact-check critic
  • SEO/slop critic (title 37 chars, description 154, keyword at word 62, 14 external citations)
  • Copy editor critic
  • Cold-eyes gate - PUBLISH-READY on all checks
  • Cover image (2400x1260, 6-slot spec) + hero diagram, verified legible at 390x844
  • bin/hugo-build green - 752 pages, 8/8 validators
  • Rendered scroll gate at 1280x800 and 390x844: zero console errors, zero 404s, no overflow
  • CI green - build, unit tests, broken-internal-links
  • draft: false

Content-only diff - no themes/, layouts/, or CSS - so per CLAUDE.md the visual suites don't apply.

Follow-ups (not in this PR)

  • E3 and F1 become upgrade-in-place tasks, not new posts. Recorded in 20.08 and .okf/.
  • automating-ssl-certificate-generation-with-traefik-kamal-step-by-guide documents Kamal 1 / Traefik and is now actively wrong for Kamal 2 (kamal-proxy). Needs a refresh or a version banner.
  • kamal-integration-in-rails-8-by-default-ruby (577-word announcement) is a consolidation candidate.

claude added 2 commits August 7, 2026 18:11
…guard

blog-pipeline.md STEP 1 and GOAL-AT-A-GLANCE.md still named the superseded
20.07 plan, so following either picks from the wrong calendar. Repointed both
to 20.08 and recorded that E3/F1 are upgrade-in-place rather than new posts -
each targets a keyword an existing post already ranks for. Fixed the
non-existent /services/startup-cto-consulting/ path in 20.08's funnel section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHgyr6mEXghWSzipXnzQuY
Q3 plan item E4. Developer-targeted technical post covering roles across
hosts, the kamal-proxy load-balancing gap, boot limit/wait behavior, the
multi-host SSL error, accessories, and migration ordering. Every claim
checked against basecamp/kamal v2.12.0 (eee0083).

Still in review: critic loop and cold-eyes gate pending, cover image pending.
Kept draft:true until those pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHgyr6mEXghWSzipXnzQuY
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a Kamal 2 multi-server deployment guide and updates content planning, publication tracking, source verification, linking rules, and blog workflow references.

Changes

Kamal content and publishing workflow

Layer / File(s) Summary
Kamal multi-server deployment guide
content/blog/kamal-2-multi-server-deployment-complete-guide/index.md, content/blog/falcon-web-server-production-tuning-benchmarks/index.md
Added multi-host Kamal 2 deployment guidance, operational details, recovery steps, TLS configuration, and a complete reference configuration. Updated a related article link.
Content plan and technical verification
.okf/content-strategy/content-plan.md, .okf/log.md, docs/projects/2510-seo-content-strategy/20-29-strategy/20.08-content-plan-data-driven-q3-2026.md
Recorded Kamal publication, upgrade-in-place decisions, linking exemptions, corrected service paths, and pinned-source verification requirements.
Plan-of-record and blog workflow alignment
docs/projects/2510-seo-content-strategy/GOAL-AT-A-GLANCE.md, docs/workflows/blog-pipeline.md
Changed the live plan of record to the Q3 2026 plan. Added unshipped-topic selection and duplicate-keyword checks.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the Q3 E4 Kamal 2 multi-server deployment guide, which is the main change in the pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/content-calendar-next-post-hztfag

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

claude added 5 commits August 7, 2026 18:26
Cover follows the 6-slot spec, duplicated from the solid-queue technical-stream
template with only text slots changed. Hero mermaid diagram sits in the first
fold showing each host's kamal-proxy pointing at its own local container, which
is the post's central correction.

Diagram syntax validated against the vendored mermaid-11.15.0 with the site's
initialize() config: renders 944x434, no overlaps or clipped labels.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHgyr6mEXghWSzipXnzQuY
Fact-check critic caught a load-bearing error verified against v2.12.0: the
pre-deploy migration hook ran the OLD image. Bare `kamal app exec` resolves to
the `latest` tag, but Kamal only moves `latest` after every host boots, so
during a pre-deploy hook `latest` still points at the release being replaced.
Now passes --version "$KAMAL_VERSION". Also corrected a self-contradiction on
--target and drain_timeout (30s, not Docker's 10s).

Added the section readers hit first in production and the post was missing:
what happens when a deploy dies partway through the fleet - no rollback of
already-booted hosts, `latest` never moves, recovery is a manual named-version
`kamal rollback`.

Voice fixes from the critic loop: removed 8 bold inline-header lists (banned),
dried out the CTA, cut the signposted intro and the slogany section closers.
External citations 3 -> 14, all as GitHub permalinks pinned to v2.12.0.

Hero diagram: dropped the per-diagram %%init%% override that was shrinking
text below the house 20px, and cut it to the single load-balancing message.
Verified legible at 390x844, no console errors, no 404s, no overflow.

Repointed the Falcon post's "Kamal 2 Multi-Server Deployment" anchor, which
pointed at the GitHub Actions post, to this one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHgyr6mEXghWSzipXnzQuY
Flipped draft:false after the cold-eyes gate returned PUBLISH-READY.

Cold-eyes caught two more factual defects the persona rounds missed: the post
claimed Sidekiq-Cron double-fires on a second job host (it coordinates via a
Redis sorted set), and dated kamal-proxy's round-robin support to late 2024
(PR #124 merged April 2025). Also harmonised `wait:` across the boot snippet,
the reference config, and the cover chip, and fixed a backwards section pointer.

Plan status: E4 marked published, E3 marked upgrade-in-place, and the Phase 1
table gained a Status column. OKF bundle records the review-loop lesson - a
source-verifying practitioner critic is required for developer-targeted posts,
because voice review cannot catch a wrong flag.

Gates: bin/hugo-build green (752 pages, 8/8 validators), rendered scroll gate
at 1280x800 and 390x844 with zero console errors, zero 404s, no overflow.
Content-only diff, so the visual suites do not apply.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHgyr6mEXghWSzipXnzQuY
Paul's call: a fractional-CTO link at the end of a post about container ids and
boot denominators reaches the wrong reader and costs more credibility with
engineers than it returns. The closing line now points only at the two sibling
technical posts.

20.08's bidirectional-funnel rule required a service link on every Rails post,
so this is recorded as an explicit exemption in the plan and in .okf/ rather
than left as a silent violation. Founder-stream posts keep the rule in full.

bin/hugo-build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHgyr6mEXghWSzipXnzQuY
@pftg
pftg marked this pull request as ready for review August 7, 2026 19:00
@pftg
pftg merged commit 24262e0 into master Aug 7, 2026
4 checks passed
@pftg
pftg deleted the claude/content-calendar-next-post-hztfag branch August 7, 2026 19:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.okf/content-strategy/content-plan.md:
- Around line 112-131: Update the blog workflow’s review-gate configuration so
developer-targeted posts require the practitioner critic alongside the existing
required critics. First inspect and align the applicable workflow definitions,
then change only the gate that currently requires founder, SEO, and editor
critics; preserve the existing gate behavior for non-technical posts.
- Around line 97-99: Update the queued-row topic check in
.okf/content-strategy/content-plan.md (lines 97-99) to search Markdown post
content for the target keyword before relying on matching slugs, then upgrade an
existing covering post and skip its row. Apply the same
content-search-before-selection behavior in docs/workflows/blog-pipeline.md
(lines 13-15).

In `@content/blog/kamal-2-multi-server-deployment-complete-guide/index.md`:
- Line 240: Update the connection arithmetic paragraph to separate web and job
capacity estimates: calculate web connections as web hosts × Puma workers ×
threads, and job connections as job hosts × worker processes × worker threads
plus scheduler connections. Remove the implication that job hosts use Puma
workers, while preserving the guidance to compare the total with Postgres
max_connections.
- Around line 20-22: Add the text language tag to the fenced code block
containing the SSL error message, changing the opening fence to use text while
preserving the message content unchanged.
- Line 334: Update the validation claim in the deployment guide to state that
running a Kamal command such as kamal deploy must load the configuration
successfully without unknown-key errors, rather than relying on YAML parsing
alone. Keep the existing explanation of version-specific key support and
separately provisioned load balancer unchanged.
- Line 123: The deployment guidance incorrectly characterizes boot: limit: 1 as
shortening the split-version window. Update the paragraph around kamal rollback
and boot: limit: 1 to describe it as serializing boot groups and reducing batch
blast radius, while explaining that earlier groups may remain on the new release
if a later group fails; retain balancer health checks and rollback as the
mitigations for mixed-version impact.
- Around line 34-36: Replace the bare `lib/kamal/cli/app/boot.rb` reference in
the boot description with a released GitHub permalink targeting the Kamal
v2.12.0 blob, adding a line anchor if it identifies the container-ID capture or
proxy deployment call.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 174a4969-15ba-4890-97b9-eba9631aa303

📥 Commits

Reviewing files that changed from the base of the PR and between eb0a527 and 5df49b4.

⛔ Files ignored due to path filters (1)
  • content/blog/kamal-2-multi-server-deployment-complete-guide/cover.png is excluded by !**/*.png
📒 Files selected for processing (7)
  • .okf/content-strategy/content-plan.md
  • .okf/log.md
  • content/blog/falcon-web-server-production-tuning-benchmarks/index.md
  • content/blog/kamal-2-multi-server-deployment-complete-guide/index.md
  • docs/projects/2510-seo-content-strategy/20-29-strategy/20.08-content-plan-data-driven-q3-2026.md
  • docs/projects/2510-seo-content-strategy/GOAL-AT-A-GLANCE.md
  • docs/workflows/blog-pipeline.md

Comment on lines +97 to +99
Rule: before drafting any queued row, run `ls content/blog/ | grep -i <term>`
and read anything that matches. If a post already covers the keyword, upgrade
it and take the next row instead. Cheap check, expensive miss.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Search post content before declaring a topic uncovered.

The current check only greps directory names. It can miss an existing post
whose slug does not contain the target term.

  • .okf/content-strategy/content-plan.md#L97-L99: search Markdown content
    before verifying matching slugs.
  • docs/workflows/blog-pipeline.md#L13-L15: apply the same content search
    before selecting the next plan row.
📍 Affects 2 files
  • .okf/content-strategy/content-plan.md#L97-L99 (this comment)
  • docs/workflows/blog-pipeline.md#L13-L15
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.okf/content-strategy/content-plan.md around lines 97 - 99, Update the
queued-row topic check in .okf/content-strategy/content-plan.md (lines 97-99) to
search Markdown post content for the target keyword before relying on matching
slugs, then upgrade an existing covering post and skip its row. Apply the same
content-search-before-selection behavior in docs/workflows/blog-pipeline.md
(lines 13-15).

Comment on lines +112 to +131
# Technical posts need a source-verification pass, not just a voice pass (2026-08-07)

The E4 draft went through the standard 3-persona loop and still carried a
correctness bug that would have broken readers' deploys: it told them to run
migrations from a `pre-deploy` hook with bare `kamal app exec`. That resolves to
the `latest` tag, but Kamal only moves `latest` after every host boots - so the
hook runs the release being replaced. The fix is `--version "$KAMAL_VERSION"`.

The founder-persona critic cannot catch this class of defect, and the voice and
SEO critics do not read source. For any developer-targeted post, replace the
ICP-E founder persona with a practitioner critic that verifies every claim, flag,
default, and error string against a PINNED upstream commit and reports file:line.
On E4 that critic caught the migration bug, a self-contradiction about `--target`,
and a wrong `drain_timeout` default; the cold-eyes gate then caught a wrong claim
about Sidekiq-Cron's dedup behavior and a wrong date for kamal-proxy PR #124.
Five factual defects, none of which voice review would ever surface.

Cite technical claims as GitHub permalinks pinned to the released tag
(`/blob/v2.12.0/...#L161`), not bare `file:line` - line numbers rot.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make the practitioner review gate executable in the blog workflow.

This section now requires pinned-source review for developer-targeted posts.
The blog pipeline still defines its review gate as founder, SEO, and editor
critics. A technical post can therefore pass without the required source
verification.

Update the pipeline gate to require the practitioner critic.

As per coding guidelines, applicable workflow files must be read and aligned
before Markdown workflow changes are made.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.okf/content-strategy/content-plan.md around lines 112 - 131, Update the
blog workflow’s review-gate configuration so developer-targeted posts require
the practitioner critic alongside the existing required critics. First inspect
and align the applicable workflow definitions, then change only the gate that
currently requires founder, SEO, and editor critics; preserve the existing gate
behavior for non-technical posts.

Source: Coding guidelines

Comment on lines +20 to +22
```
SSL is only supported on a single server unless you provide custom certificates, found 2 servers for role web
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The error-message fence has no language identifier. Use text.

Proposed fix
-```
+```text
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
SSL is only supported on a single server unless you provide custom certificates, found 2 servers for role web
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 20-20: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/blog/kamal-2-multi-server-deployment-complete-guide/index.md` around
lines 20 - 22, Add the text language tag to the fenced code block containing the
SSL error message, changing the opening fence to use text while preserving the
message content unchanged.

Source: Linters/SAST tools

Comment on lines +34 to +36
During boot, every host runs its own kamal-proxy container, and each proxy gets exactly one target - the app container on that same host. You can watch it happen in `lib/kamal/cli/app/boot.rb`: each host captures its own container id and passes it to `kamal-proxy deploy --target <id>`. A second web host gives you a second, fully independent proxy that has never heard of the first.

The proxy binary itself can do more. kamal-proxy gained round-robin across multiple targets in April 2025 ([issue #15](https://github.com/basecamp/kamal-proxy/issues/15), closed by [PR #124](https://github.com/basecamp/kamal-proxy/pull/124), explicitly aimed at multi-host Kamal). As of Kamal 2.12.0, that capability still isn't wired up. No `deploy.yml` key sets more than one target, and Kamal always passes a single [`--target=<container-id>:<port>`](https://github.com/basecamp/kamal/blob/v2.12.0/lib/kamal/configuration/proxy.rb#L97) per host.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Target file excerpt:"
sed -n '1,80p' content/blog/kamal-2-multi-server-deployment-complete-guide/index.md | cat -n

echo
echo "Local content strategy docs:"
for f in docs/90-99-content-strategy/strategy-analysis/90.10-icp-primary-website-target.md docs/90-99-content-strategy/strategy-analysis/90.11-voice-guide.md; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,80p' "$f"
  else
    echo "missing $f"
  fi
done

echo
echo "Other source path/link patterns in this post:"
rg -n 'lib/kamal/cli/app/boot|github\.com/basecamp/kamal|kamal-proxy|deploy\.yml|--target|issue `#15`|PR `#124`' content/blog/kamal-2-multi-server-deployment-complete-guide/index.md || true

Repository: jetthoughts/jetthoughts.github.io

Length of output: 20260


🌐 Web query:

Kamal basecamp v2.12.0 lib/kamal/cli/app/boot.rb kamal-proxy deploy --target

💡 Result:

In Kamal 2.12.0, the command kamal-proxy deploy --target is not a standard command provided by the Kamal CLI itself [1][2]. Instead, kamal-proxy is a separate, standalone tool [3][4] used by Kamal to handle zero-downtime routing, and the kamal-proxy deploy command is part of the kamal-proxy binary's interface [3][5]. The kamal-proxy deploy command is used to register an application instance with the proxy so that it can receive traffic [3][5]. The --target flag specifies the address of the application container instance (in the format hostname:port) that should be registered [3][5]. Key details regarding this command include: 1. Functionality: It instructs the proxy to begin routing traffic to the specified --target instance after health checks succeed [3][5]. 2. Usage: An example provided in the kamal-proxy documentation is kamal-proxy deploy service1 --target web-1:3000 [3][5]. 3. Integration with Kamal: While Kamal manages the proxy's lifecycle (via commands like kamal proxy boot or kamal proxy reboot) [6][7], the actual registration of service endpoints is handled by the proxy itself, often automatically during a Kamal deployment process [1][3]. 4. Source Context: The file lib/kamal/cli/app/boot.rb in the Kamal codebase is responsible for the application boot process during a Kamal deploy [1][8], but it does not define the kamal-proxy deploy command, which resides in the external kamal-proxy project [3][4]. If you are attempting to interact with the proxy during a deployment, it is generally recommended to use the standard kamal deploy commands [2], which handle the necessary proxy interactions automatically based on your config/deploy.yml configuration [9][7].

Citations:


🌐 Web query:

kamal v2.12.0 --target container-id port kamal-proxy deploy lib/kamal/cli/app/boot.rb

💡 Result:

The command string you provided, kamal v2.12.0 --target container-id port kamal-proxy deploy lib/kamal/cli/app/boot.rb, does not correspond to a standard or valid Kamal CLI command [1][2][3]. Kamal is a command-line tool for deploying containerized applications [4][5]. Its commands typically follow a structure like kamal [options] [1][3]. While kamal-proxy is a core component that Kamal manages, and --container-id is a valid option used in commands like kamal logs [6], they are not used together in the syntax you described. Below is a breakdown of why this command does not function as a standard Kamal instruction: 1. Subcommand/Action Mismatch: The Kamal CLI uses specific structures (e.g., kamal deploy, kamal proxy, kamal app) [1][3]. The sequence --target container-id port kamal-proxy deploy is not a recognized pattern for any Kamal command [2][3]. 2. Internal Paths: Including a file path such as lib/kamal/cli/app/boot.rb as an argument is not part of the standard usage for Kamal CLI commands. That file is part of the Kamal gem's internal source code [7], which users typically do not invoke directly via the CLI. 3. Purpose of Kamal-proxy: The kamal-proxy component is managed automatically by Kamal during the deploy process [7]. Users typically interact with it via commands like kamal proxy, rather than manually passing it to a deploy-style command [1][3]. If you are trying to achieve a specific task, such as deploying your application or checking container status, it is recommended to use the standard CLI commands: - To deploy your application: kamal deploy [2]. - To manage proxy settings: kamal proxy [1][3]. - To view logs for a specific container: kamal logs --container-id [6]. For further details on valid syntax, you can run kamal help or refer to the official Kamal documentation at kamal-deploy.org [1][3].

Citations:


Use a released GitHub permalink for the boot source path.

Line 34 cites lib/kamal/cli/app/boot.rb by bare path. Replace it with the Kamal 2.12.0 blob permalink, including any useful anchor if the claim points to a specific line.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/blog/kamal-2-multi-server-deployment-complete-guide/index.md` around
lines 34 - 36, Replace the bare `lib/kamal/cli/app/boot.rb` reference in the
boot description with a released GitHub permalink targeting the Kamal v2.12.0
blob, adding a line anchor if it identifies the container-ID capture or proxy
deployment call.

Source: Coding guidelines

kamal rollback <version> # boot that version everywhere
```

`kamal rollback` checks that the container still exists on every app host before booting it, and pruning keeps the last `retain_containers` releases (default 5), so you have a few deploys of runway rather than unlimited. Practically: keep the split-version window short by deploying with `boot: limit: 1` while you still trust the release, and make sure your balancer's own health check can pull a bad host out on its own. Kamal has no cordon, no drain, and no failover - that part is yours.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file lines =="
sed -n '100,140p' "content/blog/kamal-2-multi-server-deployment-complete-guide/index.md" || true

echo
echo "== search for Kamal boot limit/sequential/group/docs =="
rg -n "boot: limit|limit:\s*1|sequential|group|mixed-version|cordon|drain|failover|retain_containers" . --glob '!vendor/**' --glob '!node_modules/**' \| head -200 || true

Repository: jetthoughts/jetthoughts.github.io

Length of output: 4798


🌐 Web query:

Kamal v2 boot limit groups sequential rollback after all groups complete latest tag

💡 Result:

In Kamal v2, the boot configuration allows you to control the deployment rollout across multiple hosts using the limit and wait options [1][2]. The limit setting specifies the number or percentage of hosts to boot at a time, while wait defines the number of seconds to pause between these groups [1][3]. Regarding your query on sequential rollback: Kamal does not natively support a staged or group-based rollback that mirrors the "limit" and "wait" logic used during a deployment [4]. When you execute a kamal rollback [VERSION] command, Kamal performs the operation across the targeted hosts [4][5]. The rollback process effectively replaces the current running container with the specified previous version on all targeted hosts, rather than processing them in the incremental batches defined in your deployment configuration [4][5]. Hooks like pre-app-boot and post-app-boot are designed to work with the grouped boot strategy during a deployment (triggering once per group) [6]. However, these hooks operate within the context of the boot process; they do not introduce staged rollback capabilities. If you need to revert a deployment, Kamal v2 typically rolls back the entire targeted fleet to the specified container version in a single operation [4][7].

Citations:


🌐 Web query:

basecamp kamal v2.12.0 lib/kamal/cli/app.rb boot groups limit tag latest after all hosts up

💡 Result:

In Kamal v2.12.0, the boot command manages the deployment process across hosts according to the configuration defined in your deploy.yml file [1][2]. The behavior you described—using groups to limit concurrency and tagging the latest image after all hosts are successfully up—is a core part of the Kamal boot orchestration logic [3]. Key components of this process include: Configuration (boot block): You can control how many hosts are booted at once using the limit setting, which accepts an integer (number of hosts) or a percentage string (e.g., 25%) [4][5]. A wait setting can also be used to specify the number of seconds to pause between booting these groups [4][5]. Orchestration (lib/kamal/cli/app.rb): The boot command iterates through these host groups [3]. For each group, it executes the boot process, runs pre-app-boot and post-app-boot hooks, and respects the specified wait period [3]. Tagging (latest): The logic to tag the image as "latest" is executed only after the boot sequence completes across all host groups [3]. Specifically, after the host_boot_groups iteration is finished, Kamal performs a final step across all app hosts to record the auditor log and execute the command to tag the image as latest [3]. This ensures that the "latest" tag is only updated once the new version is successfully running on all target infrastructure [3].

Citations:


🌐 Web query:

kamal deploy boot limit fails some hosts remain upgraded later rollback all hosts documentation

💡 Result:

In Kamal, the boot configuration (specifically the limit setting) is used to perform rolling deployments by controlling how many hosts are updated at a time [1][2]. If a deployment fails—for example, because health checks fail or a process hangs—Kamal stops the deployment process on the current batch of hosts [3]. Because Kamal updates hosts in groups (batches) based on your boot configuration, a failed deployment will result in a state where some hosts have been successfully updated to the new version, while others remain on the old version [2]. Kamal does not automatically rollback the entire cluster if a partial deployment fails [4][5]. To restore consistency across all hosts, you must manually trigger a rollback [4][6]. To perform a rollback: 1. Identify the previous, stable version (container image or hash) that was running before the failed deploy [4][7]. You can list available containers on your servers by running: kamal app containers -q [4][6] 2. Once you have identified the target version, rollback to that version across the deployment by running: kamal rollback [VERSION] [4][7] Note that rollback only works if the target container still exists on the servers [5]. By default, Kamal prunes old containers three days after a deployment, so you must perform the rollback within this window or before the next deployment prunes the old containers [4][6][7]. Additionally, keep in mind that rolling back the application code does not automatically revert database migrations; you may need to handle any necessary database reverts manually [5].

Citations:


Do not describe boot: limit: 1 as shortening the split-version window.

limit: 1 serializes boot groups. If a later group fails to become healthy, earlier groups already on the new release stay up while latest remains on the old release and rollback runs across the requested hosts. Present boot: limit: 1 as reducing batch blast radius, and keep balancer health checks and rollback for mixed-version impact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/blog/kamal-2-multi-server-deployment-complete-guide/index.md` at line
123, The deployment guidance incorrectly characterizes boot: limit: 1 as
shortening the split-version window. Update the paragraph around kamal rollback
and boot: limit: 1 to describe it as serializing boot groups and reducing batch
blast radius, while explaining that earlier groups may remain on the new release
if a later group fails; retain balancer health checks and rollback as the
mitigations for mixed-version impact.

Source: MCP tools


Assets skew mid-deploy. Kamal's `asset_path:` bridges old and new fingerprinted assets per host, built from that host's own containers. Behind a round-robin balancer with no sticky sessions, a browser can fetch HTML from an already-updated host and then request the new CSS from a host that hasn't updated yet. `boot: limit: 1` with a short `wait` shrinks the window; a CDN in front of assets removes it.

Then there's the connection arithmetic. Total database connections scale as web hosts times Puma workers times threads, plus the same product for job hosts. Going from 1 web host to 3 triples the first term with zero config changes on the database side. Run that multiplication against your Postgres `max_connections` while you're still editing `deploy.yml`. The failure mode if you skip it is a `too many clients already` page.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Separate web and job database-connection formulas.

The documented job role runs bin/jobs, not Puma. Do not apply
web hosts × Puma workers × threads to job hosts.

Use separate estimates: web hosts times Puma workers times threads, plus job
hosts times worker processes times worker threads and scheduler connections.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/blog/kamal-2-multi-server-deployment-complete-guide/index.md` at line
240, Update the connection arithmetic paragraph to separate web and job capacity
estimates: calculate web connections as web hosts × Puma workers × threads, and
job connections as job hosts × worker processes × worker threads plus scheduler
connections. Remove the implication that job hosts use Puma workers, while
preserving the guidance to compare the total with Postgres max_connections.

deploy_timeout: 60
```

Plus the `pre-deploy` migration hook from above, and a load balancer in front of `10.0.0.11` and `10.0.0.12` that you provision separately. Kamal rejects unrecognized keys at validation time, so if this file parses on your machine, every key in it is real for your version.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Strategy docs =="
if [ -f docs/90-99-content-strategy/strategy-analysis/90.10-icp-primary-website-target.md ]; then
  sed -n '1,80p' docs/90-99-content-strategy/strategy-analysis/90.10-icp-primary-website-target.md
else
  echo "MISSING ICP strategy"
fi

echo
echo "== Voice docs excerpt =="
if [ -f docs/90-99-content-strategy/strategy-analysis/90.11-voice-guide.md ]; then
  sed -n '1,120p' docs/90-99-content-strategy/strategy-analysis/90.11-voice-guide.md
else
  echo "MISSING voice guide"
fi

echo
echo "== Target blog file excerpt =="
target="content/blog/kamal-2-multi-server-deployment-complete-guide/index.md"
if [ -f "$target" ]; then
  wc -l "$target"
  sed -n '315,345p' "$target"
else
  echo "MISSING target blog file"
fi

echo
echo "== Search config validation wording =="
rg -n "unrecognized|unknown keys|validation|kamal deploy|configuration" "$target" docs/90-99-content-strategy/strategy-analysis/90.11-voice-guide.md 2>/dev/null || true

echo
echo "== Kamal docs from remote == v2.12.0
python3 - <<'PY'
import urllib.request
url="https://github.com/ghraw/basecamp/kamal/v2.12.0/lib/kamal/configuration/docs/configuration.yml"
try:
    data=urllib.request.urlopen(url, timeout=20).read().decode()
    for i,line in enumerate(data.splitlines()[:80], 1):
        if "unrecognized" in line.lower() or "unknown" in line.lower():
            print(f"{i}: {line}")
except Exception as e:
    print(f"FATAL: {type(e).__name__}: {e}")
PY

Repository: jetthoughts/jetthoughts.github.io

Length of output: 18488


🌐 Web query:

Kamal v2.12.0 lib/kamal/configuration/docs/configuration.yml unrecognized keys validation

💡 Result:

In Kamal v2.12.0, the configuration validation mechanism strictly enforces that all keys present in your configuration file must be recognized by Kamal [1][2]. If the validator encounters keys it does not recognize, it raises a Kamal::ConfigurationError, identifying the unknown keys [3][4]. To handle this validation and manage your configuration files effectively, you can use the following methods: 1. Use Configuration Extensions: If you need to include keys in your configuration file that Kamal should ignore (such as YAML anchors, aliases, or other metadata), you can prefix those keys with x-. Kamal explicitly ignores any keys starting with x- during the validation process, preventing them from triggering an error [1][4][2]. 2. Check for Breaking Changes: If you are encountering an unknown key error after upgrading, ensure your configuration file matches the current version's schema. For example, Kamal 2.x introduced significant changes, such as replacing the traefik key with a proxy configuration [5]. If your configuration file still contains obsolete keys from older versions, you must remove or update them to comply with the current version's requirements [5]. 3. How Validation Works: The validation logic is implemented in the Kamal::Configuration::Validator class [3]. When processing the configuration, it compares the keys in your file against a predefined example configuration. Keys that are not present in the example and do not satisfy the extension prefix rule are flagged as unknown [3]. If you are seeing an unexpected error, verify that you are not using a cached or older version of the configuration or running an older Kamal command (e.g., ensuring you are using bundle exec kamal or a correct binstub to avoid running the wrong version of the gem) [6][5].

Citations:


Tie key validation to a Kamal command.

YAML parsing can still ignore unknown keys. Write that kamal deploy or another Kamal command must load/configure the file without raising an unknown-key error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/blog/kamal-2-multi-server-deployment-complete-guide/index.md` at line
334, Update the validation claim in the deployment guide to state that running a
Kamal command such as kamal deploy must load the configuration successfully
without unknown-key errors, rather than relying on YAML parsing alone. Keep the
existing explanation of version-specific key support and separately provisioned
load balancer unchanged.

Source: MCP tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants