Releases: intility/erlang-oci-builder
v0.10.2
v0.10.1
Release 0.10.1
Bug Fixes
-
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. -
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.
Improvements
- 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.
Full Changelog: v0.10.0...v0.10.1
v0.10.0
What's New
Breaking Changes
- 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.
Features
Custom Image Labels (#39)
Add labels to your container image config using the new --label flag:
rebar3 ocibuild -t myapp:1.0.0 -l "[email protected]" -l "version=1.0.0"
mix ocibuild -t myapp:1.0.0 --label "[email protected]"- Repeatable
-l/--label KEY=VALUECLI flag - CLI labels override config labels from rebar.config or mix.exs
- Configure in
labelsmap in your build config
Custom Manifest Annotations (#37)
Add custom OCI manifest annotations using the new --annotation flag:
rebar3 ocibuild -t myapp:1.0.0 -a "com.example.team=platform"
mix ocibuild -t myapp:1.0.0 --annotation "org.opencontainers.image.description=My app"- Repeatable
-a/--annotation KEY=VALUECLI flag - New
annotationsconfig option for 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 unix timestamp or RFC 3339 format
Configuration Example
%% rebar.config
{ocibuild, [
{labels, #{<<"maintainer">> => <<"[email protected]">>}},
{annotations, #{
<<"org.opencontainers.image.description">> => <<"My application">>,
<<"com.example.team">> => <<"platform">>
}}
]}.# mix.exs
ocibuild: [
labels: %{"maintainer" => "[email protected]"},
annotations: %{
"org.opencontainers.image.description" => "My application",
"com.example.team" => "platform"
}
]Migration from --desc
If you were using --desc or the description config option:
# Before
rebar3 ocibuild --desc "My application"
# After
rebar3 ocibuild -a "org.opencontainers.image.description=My application"Full Changelog: v0.9.1...v0.10.0
v0.9.1
What's New
Flexible Tag Formats
The --tag parameter now accepts three formats:
- 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) - Repo:tag (unchanged):
-t myapp:v1.0.0
docker/metadata-action Compatibility
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
sep-tags: ";"
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
run: |
MIX_ENV=prod mix release
MIX_ENV=prod mix ocibuild -t "${{ steps.meta.outputs.tags }}" --push ghcr.ioSemicolon-separated tags are split automatically.
Security
- Tags are now validated to prevent path traversal attacks
- Rejects tags containing
..path components or null bytes
Bug Fixes
- 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
Full Changelog: v0.9.0...v0.9.1
v0.9.0
ocibuild v0.9.0
Highlights
🚀 Zstd Compression - 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+, falling back to gzip on OTP 27.
📁 Smart Output Paths - Output filenames now automatically use the correct extension for the compression type (.tar.gz or .tar.zst), with warnings for mismatches.
📊 Improved CLI Output - Cleaner output with Tagged: and Digest: lines for easy CI/CD integration.
Features
Zstd Compression (OTP 28+)
# Use zstd compression (auto-detected on OTP 28+)
rebar3 ocibuild -t myapp:1.0.0 --compression zstd
# Explicit gzip for compatibility
rebar3 ocibuild -t myapp:1.0.0 --compression gzip
# Auto-detect best available (default)
rebar3 ocibuild -t myapp:1.0.0 --compression autoConfigure in rebar.config:
{ocibuild, [
{compression, auto} % gzip | zstd | auto
]}.Or mix.exs:
ocibuild: [compression: :auto]Improved CLI Output
Building OCI image: myapp
Pushing: ghcr.io/myorg/myapp:1.0.0
Layer 1/3 (erts, amd64) : [==============================] 100% 45.2 MB
Layer 2/3 (deps, amd64) : [==============================] 100% 12.8 MB
Layer 3/3 (app, amd64) : [==============================] 100% 2.0 MB
Tagged: ghcr.io/myorg/myapp:1.0.0
Tagged: ghcr.io/myorg/myapp:latest
Digest: sha256:abc123def456...
The final Digest: line is ideal for CI/CD pipelines to capture the image digest.
Smart Output Path Handling
| Input | Compression | Output |
|---|---|---|
--output myimage |
zstd | myimage.tar.zst |
--output myimage.tar |
gzip | myimage.tar.tar.gz |
--output myimage.tar.gz |
zstd | myimage.tar.gz + warning |
New Module
ocibuild_compress- Compression abstraction with OTP version detectionis_available/1- Check if compression algorithm is availabledefault/0- Get best available compressionresolve/1- Resolveautoto actual algorithmcompress/2- Compress data with specified algorithm
API Changes
ocibuild_layer:create/1,2now returns{ok, Layer} | {error, Reason}instead ofLayer- Enables proper error propagation when compression fails (e.g., zstd requested on OTP 27)
- Public API (
ocibuild:add_layer/2,3) behavior unchanged
Test Coverage
- Added 80+ new tests across utility modules
ocibuild_digest,ocibuild_time, andocibuild_jsonnow at 100% coverage- Total test count: 577
Installation
%% rebar.config
{project_plugins, [{ocibuild, "~> 0.9"}]}.# mix.exs
{:ocibuild, "~> 0.9", runtime: false}Full Changelog: v0.8.0...v0.9.0
ocibuild v0.8.0
Highlights
🔏 Cosign-Compatible Image Signing
Sign your container images with ECDSA P-256 keys to prove authenticity and enable verification by Kubernetes admission controllers like Kyverno and OPA Gatekeeper.
# Sign with a local key
rebar3 ocibuild --push ghcr.io/myorg --sign-key cosign.key
mix ocibuild --push ghcr.io/myorg --sign-key cosign.key
# Or via environment variable
OCIBUILD_SIGN_KEY=cosign.key rebar3 ocibuild --push ghcr.io/myorg
# Verify with cosign
cosign verify --key cosign.pub ghcr.io/myorg/myapp:latestSignatures are attached via the OCI Referrers API using the cosign simplesigning v1 format. Signing failures are handled gracefully—they log warnings without failing the build.
Note: Currently only local key-based signing is supported. Keyless signing via Sigstore/Fulcio may be added in a future release.
🏷️ Multiple Tag Support
Push the same image with multiple tags in a single command:
rebar3 ocibuild -t myapp:1.0.0 -t myapp:latest --push ghcr.io/myorg- Efficient: first tag does full upload, additional tags just reference the same manifest
- Works with both single-platform and multi-platform images
- All tags report the same digest in output
Security
- 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
New Modules
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
New Functions
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)
Full Changelog: v0.7.0...v0.8.0
v0.7.0
ocibuild v0.7.0
Push Existing OCI Tarballs
You can now push pre-built OCI tarballs to registries without rebuilding the image. This is useful for CI/CD pipelines where build and push are separate steps.
# Push existing tarball (uses embedded tag)
rebar3 ocibuild --push ghcr.io/myorg myimage.tar.gz
mix ocibuild --push ghcr.io/myorg myimage.tar.gz
# Push with tag override
rebar3 ocibuild --push ghcr.io/myorg --tag myapp:2.0.0 myimage.tar.gzFeatures
- 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 use temp directory
Security Enhancements
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
- OCI index schema validation: Reject tarballs with invalid
schemaVersionor empty manifests
Validation applies to both memory mode and disk mode loading.
Improvements
- Resource leak prevention: Temporary directories are now cleaned up even if exceptions occur during the push process
New Functions
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
Installation
%% rebar.config
{project_plugins, [{ocibuild, "~> 0.7"}]}.# mix.exs
{:ocibuild, "~> 0.7", runtime: false}Full Changelog: v0.6.1...v0.7.0
v0.6.1
v0.6.0
ocibuild v0.6.0
Highlights
This release brings OTP-supervised HTTP operations, a breaking change to push return values for better CI/CD integration, and cleaner code organization.
Breaking Changes
Push functions now return digest
ocibuild:push/3,4,5 and ocibuild:push_multi/4,5 now return {ok, Digest} instead of ok on success. This enables CI/CD integration with attestation workflows (e.g., GitHub's actions/attest-build-provenance).
%% Before
ok = ocibuild:push(Image, Registry, RepoTag)
%% After
{ok, Digest} = ocibuild:push(Image, Registry, RepoTag)New Features
Digest output after push
After a successful push, the full image reference with digest is printed to stdout:
Pushed: ghcr.io/org/repo:tag@sha256:abc123...
This format is machine-parseable for CI/CD pipelines that need the digest for signing, attestation, or verification.
Improvements
OTP-supervised HTTP client (#23)
Refactored HTTP operations to use proper OTP supervision instead of stand_alone mode. This fixes CI hangs where the VM couldn't exit due to orphaned 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
Domain-based source organization
Source files reorganized into logical subdirectories:
src/
├── http/ # HTTP and registry operations
├── oci/ # OCI image building modules
├── adapters/ # Build system adapters (rebar3, mix)
├── vcs/ # Version control adapters
└── util/ # Utility modules
Test suite reorganization
Tests reorganized to mirror source directory structure with proper separation between adapter tests and shared release API tests.
New Modules
| Module | Description |
|---|---|
ocibuild_http |
Public API for HTTP operations (start/0, stop/0, pmap/2,3) |
ocibuild_http_sup |
OTP supervisor for HTTP workers |
ocibuild_http_pool |
Coordinates parallel HTTP operations with bounded concurrency |
ocibuild_http_worker |
Single-use worker that owns its httpc profile |
Upgrading
- Update your dependency to
~> 0.6 - If you're checking the return value of
pushfunctions, update to handle{ok, Digest}instead ofok
Full Changelog: v0.5.1...v0.6.0
ocibuild - v0.5.1
0.5.1 - 2025-12-29
Bugfixes
- Fixed long filename truncation (#21): Files with names exceeding 100 bytes (the ustar limit) were being silently truncated, causing missing modules at runtime.
- 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
- Fixed VM shutdown hang: Force kill httpc process when cleanup times out to prevent TCP socket-owning processes from keeping the VM alive during shutdown.
Security
- 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).
Improvements
- 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.