Skip to content

Add image_name parameter to builds#96

Open
rgarcia wants to merge 12 commits intomainfrom
feat/image-name-builds
Open

Add image_name parameter to builds#96
rgarcia wants to merge 12 commits intomainfrom
feat/image-name-builds

Conversation

@rgarcia
Copy link
Contributor

@rgarcia rgarcia commented Feb 13, 2026

Summary

  • Adds optional image_name parameter to the build system, allowing callers to specify a custom image name for build output
  • When image_name is set, images are pushed to {registry}/{image_name} instead of the default {registry}/builds/{id}
  • Changes propagated through CreateBuildRequest, BuildConfig, builder agent output ref, API multipart form parsing, registry token scoping, and OpenAPI spec

Test plan

  • Create a build without image_name — verify existing builds/{id} behavior unchanged
  • Create a build with image_name=myapp — verify image pushed to {registry}/myapp
  • Verify registry token is scoped to the custom image name repo
  • Verify recovered builds use the correct image name for token refresh

🤖 Generated with Claude Code


Note

Medium Risk
Touches build output publication and registry/image reference normalization, which can affect build-to-run workflows and image availability. Networking iptables rule changes also carry some operational risk if rule matching/cleanup behaves unexpectedly on hosts with existing rules.

Overview
Builds can now accept an optional image_name and the system will re-tag the completed build output to that name. The API parses image_name from multipart form data, propagates it through CreateBuildRequest/BuildConfig, and after the builder pushes to builds/{id} the server optionally calls ImportLocalImage to create the requested tag and updates build metadata ImageRef accordingly.

Registry/image handling is adjusted to use short repo names (no host prefix): the registry conversion trigger now keys images by pathRepo only, waitForImageReady takes a short ref, and registry-related tests were updated. Separately, networking iptables rule comments are made bridge-specific (with legacy cleanup) to avoid collisions across multiple deployments, and ProvideBuildManager rewrites localhost/127.0.0.1 registry URLs to the subnet gateway for builder VM reachability; a make run target was added for non-hot-reload execution.

Written by Cursor Bugbot for commit e4389a3. This will update automatically on new commits. Configure here.

Copy link
Contributor

@hiroTamada hiroTamada left a comment

Choose a reason for hiding this comment

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

Looks good.

I personally dont like how we need to specify registry url. Should this be optional/default?
For hypeman run we try internal registry first and docker.io if registry name is not specified?

@sjmiller609

@sjmiller609
Copy link
Collaborator

I personally dont like how we need to specify registry url. Should this be optional/default?
For hypeman run we try internal registry first and docker.io if registry name is not specified?

I agree. also it would be best if "hypeman push .." "hypeman pull ..." "hypeman run ..." are always consistent in whatever the user puts for the image name there. e.g. if I push by some name and that works then I should be able to run by that same name and it's running the image I just pushed

@rgarcia
Copy link
Contributor Author

rgarcia commented Feb 13, 2026

DX Fixes Applied (ac17646)

Addressed the consistency concerns raised by @sjmiller609. Here's what was wrong and what's fixed:

Problem: Push / Run naming mismatch

When you pushed an image via hypeman push alpine:3.19 myapp, the registry stored it as localhost:8081/myapp:latest (with the registry host prefix). But hypeman run myapp normalized the name to docker.io/library/myapp:latest — a completely different key. So push and run couldn't round-trip.

Fixes

1. Strip registry host prefix on push (registry.go)

The registry's manifest PUT handler was prepending req.Host (e.g., localhost:8081) to the repo name before storing. Now it uses just the path component, so Docker's ParseNormalizedNamed handles normalization consistently:

  • push myapp → stored as docker.io/library/myapp:latest
  • run myapp → looks up docker.io/library/myapp:latest → ✅ found

2. Backward-compat fallback in GetImage (images/manager.go)

Added a fallback: if the normalized name isn't found, also try prepending the internal registry URL. This handles images that were stored with the old localhost:PORT/name format.

3. Build image_ref uses short name (builds/manager.go)

Build results now return image_ref: "mybuiltapp" instead of "localhost:8081/mybuiltapp", so the user can directly hypeman run mybuiltapp with the value from the build output.

4. Registry auth accepts user JWTs (middleware/oapi_auth.go)

hypeman push from the CLI sends a regular user JWT token, but the /v2/ registry endpoints only accepted special registry-scoped tokens. Added a fallback to validate regular user JWTs for registry paths.

5. Regenerated oapi code + added make run target

The image_name field from the OpenAPI spec wasn't in the generated Go code, causing 400 validation errors. Regenerated. Also added a make run target for running without live reload.

Verified DX flow

$ hypeman push alpine:3.19 myapp
Pushed localhost:8081/myapp

$ curl http://localhost:8081/images/myapp
{"name": "docker.io/library/myapp:latest", "status": "ready", ...}

$ hypeman run myapp
Creating instance myapp-6dh6...
diitkynoorkq644o28x06cib   ← works!

Still open

  • Build e2e test: hypeman build with image_name accepted the API call correctly, but the builder VM can't pull base images from Docker Hub (network isolation issue unrelated to this PR). The image_ref would return "mybuiltapp" on success.
  • CLI build command: Doesn't expose --image-name flag yet — would need a CLI-side change to pass through the new API field.

@cursor

This comment has been minimized.

@rgarcia
Copy link
Contributor Author

rgarcia commented Feb 13, 2026

Build VM Networking Fixed (b2f5d1b)

The build system now works end-to-end. Here's what was broken and what's fixed:

Root cause: builder VMs couldn't reach the host registry

The builder VM was configured with registry_url: "localhost:8081", but inside the VM localhost refers to the VM itself — not the host machine. Three separate issues compounded:

  1. Registry URL not reachablelocalhost:8081 inside VM = connection refused
  2. BuildKit HTTPS mismatch — the baked builder image wrote a buildkitd.toml with https=true, but the registry is HTTP
  3. iptables NAT collision — multiple hypeman instances on the same host shared the same hypeman-nat iptables comment, causing them to overwrite each other's MASQUERADE/FORWARD rules. Our instance's rules were getting deleted by other instances' startup.

Fixes

1. Registry URL rewriting for VMs (providers.go)

ProvideBuildManager now rewrites localhost:PORTGATEWAY_IP:PORT (e.g., 10.101.0.1:8081) using the subnet config. Builder VMs reach the host via the bridge gateway IP.

2. Proper BuildKit config in builder agent (builder_agent/main.go)

Added setupBuildKitConfig() that writes a buildkitd.toml with http=true + insecure=true for the internal registry. Also sets DOCKER_CONFIG=/home/builder/.docker explicitly for rootlesskit user namespace compatibility.

3. Multi-tenant iptables isolation (bridge.go)

Changed iptables comments from generic hypeman-nat to bridge-scoped hypeman-nat-{bridge_name}. Each hypeman instance now manages its own rules without interfering with others.

4. Registry auth path pattern fix (oapi_auth.go)

Fixed registryPathPattern regex to support repository names with 3+ path segments (a/b/c). Addresses bugbot's "deep image paths break build pushes" concern.

5. VM subnet auth fallback (oapi_auth.go)

isInternalVMRequest was hardcoded to 10.102.x.x — now accepts the subnet CIDR as a parameter so it matches the actual configured subnet.

Verified e2e flow

$ curl -X POST localhost:8081/builds -F source=@test.tar.gz -F image_name=mybuiltapp
{"id": "xjop5eqomak8gkxco2odqnoo", "status": "queued"}

# Build completes in ~3.6s:
$ curl localhost:8081/builds/xjop5eqomak8gkxco2odqnoo
{"status": "ready", "image_ref": "mybuiltapp", "image_digest": "sha256:0f464e..."}

$ hypeman run mybuiltapp
Creating instance mybuiltapp-uzyx...
e6wo9ytjpl5gf3hngurx8w4k  ← works!

Bugbot responses

  • "Qualified names no longer round-trip" — False positive. ParseNormalizedNamed handles normalization consistently for both push and run. The backward-compat fallback in GetImage covers legacy images stored with the old prefix.
  • "Deep image paths break build pushes" — Valid concern, fixed by updating registryPathPattern to use (.+?)/(manifests|blobs|tags|referrers)/ instead of the old 2-segment limit.

@cursor

This comment has been minimized.

@rgarcia rgarcia force-pushed the feat/image-name-builds branch from 6f307c7 to 5dca4b0 Compare February 13, 2026 18:31
@github-actions
Copy link

github-actions bot commented Feb 13, 2026

✱ Stainless preview builds

This PR will update the hypeman SDKs with the following commit message.

feat: Add image_name parameter to builds

Edit this comment to update it. It will appear in the SDK's changelogs.

hypeman-openapi studio · code · diff

Your SDK built successfully.
generate ⚠️

hypeman-typescript studio · code · diff

Your SDK built successfully.
generate ⚠️build ✅lint ✅test ✅

npm install https://pkg.stainless.com/s/hypeman-typescript/509bdbb868410a87fa0941927e916dfe1fc18224/dist.tar.gz
hypeman-go studio · code · diff

Your SDK built successfully.
generate ⚠️lint ✅test ✅

go get github.com/stainless-sdks/hypeman-go@0823ab1161a61ebe621260924b3de08c84b7421f

This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-02-14 14:11:09 UTC

@rgarcia
Copy link
Contributor Author

rgarcia commented Feb 13, 2026

Rebased onto main + addressed bugbot comment

Bugbot: "Repo parsing breaks reserved path names"

This is now a non-issue after rebasing. The base branch already switched from a regex to v2.Router() from docker/distribution (introduced in the two-tier cache PR #70), which properly parses all OCI repository names including those with reserved segment names like blobs or manifests. The regex we had (registryPathPattern) no longer exists in the codebase.

Rebase summary

Rebased onto main (which included ~30 commits since our branch point). Key changes integrated:

All conflicts resolved carefully to preserve both sides' functionality. Builds and vets clean.

@cursor

This comment has been minimized.

@cursor

This comment has been minimized.

@cursor

This comment has been minimized.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issues.

@cursor
Copy link

cursor bot commented Feb 13, 2026

Bugbot Autofix prepared fixes for 2 of the 2 bugs found in the latest run.

  • ✅ Fixed: Build metadata can reference missing custom image
    • Moved imageRef = req.ImageName inside the success branch of ImportLocalImage, so metadata only references the custom image name when re-tagging actually succeeds.
  • ✅ Fixed: Custom image readiness check uses wrong reference
    • Changed waitForImageReady call to pass repo:tag instead of just repo, so non-latest tagged custom images are correctly checked for readiness.

Create PR

Or push these changes by commenting:

@cursor push c3527c7dd6
Preview (c3527c7dd6)
diff --git a/lib/builds/manager.go b/lib/builds/manager.go
--- a/lib/builds/manager.go
+++ b/lib/builds/manager.go
@@ -622,14 +622,15 @@
 			if tag == "" {
 				tag = "latest"
 			}
-			imageRef = req.ImageName
 			if _, err := m.imageManager.ImportLocalImage(buildCtx, repo, tag, result.ImageDigest); err != nil {
 				m.logger.Warn("failed to re-tag image", "build_id", id, "image_name", req.ImageName, "error", err)
 				// Don't fail the build - the image is still accessible via builds/{id}
 			} else {
 				m.logger.Info("re-tagged build image", "build_id", id, "from", buildRepo, "to", repo)
+				imageRef = req.ImageName
 				// Wait for the re-tagged image to be ready
-				if err := m.waitForImageReady(buildCtx, repo); err != nil {
+				retaggedRef := repo + ":" + tag
+				if err := m.waitForImageReady(buildCtx, retaggedRef); err != nil {
 					m.logger.Warn("re-tagged image conversion timed out", "build_id", id, "image_name", req.ImageName, "error", err)
 				}
 			}

rgarcia and others added 8 commits February 14, 2026 08:59
Allows callers to specify a custom image name for build output. When set,
the image is pushed to {registry}/{image_name} instead of the default
{registry}/builds/{id} path. Propagated through CreateBuildRequest,
BuildConfig, builder agent, API multipart parsing, and OpenAPI spec.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Strip registry host prefix when storing pushed images so names are
  consistent between push and run (e.g., push "myapp" → stored as
  "docker.io/library/myapp:latest" → run "myapp" finds it)
- Add fallback in GetImage to try internal registry prefix for backward
  compatibility with previously stored images
- Build image_ref now returns short name (e.g., "mybuiltapp") instead of
  "localhost:8081/mybuiltapp" so it can be used directly with `run`
- Allow regular user JWT tokens for registry /v2/ endpoints (enables
  `hypeman push` from CLI without special registry tokens)
- Regenerate oapi code to include image_name field
- Add `make run` target for running without live reload

Co-authored-by: Cursor <cursoragent@cursor.com>
- Rewrite registry URL from localhost to gateway IP (10.101.0.1:8081)
  when configuring builder VMs, since localhost inside a VM refers to
  the VM itself, not the host
- Add setupBuildKitConfig to builder agent: writes buildkitd.toml with
  http=true for the internal registry (fixes HTTPS connection failures)
- Set DOCKER_CONFIG env var explicitly for buildctl to find credentials
  in rootlesskit user namespace
- Fix registryPathPattern to support repository names with 3+ path
  segments (e.g., a/b/c) - addresses bugbot deep path concern
- Pass subnet CIDR to JwtAuth middleware so VM IP-based auth fallback
  matches the actual configured subnet (was hardcoded to 10.102.x.x)
- Use bridge-name-scoped iptables comments (e.g., hypeman-nat-vmbr-rgarcia)
  to prevent multi-tenant instances from overwriting each other's rules

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix registry_test.go: look up images by short repo name (without host
  prefix) to match how the registry stores them after the path-based
  storage change
- Remove credential helper from builder_agent: auth is now handled
  server-side via anonymous guest tokens from the token endpoint
- Token endpoint issues guest tokens for all anonymous scoped requests,
  enabling BuildKit mirror pulls and build pushes without client-side
  credentials
- Update token tests to reflect new guest token behavior

Co-authored-by: Cursor <cursoragent@cursor.com>
…name token scoping

- Revert token.go: remove guest tokens, restore original auth-required behavior
- Revert builder_agent: restore original Docker config.json auth setup (JWT as Basic auth)
- Revert token/auth tests to match original behavior
- Fix token scoping: always include builds/{id} in token (builder's push destination),
  plus image_name repo when set
- Fix post-build flow: wait for builds/{id} then re-tag to image_name via ImportLocalImage
- Add extractImageTag helper for parsing tags from image names

The root cause was two bugs in the image_name implementation:
1. The registry JWT only granted push to image_name, but builder pushes to builds/{id}
2. waitForImageReady looked for image_name, but the image was stored as builds/{id}

Co-authored-by: Cursor <cursoragent@cursor.com>
…pers

Remove stripImageTag and extractImageTag in favor of images.ParseNormalizedRef
which already handles repo/tag splitting. Also drop speculative image_name repo
from JWT since the builder never accesses it.

Co-authored-by: Cursor <cursoragent@cursor.com>
rgarcia and others added 4 commits February 14, 2026 08:59
… for readiness check

- Move `imageRef = req.ImageName` inside the success branch of
  ImportLocalImage so metadata falls back to builds/{id} on failure
- Use `repo:tag` instead of just `repo` when calling waitForImageReady
  so tagged images (e.g. myapp:v1.0) are checked correctly

Co-authored-by: Cursor <cursoragent@cursor.com>
…fallback

- Remove --network=host from builder image docker build (not needed)
- Remove registryURL fallback in images.GetImage — we don't need
  backward-compat lookups with host-prefixed names since this isn't
  in prod yet
- Remove registryURL parameter from images.NewManager
- Remove user JWT fallback for registry auth in middleware — main
  branch doesn't have it and it was added speculatively
- Add back removed comment about WWW-Authenticate telling clients
  where to get a token
- Fix comment about ImageName token behavior

Co-authored-by: Cursor <cursoragent@cursor.com>
Tests were using the old pattern of registry-host-prefixed refs
(e.g., "localhost:5000/builds/{id}") but waitForImageReady now
takes the short repo name directly (e.g., "builds/{id}").

Co-authored-by: Cursor <cursoragent@cursor.com>
…en oapi

- Remove unnecessary `var err error` in getImageByRef, use := instead
- Revert lib/middleware/oapi_auth.go to main (no functional changes needed)
- Regenerate lib/oapi/oapi.go to include image_name field after rebase

Co-authored-by: Cursor <cursoragent@cursor.com>
@rgarcia rgarcia force-pushed the feat/image-name-builds branch from 51c86e7 to e4389a3 Compare February 14, 2026 14:09
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.

3 participants