Skip to content

feat(deploy): source deploys against a local daemon — 501 falls back to docker build - #106

Merged
Fermionic-Lyu merged 1 commit into
mainfrom
feat/local-source-deploy
Aug 19, 2026
Merged

feat(deploy): source deploys against a local daemon — 501 falls back to docker build#106
Fermionic-Lyu merged 1 commit into
mainfrom
feat/local-source-deploy

Conversation

@Fermionic-Lyu

@Fermionic-Lyu Fermionic-Lyu commented Aug 19, 2026

Copy link
Copy Markdown
Member

Why

insta deploy <dir> was cloud-only: it mints a Fly deploy token, which insta-oss answers with a 501, and the command dead-ended — breaking the parity rule that one deploy script runs unchanged on both targets (insta-oss roadmap Phase 4).

What

  • On a 501 from the token mint (only a local daemon answers that), the CLI builds the directory locally — docker build -t insta-src-<proj8>-<group>:<timestamp> . — and hands the daemon the tag. The daemon deploys from the same docker this shell uses, so no registry, no push, no flyctl.
  • ensureFlyctl() moved after the token mint: the local path never auto-installs a third-party CLI it doesn't need.
  • Cloud behavior byte-for-byte unchanged; any non-501 token error still surfaces loudly. Dockerfile EXPOSE port defaulting applies to both paths as before.
  • buildFromSource exported with the repo's injectable-runner pattern (no global mocks).

Verification

  • 371/371 tests (4 new: tag shape, docker invocation + failure surfacing, the 501 fallback path, non-501 re-throw), typecheck clean.
  • Live end-to-end: insta deploy . against a real instad — CLI fell back, built nginx:alpine-based source locally, daemon served the source-built content at localhost:80.

🤖 Generated with Claude Code


Summary by cubic

Enables source deploys to a local daemon by falling back to a local Docker build when POST /projects/:id/deploy-token returns 501, restoring one-script parity across cloud and OSS. Cloud behavior is unchanged; only the 501 path differs.

  • On 501, build the source dir with docker build -t insta-src-<proj8>-<group>:<timestamp> . and pass the tag to the daemon; uses the host Docker only (no registry, no push, no flyctl).
  • Move ensureFlyctl() after the token mint so the local path never installs flyctl.
  • Export buildFromSource, dockerBuildLocal, and localImageTag; builds accept an injectable BuildRunner (matches repo DI pattern).
  • Preserve error/port behavior: non-501 token errors propagate; Dockerfile EXPOSE defaulting unchanged.
  • Tests added for tag shape, docker invocation and failure surfacing, the 501 fallback path, and non-501 rethrow; e2e validated against a local daemon.

Written for commit 8b01d71. Summary will update on new commits.

Review in cubic

…back to docker build

`insta deploy <dir>` was cloud-only: it mints a Fly deploy token, which
insta-oss answers with 501, and the command dead-ended. But a local daemon
deploys from the SAME docker this shell uses — so on 501 the CLI now builds
the directory locally (`docker build -t insta-src-<proj8>-<group>:<ts> .`)
and hands the daemon the tag. No registry, no push, no flyctl (ensureFlyctl
moved after the token mint so the local path never auto-installs it).

Same workflow both targets, per the parity rule: one deploy script now runs
unchanged on cloud and oss. Cloud behavior is byte-for-byte unchanged; any
non-501 token error still surfaces loudly.

buildFromSource is exported with the repo's injectable-runner pattern (no
global mocks). Verified: 371/371 tests (4 new: tag shape, docker invocation
+ failure surfacing, the 501 fallback, non-501 re-throw), typecheck; live
end-to-end — `insta deploy .` against instad built nginx:alpine locally and
the daemon served the source-built content.
@Fermionic-Lyu
Fermionic-Lyu enabled auto-merge (squash) August 19, 2026 17:40

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review — feat(deploy): source deploys against a local daemon (501 → docker build fallback)

Summary: A tight, well-tested change that restores one-script deploy parity by falling back to a local docker build when the deploy-token mint answers 501; cloud behavior is preserved and the fallback is correctly gated on ApiError.status === 501.

Requirements context

No matching spec/plan found — this repo has no docs/superpowers/ (nor any docs/ tree at all), so I assessed against the PR description, the referenced insta-oss "clean-501 sweep" parity work, and the surrounding code. The stated intent (source deploys must run unchanged against both cloud and a local insta-oss daemon) is well-defined and matches the implementation.

Verification I did

  • Confirmed ApiClient.rawRequest throws ApiError(status, …) for any status ≥ 400 (src/api.ts:57-60, src/api.ts:7-8), and ApiError.status is public — so the e instanceof ApiError && e.status !== 501 guard (src/commands/deploy.ts:97) is sound and won't mis-classify network errors as the 501 path.
  • Confirmed BuildRunner uses spawn(cmd, args, …) with an argv array and no shell (src/flyctl-build.ts), so the injected docker build invocation is not shell-interpolated.
  • Confirmed the injected run is threaded through to both dockerBuildLocal and flyctlBuildAndPush and defaults to defaultBuildRunner; the public deploy() caller passes no runner, so production uses the real spawner.
  • Confirmed EXPOSE-port defaulting still happens in deploy() (src/commands/deploy.ts:43-51) ahead of buildFromSource, so both paths keep it.

Findings

Critical

(none)

Suggestion

  • Functionality / ops — local source images accumulate, never pruned (src/commands/deploy.ts:63-65, :98). Each local build gets a unique insta-src-<proj8>-<group>:<timestamp> tag, so repeated insta deploy runs against a daemon leave a growing pile of dangling insta-src-* images on the host — the local equivalent of a slow disk leak. The comment "unique per build so a redeploy replaces" (:61-62) is also slightly inaccurate: a fresh timestamped tag does not replace the previous one; it adds a new one (the old tag lingers as <none> once overwritten only if the tag string collides, which it won't). Consider pruning prior insta-src-<proj8>-<group>:* tags after a successful deploy, or reusing a stable tag per (project, group), and tightening the comment.

Information

  • Software engineering / type-safety (src/commands/deploy.ts:91). Switching const tok = await api.rawRequest(...) to a bare let tok inside the try drops the RawResult type the destructure at :105 relied on (tok.body is now effectively untyped). Annotating let tok: RawResult (or Awaited<ReturnType<ApiClient['rawRequest']>>) restores the compile-time check on token/flyApp without changing behavior.
  • Functionality — cross-repo contract (src/commands/deploy.ts:98-102, :54-55). The fallback returns a bare local tag (no registry prefix) that is then POSTed to /projects/:id/deploy; correctness depends on insta-oss resolving a local docker image tag rather than attempting a registry pull. The PR states this was validated e2e — noting the coupling so it stays on the radar if either side changes.
  • Edge case — group → tag validity (src/commands/deploy.ts:64, :98). A --group containing docker-tag-illegal characters (uppercase, /, etc.) would surface only as a docker build failure rather than an earlier, clearer validation error. Low blast radius; left as-is is fine, but a note in the error path could help.

Security

No security-relevant regressions. The new subprocess uses argv (no shell injection surface); --group/projectId reach docker only as argv elements. No secrets are logged or added to responses; process.env is passed to the docker subprocess (standard, needed for DOCKER_HOST etc.) but not injected as build args. Auth/approval is unchanged — approval is still enforced at the /deploy step (src/commands/deploy.ts:56). No new dependencies.

Performance

No concerns. One extra local docker build occurs only on the 501 path; no new N+1 queries, hot-path work, or blocking I/O on shared paths.

Test coverage

Good — 4 focused tests (test/deploy-local-build.test.ts) cover tag shape (incl. the group ?? 'default' branch), docker invocation argv, build-failure surfacing, the 501 fallback, and non-501 rethrow, all via the injectable runner with no global mocks — consistent with the repo's DI test pattern (flyctl-build.test.ts, deploy.test.ts).


Verdict

approved (informational — no Critical findings; the GitHub green-check remains a separate human action). The Suggestion (image pruning) and Information items are non-blocking.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM - approved.

@Fermionic-Lyu
Fermionic-Lyu merged commit fcd8678 into main Aug 19, 2026
2 checks passed
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