Skip to content

feat(docker): add support for sandboxes from docker archives#2361

Open
feloy wants to merge 2 commits into
NVIDIA:mainfrom
feloy:docker-archive
Open

feat(docker): add support for sandboxes from docker archives#2361
feloy wants to merge 2 commits into
NVIDIA:mainfrom
feloy:docker-archive

Conversation

@feloy

@feloy feloy commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Add support for Docker archives (.tar, .tar.gz, .tgz) as --from sources on sandbox create. This enables air-gapped environments and VM-based build pipelines where a Docker archive is the natural hand-off artifact.

Related Issue

Closes #2175

Changes

  • Add DockerArchive { path } variant to ResolvedSource in the CLI
  • Detect .tar/.tar.gz/.tgz files in resolve_from() via extension-based matching (consistent with the existing Dockerfile filename heuristic)
  • Add load_docker_archive() in openshell-bootstrap using Bollard's import_image() (POST /images/load), parsing the image tag from Docker's "Loaded image: <tag>" response
  • Add load_from_docker_archive() in the CLI with the same local-gateway guard as Dockerfile sources
  • Update --from help text and published docs to mention Docker archive support

No proto, gateway, or server changes — the archive's embedded tag flows through the existing SandboxTemplate.image field.

Testing

  • mise run pre-commit passes
  • Unit tests added/updated
    • resolve_from correctly classifies .tar, .tar.gz, .tgz files as DockerArchive
    • Missing archive paths produce the expected local error
    • filename_looks_like_docker_archive detects valid extensions (case-insensitive) and rejects non-archives
  • E2E tests added/updated (if applicable)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

feloy added 2 commits July 20, 2026 08:33
Add support for loading pre-built Docker archives into the local daemon
when creating sandboxes, enabling air-gapped and macOS VM-based build
pipelines where a Docker archive is the natural hand-off artifact.

Closes NVIDIA#2175

Signed-off-by: Philippe Martin <phmartin@nvidia.com>
Signed-off-by: Philippe Martin <phmartin@redhat.com>
Verify the full round-trip: docker build → docker save → openshell
sandbox create --from archive.tar → marker file present in output.

Signed-off-by: Philippe Martin <phmartin@nvidia.com>
Signed-off-by: Philippe Martin <phmartin@redhat.com>
@feloy
feloy requested review from a team, derekwaynecarr, maxamillion and mrunalp as code owners July 20, 2026 08:46
@copy-pr-bot

copy-pr-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@drew drew changed the title Docker archive feat(docker): add support for sandboxes from docker archives Jul 20, 2026
Comment on lines +2635 to +2638
lower.ends_with(".tar.gz")
|| Path::new(&*lower)
.extension()
.is_some_and(|ext| ext == "tar" || ext == "tgz")

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.

Question: Why use ends_with in one case and `.extension for the other? Can we use the same logic for all cases?

Also we already have a path? Can't we call .extension() directly on that and then map the resultant string through to_lowercase()?

Comment on lines 2532 to +2533
/// 1. Existing file whose name contains "Dockerfile" → build from file.
/// 1b. Existing file with `.tar`/`.tar.gz`/`.tgz` extension → load archive.

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.

Split into: A single heading like "Existing file" followed by two points indicating the different sources.

/// Resolution order:
/// 1. Existing file whose name contains "Dockerfile" → build from file.
/// 1b. Existing file with `.tar`/`.tar.gz`/`.tgz` extension → load archive.
/// 2. Existing directory that contains a `Dockerfile` → build from directory.

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.

Question: Why are two "Dockerfile" cases now separated by a archive case? Should the explicit Dockerfile and Dockerfile parent folder cases not be treated the same way?

/// 1. Existing file whose name contains "Dockerfile" → build from file.
/// 1b. Existing file with `.tar`/`.tar.gz`/`.tgz` extension → load archive.
/// 2. Existing directory that contains a `Dockerfile` → build from directory.
/// 3. Missing explicit local paths → local error, not image pull.

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.

Out-of-scope: What are "Missing explicit local paths"?

@@ -2610,6 +2625,19 @@ fn filename_looks_like_dockerfile(path: &Path) -> bool {
lower.contains("dockerfile") || lower.ends_with(".dockerfile")

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.

Out of scope, but the || lower.ends_with(".dockerfile") is redundant since lower.contains("dockerfile")` will ALWAYS be true in that case.

@elezar elezar 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.

Thanks for adding archive-based sandbox sources. I think we need to address two issues before merging this:

  1. Image ingestion is not compute-driver aware. The current check only distinguishes local from remote gateways. Both Dockerfile builds and this new archive loader use Bollard’s local Docker connection, then assume the resulting image is visible to the active driver. That works reliably for Docker, but not for Podman unless the CLI’s DOCKER_HOST happens to point to the exact Podman socket used by the gateway. The VM driver also has its own Docker/Podman resolution behavior. We should either introduce a driver-aware image-ingestion path or explicitly restrict local Dockerfile/archive sources to confirmed Docker-backed gateways.

  2. The archive is buffered entirely in memory. std::fs::read() synchronously loads the complete archive before uploading it. Container archives can be several gigabytes. Bollard provides import_image_stream(), which should be used with an asynchronous, chunked file stream.

These are closely related: Dockerfiles and archives are both ways of ingesting a local image into the active compute runtime. I suggest establishing that capability boundary first, then implementing archive loading through it. A smaller alternative would be to scope this PR explicitly to Docker-backed gateways, reject other drivers clearly, and still switch the upload to the streaming API.

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.

feat: support Docker archive (.tar) as --from source on sandbox create

3 participants