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.

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