1
0
Fork 0
mirror of https://github.com/imjasonh/krust synced 2026-07-07 22:35:25 +00:00
krust/CLAUDE.md
Jason Hall 6681c2aef5
Add pre-commit hooks for code quality
- Add .pre-commit-config.yaml with rustfmt, clippy, and cargo check
- Include standard pre-commit hooks for whitespace and file fixes
- Update README with development setup instructions
- Fix formatting and trailing whitespace in various files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-07 21:27:18 -04:00

4.8 KiB

krust Development Notes

This document captures key learnings and decisions made during the development of krust with Claude.

Project Overview

krust is a container image build tool for Rust applications, inspired by ko.build for Go. It builds static binaries and packages them into minimal OCI container images without requiring Docker.

Key Design Decisions

1. Static Binaries with musl

We chose musl libc over glibc for static linking because:

  • True static linking: glibc uses dynamic loading internally (NSS) which breaks in static binaries
  • Smaller binaries: musl static binaries are 5-10x smaller than glibc
  • No runtime surprises: glibc static binaries often fail with DNS resolution, user lookups, or locale issues
  • Container-optimized: Perfect for minimal container images

2. Default Push Behavior

krust pushes images by default (use --no-push to skip) because:

  • Aligns with the common workflow of building and immediately using images
  • Enables the docker run $(krust build) pattern
  • Reduces friction for the most common use case

3. Output Design

  • stdout: Only the pushed image reference by digest (e.g., ttl.sh/user/app@sha256:...)
  • stderr: All logging and progress information
  • This enables composability with other tools

4. Image Naming Strategy

  • Uses KRUST_REPO environment variable for repository prefix
  • Automatically appends project name from Cargo.toml
  • Can be overridden with --image flag
  • Default tag is latest

Technical Learnings

OCI Image Building

  1. Layer Digest vs Diff ID:

    • Layer digest: SHA256 of the compressed (gzip) layer
    • Diff ID: SHA256 of the uncompressed tar (goes in image config)
    • Docker validates these match during pull
  2. Image Structure:

    Manifest -> Config + Layers
    Config contains: architecture, OS, environment, command, diff_ids
    Layers contain: compressed tar.gz files
    
  3. Registry API:

    • Push blobs (config and layers) first
    • Then push manifest referencing those blobs
    • Manifest URL contains the final digest

Cross-Compilation on macOS

For Linux targets from macOS, you need:

  1. Target toolchain: rustup target add x86_64-unknown-linux-musl
  2. Cross-linker: brew install filosottile/musl-cross/musl-cross
  3. Cargo config to specify the linker:
    [target.x86_64-unknown-linux-musl]
    linker = "x86_64-linux-musl-gcc"
    

Rust Static Linking

  • Use RUSTFLAGS="-C target-feature=+crt-static" for static linking
  • musl targets default to static, but explicit is better
  • The resulting binary has no runtime dependencies

Architecture Decisions

Module Structure

src/
├── main.rs          # CLI entry point and orchestration
├── lib.rs           # Public API exports
├── cli/             # Command-line interface definitions
├── builder/         # Rust compilation logic
├── image/           # OCI image construction
├── registry/        # Registry push operations
└── config/          # Configuration management

Error Handling

  • Used anyhow for error propagation with context
  • Errors include contextual information for debugging
  • All errors go to stderr, preserving stdout for output

Dependencies

Key crates chosen:

  • clap - CLI parsing with derive macros
  • tokio - Async runtime for registry operations
  • oci-distribution - OCI registry client
  • tar + flate2 - Layer creation
  • sha256 - Digest calculation
  • tracing - Structured logging

Testing Strategy

  1. Unit tests for each module
  2. Integration tests for CLI commands
  3. E2E tests that actually run the built binary
  4. Used assert_cmd for testing CLI behavior

Development Workflow

The iterative development process:

  1. Start with basic CLI structure
  2. Implement core functionality (build, image, push)
  3. Test with real registries (ttl.sh for anonymous push)
  4. Fix issues discovered during real usage
  5. Refine UX based on actual workflows

Future Improvements

Potential enhancements identified:

  1. Registry authentication support
  2. Multi-platform image manifests
  3. Build caching
  4. Image layer optimization
  5. Support for custom Dockerfile-like configs
  6. SBOM (Software Bill of Materials) generation

Useful Commands

# Test the full workflow
export KRUST_REPO=ttl.sh/test
docker run $(krust build example/hello-krust)

# Debug output
krust build -v 2>&1 | less

# Check static linking
file target/x86_64-unknown-linux-musl/release/binary
ldd target/x86_64-unknown-linux-musl/release/binary  # should say "not a dynamic executable"

Resources