Skip to content

Add Docker-style image tagging and decouple content / repositories - #425

Open
chruffins wants to merge 19 commits into
mainfrom
hypeship/image-tag
Open

Add Docker-style image tagging and decouple content / repositories#425
chruffins wants to merge 19 commits into
mainfrom
hypeship/image-tag

Conversation

@chruffins

@chruffins chruffins commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

summary

Kernel browser sessions and app deployments run as VM-backed instances. This PR adds Docker-style local image tagging and separates immutable bootable image content from mutable repository/tag references.

A ready Hypeman image can be retagged and pushed without pulling or reconverting it. One converted rootfs is stored per content digest, while repository tags remain lightweight references. Existing legacy image trees remain readable during the migration.

1. pre-existing flows + new flows

existing pull and lookup flow

createImage(name):
  resolve the registry reference to a manifest digest
  write pending metadata
  pull OCI layers and convert them to a bootable rootfs
  write the rootfs and ready metadata
  create a tag reference

getImage(name):
  resolve a tag to a digest, or use the supplied digest
  read metadata and verify the ready rootfs exists
  return the image

new local tagging flow

tagImage(source, target):
  validate source and target references
  require target to contain a tag
  resolve the source tag or digest
  require the source image to be ready

  if source and target use the same repository:
    create or replace the target tag reference
  else:
    promote legacy content into the shared digest layout when needed
    move legacy tags to shared content references
    create the target repository tag reference

  return the target image

Example:

hypeman tag alpine:latest registry.example.com/app:v1
hypeman push registry.example.com/app:v1

Tagging does not pull or reconvert the image. The companion CLI changes are in kernel/hypeman-cli#65.

2. changes to the data model

There is no database schema or migration.

The API adds:

POST /images/{name}/tag

request:
  { "target": "registry.example.com/app:v1" }

response:
  image

The endpoint returns explicit errors for invalid references, missing sources, and non-ready sources.

The logical ownership model is now:

content[digest]  -> one canonical rootfs and metadata record
repository/tag  -> a reference to content[digest]

The existing image metadata format remains compatible. Tags are represented by filesystem references rather than a new database table.

This matches the app platform’s existing deployment identity: deployments retain both an image reference and an image digest. The reference is a mutable lookup name; the digest identifies the immutable image used by the VM.

3. changes to filesystem layout

legacy layout

images/
└── <repository>/
    ├── <tag> -> <digest>
    └── <digest>/
        ├── metadata.json
        └── rootfs.erofs

On macOS, the rootfs uses rootfs.ext4.

content-addressed layout

images/
├── content/
│   └── <digest>/
│       ├── metadata.json
│       └── rootfs.erofs
└── repositories/
    └── <repository>/
        └── <tag> -> ../../content/<digest>

The content directory owns the converted rootfs. Repository/tag paths are references and do not contain another copy of the image.

Readers support both layouts:

  • ready legacy content remains authoritative while shared content is incomplete
  • ready shared content becomes canonical once promotion completes
  • metadata and rootfs are always resolved from the same layout
  • failed or orphaned content can be removed without deleting referenced content

4. why change the filesystem layout

protect the bootable browser artifact

Hypeman converts OCI layers into an EROFS or EXT4 disk that boots the browser VM. That converted disk is the expensive runtime artifact; names such as chrome:stable and deployment-specific tags are only references to it.

Multiple names should not create multiple bootable disks or change the bytes used to start a browser session.

keep app deployment identity immutable

The app platform stores an image digest for deployments and app versions. A deployment must be able to start, roll back, or be recreated from the same digest even after a mutable tag moves forward.

Content-addressed storage makes the digest the local content owner and keeps aliases from creating separate image copies.

support host-local caching

Browser sessions and app invocations may run on different hosts over their lifetimes. A host can receive or prewarm a digest, materialize whatever repository/tag reference it needs, and start the VM from the same cached rootfs.

The host does not need to preserve every repository name that previously referenced the image.

make cleanup and accounting correct

Deleting one tag must not delete content still needed by another tag, app version, browser session, or digest-only reference. Separating content from references lets cleanup retain a digest until its final reference disappears.

Hard-linked aliases can also be counted once by physical inode, so repository aliases do not inflate disk usage.

preserve compatibility and enable promotion

Existing legacy images are readable without an offline migration. Cross-repository tagging promotes legacy content by hard-linking the rootfs into the shared directory, writing shared metadata, atomically installing references, and removing the duplicate legacy tree.

The shared content boundary also provides a clear future home for digest-level prewarming, eviction, verification, replication, backup, and restore.

validation

  • targeted image, storage, disk-accounting, API, scope, and CLI tests pass
  • regression coverage includes legacy/content layout selection, cross-repository promotion, alias deletion, malformed metadata, hard-linked accounting, and atomic tag replacement
  • full integration tests require embedded runtime binaries, mkfs.erofs, registry access, and host virtualization support

Note

High Risk
Reworks on-disk image layout, tagging, deletion, and disk accounting—core VM boot artifacts. Dual-layout promotion and shared-content cleanup can leak or drop images if layout selection is wrong.

Overview
Adds Docker-style local tagging so a ready image can be retagged (including across repositories) without pulling or reconverting. New POST /images/{name}/tag maps invalid names to 400, missing sources to 404, and non-ready images to 409.

Image storage now separates immutable digest content (images/content/<digest>) from repository/tag references (images/repositories/...). Cross-repo tags promote legacy trees by hard-linking the rootfs, rewriting tags, and removing the duplicate digest directory. Readers keep using a complete legacy image until shared content is ready.

Deletes only drop shared content when nothing else references it (other tags, in-flight pulls, or digest-only records). Disk accounting counts hard-linked aliases once. GetImage/DeleteImage now honor the requested reference so digest aliases in another repo delete the right tags.

Reviewed by Cursor Bugbot for commit efe820a. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread openapi.yaml
Comment thread lib/images/storage.go
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
-->

✱ stlc build

go code · compare

Your SDK build was successful.

generate ✅bootstrap ✅format ✅

116 files generated at 2e6e01b (pushed)

go get github.com/kernel/hypeman-go-staging@2e6e01b8ef9e5ad0fa304837a0e69dc016a796c5
python code · compare

Your SDK build was successful.

generate ✅bootstrap ✅format ✅

231 files generated at ef2ae23 (pushed)

typescript code · compare

Your SDK build was successful.

generate ✅bootstrap ✅format ✅

138 files generated at d43e178 (pushed)

Diagnostics: ❗ 0 new / 1 total error, 💡 0 new / 5 total note
LevelCodeMessageTargets
Build metadata
Buildbd_76BGcwVk-cheerful-iron
Timestamp2026-08-20T18:41:21.871Z
stlc8413509
Spec hashc0136e7a9a1e
Config hash659c3687c3f0

This comment is auto-generated by stlc and is kept up to date as you push.
If you push new commits, re-run this workflow to update this comment.
Last updated: 2026-08-20 18:41:56 UTC

Comment thread lib/images/disk_usage.go
@chruffins
chruffins marked this pull request as ready for review August 19, 2026 15:26
@chruffins
chruffins requested a review from sjmiller609 August 19, 2026 15:26
@chruffins

Copy link
Copy Markdown
Contributor Author

companion PR here: kernel/hypeman-cli#65

@sjmiller609

Copy link
Copy Markdown
Collaborator

mentioned to assess this directory layout:

images/
├── docker.io/library/alpine/       # existing legacy layout, untouched
│   ├── latest -> <digest>
│   └── <digest>/
│       ├── metadata.json
│       └── rootfs.erofs
│
├── content/                        # new layout only
│   └── <digest>/
│       ├── metadata.json
│       └── rootfs.erofs
│
└── repositories/                   # new tags only
    └── example.com/app/
        └── v1 -> ../../../content/<digest>

and if the migration is worth it or if we should stick to existing for now. un-requesting review until pinged again.

@sjmiller609
sjmiller609 removed their request for review August 19, 2026 18:42
Comment thread lib/images/storage.go
Comment thread lib/images/storage.go Outdated
Comment thread lib/images/storage.go Outdated
Comment thread lib/images/storage.go
Comment thread lib/images/storage.go
@chruffins chruffins changed the title Add Docker-style image tagging Add Docker-style image tagging and decouple content / repositories Aug 20, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit efe820a. Configure here.

Comment thread lib/images/manager.go
return os.Rename(diskTempPath, path)
}); err != nil {
return fmt.Errorf("install image disk: %w", err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rootfs install rename can fail

Medium Severity

Converted rootfs is now written under system/builds and moved into images/ with os.Rename. os.Rename cannot cross filesystems, so a split dataDir layout (separate volume or bind-mount for images/) makes every convert fail after a successful export. The temp file is then deleted, so the work is discarded and retry hits the same error.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit efe820a. Configure here.

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