0.10.5 (2026-06-30)
0.10.4 (2026-06-21)
0.10.3 (2026-03-13)
0.10.2 (2026-01-21)
- registry: empty string credentials cause 401 unauthorized instead of using anonymous access (#44) (f8fdcb8)
-
Fixed token exchange requesting excessive scope (#41): When pulling base images from private registries, token exchange now requests only
pullscope instead ofpull,push. This fixes authentication failures when the caller has read access to the base image but not write access.- Pull operations (
pull_manifest,pull_blob,check_blob_exists) now requestpullscope only - Push operations continue to request
pull,pushscope as before - New
operation_scope()type (pull | push) threads through the auth chain
- Pull operations (
-
Fixed multiple Accept headers not being sent correctly: Combined multiple
Acceptheader values into a single comma-separated header, fixing manifest fetching from registries like GHCR that require proper Accept header handling for OCI index manifests.
- Unified registry authentication: Removed special-case Docker Hub authentication code. All registries (including Docker Hub) now use the same standard OCI distribution authentication flow via WWW-Authenticate challenge discovery. This reduces code complexity while maintaining full compatibility.
- Removed
--descCLI option anddescriptionconfig option: Use--annotation "org.opencontainers.image.description=Your description"or theannotationsconfig map instead. This consolidates description handling into the more flexible annotations system.
-
Custom image labels (#39):
- New
--label KEY=VALUECLI flag (repeatable) for adding custom OCI image config labels - New
-lshort alias for--label - CLI labels override config labels from rebar.config or mix.exs
- Shared
parse_cli_kv_list/2helper eliminates code duplication between annotation and label parsing
- New
-
Custom manifest annotations (#37):
- New
--annotation KEY=VALUECLI flag (repeatable) for adding custom OCI manifest annotations - New
-ashort alias for--annotation - New
annotationsconfig option for both rebar.config and mix.exs - CLI annotations override config annotations
- Security validation: annotations are checked for null bytes and path traversal
- Protected annotations (source, revision, base.name, base.digest) cannot be overridden
- Special handling for
org.opencontainers.image.created: accepts both unix timestamp and RFC 3339 format, normalizes to UTC ISO 8601 - New
ocibuild_time:parse_rfc3339/1andnormalize_rfc3339/1functions for RFC 3339 timestamp handling
- New
-
Flexible tag formats (#34):
- Bare tags:
-t v1.0.0or-t latest(uses release name as repo) - Full references:
-t ghcr.io/org/myapp:v1.0.0(extracts repo path for push) - Existing repo:tag format unchanged:
-t myapp:v1.0.0
- Bare tags:
-
docker/metadata-action compatibility:
- Use
sep-tags: ";"in the action, then pass directly:-t "${{ steps.meta.outputs.tags }}" - Semicolon-separated tags are split automatically
- Works in both CLI and config files
- Use
- Tag validation: Tags are validated to prevent path traversal attacks
- Rejects tags containing
..path components - Rejects tags containing null bytes
- Rejects tags containing
- Fixed registry path handling when using full reference tags with
--push-t ghcr.io/org/myapp:v1 --push ghcr.ionow correctly pushes toghcr.io/org/myapp:v1- Previously would incorrectly create
ghcr.io/ghcr.io/org/myapp:v1
- Zstd compression support (OTP 28+):
- Layers can now be compressed with zstd for 20-50% smaller images and 5-10x faster decompression
- Automatic OTP version detection: uses zstd on OTP 28+, falls back to gzip on OTP 27
- New
--compressionCLI flag:gzip,zstd, orauto(default:auto) - Configurable via
compressionoption in rebar.config or mix.exs - Explicit
--compression zstdon OTP 27 returns clear error with OTP version info - Media type automatically set to
application/vnd.oci.image.layer.v1.tar+zstdfor zstd layers
ocibuild_compress- Compression abstraction with OTP version detectionis_available/1- Check if compression algorithm is availabledefault/0- Get best available compression (zstd on OTP 28+, gzip on OTP 27)resolve/1- Resolveautoto actual algorithmcompress/2- Compress data with specified algorithm
-
Smart output path handling:
- Default output filename now uses correct extension for compression (
.tar.gzfor gzip,.tar.zstfor zstd) - Paths without extension or ending in
.tarauto-complete with correct compression extension - Valid archive extensions (
.tar.gz,.tgz,.tar.zst,.tar.zstd) are used as-is - Warns when extension doesn't match compression (e.g.,
--output myimage.tar.gz --compression zstd) - Warns for unrecognized extensions (e.g.,
.png,.zip) but allows them
- Default output filename now uses correct extension for compression (
-
Improved console output:
- "Building OCI image" message now shows only image name without tag for clarity
- Save and push operations now show "Tagged: " for each tag
- Final output shows "Digest: sha256:..." for easy CI/CD pipeline integration
- Removed extra blank lines between output messages
-
Expanded test coverage: Added dedicated unit tests for utility modules
ocibuild_digest_tests- 38 tests covering SHA256 digests, hex encoding, and security edge casesocibuild_time_tests- 23 tests covering ISO8601 conversion andSOURCE_DATE_EPOCHhandlingocibuild_compress_tests- 29 tests covering compression algorithms and OTP version detectionocibuild_release_tests- Added 19 tests for output path extension handlingocibuild_digest,ocibuild_time, andocibuild_jsonnow at 100% code coverage
ocibuild_layer:create/1,2now returns{ok, Layer} | {error, Reason}instead ofLayer- This enables proper error propagation when compression fails (e.g., zstd on OTP 27)
- Internal callers updated; public API (
ocibuild:add_layer/2,3) crashes on error as before
-
Cosign-compatible image signing (#26):
- Sign container images with ECDSA P-256 keys (cosign-compatible)
- Signatures attached via OCI referrers API using cosign simplesigning v1 format
- New
--sign-keyCLI flag to specify signing key path - New
OCIBUILD_SIGN_KEYenvironment variable for CI/CD pipelines - Configure
sign_keyin rebar.config or mix.exs for default signing - Graceful degradation: signing failures log warnings without failing the build
- Verify signatures with standard cosign:
cosign verify --key cosign.pub ghcr.io/org/repo:tag
-
Multiple tag support (#30):
- Push the same image with multiple tags in a single command:
-t myapp:1.0.0 -t myapp:latest - Efficient: first tag does full upload, additional tags just reference the same manifest
- Works with both build-and-push and push-tarball modes
- Works with both single-platform and multi-platform images
- All tags report the same digest in output
- Push the same image with multiple tags in a single command:
- Digest validation for file paths: Digests from untrusted sources (manifests, tarballs) are now validated before being used to construct file paths, preventing path traversal attacks via malicious digest values like
sha256:../../etc/passwd
ocibuild_sign- ECDSA P-256 signing for container imagesload_key/1- Load PEM-encoded EC private keysign/2- Sign data and return base64-encoded signaturebuild_signature_payload/2- Build cosign simplesigning payload
ocibuild_registry:push_signature/7,8- Push signature as OCI referrer artifactocibuild_registry:tag_from_digest/5- Tag an existing manifest with a new tag (no blob re-upload)
- Push existing OCI tarballs:
- Push pre-built OCI tarballs to registries without rebuilding
- Works with both single-platform and multi-platform images
- Tag extracted from
org.opencontainers.image.ref.nameannotation by default - Use
--tagto override; manifest annotation is updated to match - Hybrid loading: small images (<100MB) loaded to memory, larger images extracted to temp directory
- Tarball security validation: Comprehensive validation of OCI tarballs to prevent malicious input:
- Path traversal protection: Reject entries with
../components or absolute paths - Symlink rejection: Prevent symlink escape attacks that could read arbitrary files
- Hardlink rejection: Prevent hardlink-based attacks
- Unknown entry type rejection: Only allow regular files and directories (reject devices, FIFOs, sockets)
- Null byte detection: Defense-in-depth against null byte injection
- Validation applies to both memory mode and disk mode loading
- OCI index schema validation: Reject tarballs with invalid
schemaVersionor empty manifests
- Path traversal protection: Reject entries with
ocibuild_layout:load_tarball_for_push/1,2- Load OCI tarball for pushing without rebuildingocibuild_registry:push_blobs/6- Push pre-loaded blobs to registryocibuild_registry:push_blobs_multi/6- Push pre-loaded multi-platform blobs with index
- Fixed rebar3 plugin usage (#28): The rebar3 plugin now works correctly when used as a project plugin.
- Added
init/1dispatch function toocibuild.erlfor proper plugin initialization - Fixed
get_base_image/2to handle both binary and list base image values from config - Updated README to recommend
{project_plugins, [{ocibuild, "~> 0.6"}]}instead of{deps, ...}
- Added
- Improved HTTP shutdown reliability: Extended graceful shutdown timeout and added explicit pool termination to prevent VM hangs on exit.
- Pool is now explicitly stopped with
gen_server:stop/3before supervisor shutdown - Graceful shutdown timeout increased from 1 second to 3 seconds
- Prevents the ~10 minute delay seen in CI when HTTP connections weren't cleaning up properly
- Pool is now explicitly stopped with
- Push functions now return digest:
ocibuild:push/3,4,5andocibuild:push_multi/4,5now return{ok, Digest}instead ofokon success. This enables CI/CD integration with attestation workflows (e.g.,actions/attest-build-provenance).- Before:
ok = ocibuild:push(Image, Registry, RepoTag) - After:
{ok, Digest} = ocibuild:push(Image, Registry, RepoTag)
- Before:
- Digest output after push: After a successful push, the full image reference with digest is printed to stdout:
This format is machine-parseable for CI/CD pipelines that need the digest for signing, attestation, or verification.
Pushed: ghcr.io/org/repo:tag@sha256:abc123...
- OTP-supervised HTTP client (#23): Refactored HTTP operations to use proper OTP supervision instead of
stand_alonemode. This fixes CI hangs where the VM can't exit due to open http connections.- New supervisor tree:
ocibuild_http_sup→ocibuild_http_pool→ocibuild_http_worker - Each HTTP worker owns its own httpc profile for clean isolation
- Clean shutdown via OTP supervision cascade (no more force-kill workarounds)
- Removed
persistent_termtracking and manual process cleanup
- New supervisor tree:
- Domain-based source organization: Source files reorganized into logical subdirectories:
src/http/- HTTP and registry operationssrc/oci/- OCI image building modulessrc/adapters/- Build system adapterssrc/vcs/- Version control adapterssrc/util/- Utility modules
- Test suite reorganization: Tests reorganized to mirror source directory structure with proper separation between adapter tests and shared release API tests
ocibuild_http- Public api for HTTP operations withstart/0,stop/0,pmap/2,3ocibuild_http_sup- OTP supervisor for HTTP workersocibuild_http_pool- Coordinates parallel HTTP operations with bounded concurrencyocibuild_http_worker- Single-use worker that owns its httpc profile
- Fixed long filename truncation (#21): Files with names exceeding 100 bytes (the ustar limit) were being silently truncated, causing missing modules at runtime. This commonly affected Elixir projects using libraries like AshAuthentication which generate long module names (e.g.,
Elixir.AshAuthentication.Strategy.Password.Authentication.Strategies.Password.Resettable.Options.beamat 101 bytes).- Implemented PAX extended headers (POSIX.1-2001) for paths that don't fit in ustar format
- PAX headers support arbitrary path lengths with no practical limit
- Backwards compatible: ustar is still used when paths fit, PAX only when needed
- Null byte injection protection: Paths containing null bytes (
\0) are now rejected. Null bytes could truncate filenames when extracted by C-based tools, potentially allowing path manipulation. - Empty path validation: Empty paths are now rejected with a clear error message.
- Mode validation: File modes outside the valid range (0-7777 octal) are now rejected. Previously, invalid modes could set unintended permissions like setuid/setgid.
- Overflow protection: Numeric fields (size, mtime, mode) that exceed their octal field capacity now raise errors instead of being silently truncated, which could corrupt archives.
- Duplicate path detection: Archives with duplicate paths now raise an error, preventing undefined extraction behavior. Detection works correctly when paths use different formats (e.g.,
/app/fileandapp/fileboth normalize to./app/file).
- Clearer error messages: All validation errors now include the offending value for easier debugging:
{null_byte, Path}- Path contains null byte{empty_path, <<>>}- Empty path provided{path_traversal, Path}- Path contains..{invalid_mode, Mode}- Mode outside valid range{duplicate_paths, [Path]}- Duplicate paths in file list{octal_overflow, N, Width}- Value too large for field
- Iteration guard: PAX length calculation now has a maximum iteration limit to prevent theoretical infinite loops.
- Code cleanup: Reorganized TAR header field macros with unused offset definitions preserved as documentation comments.
- SBOM generation: Automatic SPDX 2.2 Software Bill of Materials generation for supply chain security
- Embedded in every image at
/sbom.spdx.json - Attached as OCI referrer artifact when pushing to registries that support the referrers API
- Includes application dependencies, ERTS version, OTP version, and base image reference
- Package URLs (PURLs) generated for all dependencies (hex, github, gitlab, bitbucket, generic)
- Embedded in every image at
- New
--sbomCLI flag: Export SBOM to a local file path (e.g.,--sbom myapp.spdx.json) - New
ocibuild_sbommodule: Public API for SBOM generationgenerate/2- Generate SPDX 2.2 JSON from dependencies and optionsmedia_type/0- Returnsapplication/spdx+jsonto_purl/1- Convert dependency to Package URLbuild_referrer_manifest/4- Build OCI referrer manifest for artifact attachment
- OCI Referrers API support: New
ocibuild_registry:push_referrer/7,8for attaching artifacts to images
- VCS-agnostic URL handling: SBOM source URLs support multiple VCS suffixes (
.git,.hg,.svn) - SPDX ID sanitization: Package names are sanitized to comply with SPDX ID format (
[a-zA-Z0-9.-]+) - URI encoding for namespaces: Document namespace URLs are properly percent-encoded per RFC 3986
- Graceful SBOM failures: SBOM generation errors are logged as warnings without failing the build
- Silent referrer skip: Registries without referrer support are handled gracefully (no errors)
- Reusable manifest utilities:
ocibuild_layoutnow exportsbuild_config_blob/1andbuild_layer_descriptors/1
- Updated README with SBOM feature description
- Updated CLAUDE.md with
--sbomCLI option andocibuild_sbommodule - Updated AGENTS.md to mark SBOM generation as implemented
- Smart dependency layering: Releases are now automatically split into multiple OCI layers for optimal caching
- ERTS layer: Contains ERTS and OTP libraries (when
include_erts: true) - Deps layer: Contains third-party dependencies from lock file
- App layer: Contains your application code,
bin/, andreleases/ - Uses lock file (
rebar.lock/mix.lock) as source of truth for classification - Falls back to single layer when lock file is unavailable (backward compatible)
- Typical improvement: 80-90% smaller uploads when only app code changes
- ERTS layer: Contains ERTS and OTP libraries (when
- New
Ocibuild.Lockmodule: Shared Elixir module for parsingmix.lockfiles- Uses
Mix.Dep.Lock.read/1for safe lockfile parsing (noCode.eval_string) - Supports hex dependencies (7 and 8-element tuples) and git dependencies
- Uses
- New
get_dependencies/1adapter callback: Optional callback for adapters to provide dependency info for smart layering - Configurable git timeout: New
OCIBUILD_GIT_TIMEOUTenvironment variable- Timeout in milliseconds for git network operations (default: 5000, max: 300000)
- Useful for slow networks or large repositories
- Port message flushing: Fixed potential race condition where stale port messages could pollute the mailbox after git command timeout
- URL sanitization: CI environment variables are now sanitized to prevent URL injection attacks
- Strips credentials, query params, fragments, and control characters from URLs
- Timeout upper bound: Git timeout is now capped at 5 minutes to prevent indefinite hangs
- Layer partitioning functions: New public API in
ocibuild_release:partition_files_by_layer/5- Split files into ERTS, deps, and app layersclassify_file_layer/5- Classify a single file pathbuild_release_layers/5- Build layers with smart partitioning
- Dependency parsing for rebar3:
ocibuild_rebar3:parse_rebar_lock/1parses both old and new lock file formats - Consolidated test helpers: Elixir tests now use shared
Ocibuild.TestHelpersmodule
- Added "Environment Variables" section to README documenting
OCIBUILD_GIT_TIMEOUT - Added "Smart Dependency Layering" section to README explaining layer structure and benefits
- Documented reproducible builds requirement for layer caching to work across builds
- Automatic OCI annotations from VCS: Images are now automatically annotated with version control information
org.opencontainers.image.source- Repository URL (from VCS remote or CI environment)org.opencontainers.image.revision- Commit SHAorg.opencontainers.image.version- Application version from build systemorg.opencontainers.image.created- Build timestamp (respectsSOURCE_DATE_EPOCH)org.opencontainers.image.base.name- Base image referenceorg.opencontainers.image.base.digest- Base image digest
- CI environment variable support: VCS information is automatically detected from CI systems
- GitHub Actions:
GITHUB_SERVER_URL,GITHUB_REPOSITORY,GITHUB_SHA - GitLab CI:
CI_PROJECT_URL,CI_COMMIT_SHA - Azure DevOps:
BUILD_REPOSITORY_URI,BUILD_SOURCEVERSION
- GitHub Actions:
- New
ocibuild_vcsbehaviour: Pluggable VCS adapter system for future support of Mercurial, SVN, etc. - New
ocibuild_vcs_gitmodule: Git adapter for VCS annotations based on Git repositories. - New
--no-vcs-annotationsCLI flag: Disable automatic VCS annotations when not desired - New
vcs_annotationsconfig option: Set tofalsein rebar.config or mix.exs to disable by default - Non-root by default: Containers now run as UID 65534 (nobody) by default for improved security
- Add
--uidCLI option to override (e.g.,--uid 1000for custom user,--uid 0for root) - Configurable via
uidoption in rebar.config or mix.exs
- Add
- Reproducible builds: Support for
SOURCE_DATE_EPOCHenvironment variable to produce identical images from identical inputs- All timestamps (config
created, history entries, TAR file mtimes) use the epoch value when set - Files are sorted alphabetically for deterministic layer ordering
- Enables build verification, security audits, and registry deduplication
- All timestamps (config
- New
ocibuild_timemodule: Centralized timestamp utilities withget_timestamp/0,get_iso8601/0, andunix_to_iso8601/1 - New
ocibuild_tar:create/2: TAR creation now accepts options map withmtimeparameter for reproducible archives - New
ocibuild_layer:create/2: Layer creation now accepts options map for passing mtime through to TAR
- Adapter
get_app_version/1callback: Optional callback for adapters to provide application version for annotations - SSH to HTTPS URL conversion: Git SSH URLs (e.g.,
git@github.com:org/repo.git) are automatically converted to HTTPS for public visibility - Unified image configuration:
configure_release_image/3is now the single source of truth for image configuration, used by both CLI adapters and the programmatic API (build_image/3) build_image/3now supports all options: The programmatic API now supportsuid,annotations, and properly clears inheritedCmdfrom base images
- Multi-platform image support: Build and push images for multiple platforms (e.g.,
linux/amd64,linux/arm64) with OCI image index - Parallel layer downloads: Base image layers are now downloaded in parallel with bounded concurrency
- Parallel layer uploads: Application and base layers are uploaded in parallel during push operations
- Parallel multi-platform push: When pushing multi-platform images, all platform images are pushed simultaneously
- Multi-line progress display: New progress bar system shows all concurrent operations with real-time updates
- Layer caching during push: Layers cached during save are reused during push, avoiding redundant downloads
- Auto-detect platform: When
--platformis omitted, automatically detects and uses the current system platform
- Fixed
--platformflag being ignored: Single platform builds now correctly use the specified platform instead of auto-detecting - Fixed progress bar duplication: Progress bars are now properly cleared between save and push operations
- Fixed GHCR authentication: GitHub Container Registry now uses proper OAuth2 token exchange instead of Basic Auth
- Fixed push destination parsing:
--push ghcr.io/orgnow correctly separates registry host from namespace
- Publish the initial release to hex.pm.