Skip to content

Added --export command #4405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

craigloewen-msft
Copy link

I made a PR to add the --export command to nerdctl.

I did it to solve issues like #1854 and since I saw it had support from the maintainers.

I also looked at this PR for inspiration: https://github.com/containerd/nerdctl/pull/2161/files

Please let me know if you want any changes or if it looks good!


// For now, use direct tar access. nsenter may have permission issues in rootless mode.
tarArgs := []string{"-c", "-f", "-", "-C", rootPath, "."}
cmd := exec.CommandContext(ctx, tarBinary, tarArgs...)
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

I tried to fix this to use that WriteDiff function but couldn't get it working. No matter what that 'WriteDiff' function seems to only write 1024 bytes.

Here's my debug code while testing it:


func createTarArchive(ctx context.Context, rootPath string, pid int, options types.ContainerExportOptions) error {
	// Create a temporary empty directory to use as the "before" state for WriteDiff
	emptyDir, err := os.MkdirTemp("", "nerdctl-export-empty-")
	if err != nil {
		return fmt.Errorf("failed to create temporary empty directory: %w", err)
	}
	defer os.RemoveAll(emptyDir)

	// Debug logging
	log.G(ctx).Debugf("Using WriteDiff to export container filesystem from %s", rootPath)
	log.G(ctx).Debugf("Empty directory: %s", emptyDir)
	log.G(ctx).Debugf("Output writer type: %T", options.Stdout)

	// Check if the rootPath directory exists and has contents
	if entries, err := os.ReadDir(rootPath); err != nil {
		log.G(ctx).Debugf("Failed to read rootPath directory %s: %v", rootPath, err)
	} else {
		log.G(ctx).Debugf("RootPath %s contains %d entries", rootPath, len(entries))
		for i, entry := range entries {
			if i < 10 { // Only log first 10 entries to avoid spam
				log.G(ctx).Debugf("  - %s (dir: %v)", entry.Name(), entry.IsDir())
			}
		}
		if len(entries) > 10 {
			log.G(ctx).Debugf("  ... and %d more entries", len(entries)-10)
		}
	}

	// Create a counting writer to track bytes written
	cw := &countingWriter{w: options.Stdout}

	// Use WriteDiff to create a tar stream comparing the container rootfs (rootPath)
	// with an empty directory (emptyDir). This produces a complete export of the container.
	err = archive.WriteDiff(ctx, cw, rootPath, emptyDir)
	if err != nil {
		return fmt.Errorf("failed to write tar diff: %w", err)
	}

	log.G(ctx).Debugf("WriteDiff completed successfully, wrote %d bytes", cw.count)

	return nil
}

// countingWriter wraps an io.Writer and counts the bytes written
type countingWriter struct {
	w     io.Writer
	count int64
}

func (cw *countingWriter) Write(p []byte) (n int, err error) {
	n, err = cw.w.Write(p)
	cw.count += int64(n)
	return n, err
}

And it gave this debug output

❯ nerdctl --debug export -o /tmp/test.tar temp-container
DEBU[0000] stateDir: /run/user/1000/containerd-rootless
DEBU[0000] RootlessKit detach-netns mode: true
DEBU[0000] rootless parent main: executing "/usr/sbin/nsenter" with [-r/ -w/home/craig/dev/nerdctl --preserve-credentials -m -U -t 446 -F nerdctl --debug export -o /tmp/test.tar temp-container]
DEBU[0000] Using running container root /proc/26209/root (pid 26209)
DEBU[0000] Using WriteDiff to export container filesystem from /proc/26209/root
DEBU[0000] Empty directory: /tmp/nerdctl-export-empty-3661002940
DEBU[0000] Output writer type: *os.File
DEBU[0000] RootPath /proc/26209/root contains 17 entries
DEBU[0000]   - bin (dir: true)
DEBU[0000]   - dev (dir: true)
DEBU[0000]   - etc (dir: true)
DEBU[0000]   - home (dir: true)
DEBU[0000]   - lib (dir: true)
DEBU[0000]   - media (dir: true)
DEBU[0000]   - mnt (dir: true)
DEBU[0000]   - opt (dir: true)
DEBU[0000]   - proc (dir: true)
DEBU[0000]   - root (dir: true)
DEBU[0000]   ... and 7 more entries
DEBU[0000] Using double walk diff for /tmp/nerdctl-export-empty-3661002940 from /proc/26209/root
DEBU[0000] WriteDiff completed successfully, wrote 1024 bytes

So it seems like it should work? But just doesn't

craigloewen-msft and others added 2 commits July 9, 2025 10:55
Co-authored-by: Akihiro Suda <suda.kyoto@gmail.com>
Signed-off-by: Craig Loewen <crloewen@microsoft.com>
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