perf(build): fold the registry login into the mirror probe's round trip - #165
Merged
Conversation
`dash build pull` paid four SSH round trips per app host: a `docker login`, a `docker info` mirror probe, the audited clean-and-pull (#159), and `validate_image`. The first two carried no work the other could not carry — the probe runs before the pulls it shapes, and nothing between the login and the probe depends on ordering across hosts. `Dash::Commands::Registry#login_then` composes them into one command per host: docker login <server> -u … -p … > /dev/null && docker info --format '…' The login's stdout is redirected away so the capture returns the mirror answer alone. Mirror seeding is unchanged — a host with no mirror still fails the `docker info` half with docker's index error, which is what "no mirror" means, and a rejected login short-circuits the `&&` and raises as before. The two single-command cases stay single: one app host issues only the login (nothing to seed), a local registry only the probe (`Registry#login` returns nil). Four app hosts: 16 round trips → 12. This is a fold, not a skip; every command that ran before still runs, in the same order. Refs #161
mhenrixon
enabled auto-merge (squash)
September 12, 2026 10:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
dash build pullpaid four SSH round trips per app host:docker login, thedocker infomirror probe, the audited clean-and-pull (#159), andvalidate_image. The first two now share one round trip.lib/dash/commands/registry.rb— newRegistry#login_then(*commands): composesdocker login <server> -u … -p … > /dev/null && <command>. The login's stdout is redirected away so a capture returns the folded command's answer alone. For a local registry (loginis nil) it collapses to the command alone. Credentials stay wrapped insensitive(...)— composition keeps the array elements intact, so the debug log redacts exactly as it does for a standalone login.lib/dash/cli/build.rb—mirror_hosts→login_and_mirror_hosts: capturesDASH.registry.login_then(DASH.builder.first_mirror)per host. On a single app host there is nothing to seed, so the probe never runs and the plain login sweep stays. Thereflect: slice index out of rangerescue is unchanged; a rejected login short-circuits the&&and raises with docker's ownunauthorized/deniedtext, which the rescue does not match.This is a fold, not a skip: every command that ran before still runs, on the same hosts, in the same order. Mirror seeding behaves exactly as today.
Closes #161
Before / after
Round trips per
dash build pull, measured withrecorded_commandson the 4-app-host fixture (deploy_with_accessories.yml, remote registry, no mirror) — the same topology as the deploy report in #161:main@ 61c740e)The 16 matches the
Pull app image … 16 sshrow the issue was filed from. I did not have the 4-host staging target to re-run a wall-clock deploy, so the number above is the command count from the test harness rather than a re-pasted deploy-report row; the per-host structure is pinned bytest "pull issues three round trips per host".Single host: login, pull, validate (3, unchanged). Local registry, 2 hosts: probe, pull, validate ×2 (6, unchanged).
Test plan
test/commands/registry_test.rb— exact composed string, local-registry variant (command alone), credentials remainDash::Utils::Sensitivetest/cli/build_test.rb— composed command appears redacted in output; 3 round trips per host on 4 hosts; single host logs in without probing; local registry probes without logging in; a login failure inside the composed command still raises (SSHKit::Runner::ExecuteErrorcarryingunauthorized); mirror / mirrors seeding tests updated to match the composed capturetest/cli/main_test.rb— the four mirror-probe capture expectations match the composed command; the cost-guard sequence is untouchedbundle exec rubocop --parallel— no offensesbin/test(full suite, Docker +ghcr.io/zoolutions/dash-proxy:v1.1.0.1): 1904 runs, 6087 assertions, 0 failures — the harness registry has no mirror, so the real deploys took the rescue path through the composed commandPull app imagerow reads 12 ssh on four hostsDeviations & judgment calls
Deviations
Dash::Commands::Registry#login_then(the issue offered that orBuilder::Base#login_and_first_mirror); the registry is whereloginand itssensitive(...)wrapping already are, and the builder has no handle on the registry command object anyway.Discoveries
mirror_hostscarried theapp_hosts.many?branch itself, so the login had to move inside it — hence the rename tologin_and_mirror_hosts. A helper that only answered "which hosts have mirrors" could not also tellpullwhether the login had happened.forward_local_registry_portto inside it. Harmless: the block is only a real port-forward when the registry is local, and a local registry has no login at all.test/cli/main_test.rbhad four.with(:docker, :info, "--format …")capture expectations on the mirror probe, not the one the issue anticipated. All four became block matchers. The cost-guard sequence is untouched.SSHKit::Runner::ExecuteError(the parallel runner wraps it), notSSHKit::Command::Failed; the rescue inside theonblock correctly declines to match docker'sunauthorizedtext and re-raises.docs/references the mirror probe, so no docs change.Judgment calls
.with(exact, args)cannot match the composed command:Dash::Utils::Sensitivedefines no==, so credential arguments compare by identity. Every mirror-probe expectation became a block matcher asserting the command ends with the probe — which also pins that the probe is the last half, i.e. the one whose stdout the capture reads..claude/rules/performance.mdthe gem's metric is round trips, and that count is exact.Summary by cubic
Folds the
docker logininto thedocker infomirror probe indash build pull, cutting SSH round trips per app host from four to three (16 to 12 on four hosts) without skipping any command. AddsRegistry#login_thento compose both commands and redirects the login's stdout so the capture still returns the mirror probe's answer. Credentials stay redacted, a rejected login still raises, and single-host and local-registry cases are unchanged.Closes #161.
Written for commit f014956. Summary will update on new commits.