Skip to content

Releases: intility/erlang-oci-builder

v0.10.2

21 Jan 08:19
683f149

Choose a tag to compare

0.10.2 (2026-01-21)

Bug Fixes

  • Revert version from 0.10.2 to 0.10.1 (3685339)

v0.10.1

19 Jan 11:42
c3a6c3f

Choose a tag to compare

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 pull scope instead of pull,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 Accept header 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

15 Jan 12:30
3afcf99

Choose a tag to compare

What's New

Breaking Changes

  • Removed --desc CLI option and description config option: Use --annotation "org.opencontainers.image.description=Your description" or the annotations config 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=VALUE CLI flag
  • CLI labels override config labels from rebar.config or mix.exs
  • Configure in labels map 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=VALUE CLI flag
  • New annotations config 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

09 Jan 14:39
bf2d52f

Choose a tag to compare

What's New

Flexible Tag Formats

The --tag parameter now accepts three formats:

  • Bare tags: -t v1.0.0 or -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.io

Semicolon-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.io now correctly pushes to ghcr.io/org/myapp:v1

Full Changelog: v0.9.0...v0.9.1

v0.9.0

09 Jan 09:21
79fc3e4

Choose a tag to compare

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 auto

Configure 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 detection
    • is_available/1 - Check if compression algorithm is available
    • default/0 - Get best available compression
    • resolve/1 - Resolve auto to actual algorithm
    • compress/2 - Compress data with specified algorithm

API Changes

  • ocibuild_layer:create/1,2 now returns {ok, Layer} | {error, Reason} instead of Layer
    • 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, and ocibuild_json now 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

08 Jan 22:38
aa2c3fd

Choose a tag to compare

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:latest

Signatures 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 images
    • load_key/1 - Load PEM-encoded EC private key
    • sign/2 - Sign data and return base64-encoded signature
    • build_signature_payload/2 - Build cosign simplesigning payload

New Functions

  • ocibuild_registry:push_signature/7,8 - Push signature as OCI referrer artifact
  • ocibuild_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

08 Jan 13:00
98d6ca1

Choose a tag to compare

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.gz

Features

  • Works with both single-platform and multi-platform images
  • Tag extracted from org.opencontainers.image.ref.name annotation by default
  • Use --tag to 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 schemaVersion or 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 rebuilding
  • ocibuild_registry:push_blobs/6 - Push pre-loaded blobs to registry
  • ocibuild_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

05 Jan 12:55
b7cdaf1

Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v0.6.0...v0.6.1

v0.6.0

02 Jan 14:57
abe1bc3

Choose a tag to compare

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_supocibuild_http_poolocibuild_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_term tracking 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

  1. Update your dependency to ~> 0.6
  2. If you're checking the return value of push functions, update to handle {ok, Digest} instead of ok

Full Changelog: v0.5.1...v0.6.0

ocibuild - v0.5.1

29 Dec 00:56
948a3b4

Choose a tag to compare

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/file and app/file both 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.