Stevedore

CI

A library-first, daemonless OCI toolkit for Elixir — everything you can do to a container image except run it.

A stevedore is the dockworker who loads, unloads, stows, and inspects shipping containers, and never sails the ship. That is exactly the line this library draws:

Stevedore handles OCI artifacts at rest — as bytes. Running them (namespaces, mounts, cgroups) is in motion, and out of scope.

Everything Stevedore does operates on images-as-data: fetch, inspect, copy, mirror, build, modify, analyze, sign, verify, and serve. None of it needs a kernel, root, or a container runtime — which is the whole point. It stays portable and embeddable by any application that deals with images, whether or not that application can (or wants to) run them.

It takes its feature surface from Skopeo (copy, inspect, sync, sign) and extends it with the create/modify/analyze surface of crane and ORAS, plus an opt-in registry server.

Status

Pre-1.0, under active development. The full at-rest toolkit (registry client, copy + transports, registry server, build/mutate, analyze, sign/verify/referrers, CLI + deploy) is implemented and tested; the public API may still shift before 1.0.

A taste

# Mirror a multi-arch image from one registry to another (digests preserved, blobs skipped/mounted):
Stevedore.copy("docker://alpine:3.20", "docker://ghcr.io/me/alpine:3.20", all: true)
# Build an image from a directory tree — declaratively, no Dockerfile, no `RUN`:
{:ok, image} = Stevedore.Build.from_dir("./rootfs", %{entrypoint: ["/bin/app"]})
Stevedore.copy(image, "oci:./out:1.0")
# Read what's inside, in memory and without root (whiteout-aware):
{:ok, sbom} = Stevedore.Analyze.sbom(image)
# Run a real /v2 registry — nothing starts until you ask:
Stevedore.start_link(store: "/var/lib/stevedore", port: 5000)

…or from the shell:

mix stevedore.copy docker://alpine:3.20 oci:./alpine:3.20
mix stevedore.inspect docker://alpine:3.20
mix stevedore.deploy docker://alpine:3.20 ./public --server nginx --config registry.conf

Design principles

What it can do

AreaHighlights
Fetchdocker:// Distribution v2 client: bearer-token auth, multi-arch select, digest-verified blobs, CDN-redirect token-leak protection
Copyone primitive across transports — docker://, oci:, oci-archive:, docker-archive:, dir:, static: — with multi-arch/platform/format control, blob-skip and cross-repo mount
Build / modifyassemble images from layers or a directory; append, retag, rewrite config, annotate, rebase, flatten — all digest-correct
Analyzewhiteout-aware merged filesystem, per-layer entries, diffs, file reads, best-effort SBOM
Sign / verifycosign-compatible signatures (native ECDSA), policy verification, OCI 1.1 subject/Referrers
Serve / deploya writable /v2 registry (Bandit), or a static tree + generated nginx/caddy config a dumb web server can host

Installation

Add stevedore to your dependencies:

def deps do
[
{:stevedore, "~> 0.1"}
]
end

The base build is dependency-free. Opt into modes as needed:

{:stevedore, "~> 0.1"},
{:req, "~> 0.5"}, # the docker:// registry client
{:plug, "~> 1.16"}, # \
{:bandit, "~> 1.5"}, # } the standalone /v2 registry server
{:ezstd, "~> 1.1"} # zstd-compressed layers

Target Elixir is ~> 1.18 (uses the built-in JSON module — no jason).

Documentation

Every module carries a @moduledoc and every public function a @doc + @spec (with iex> doctests). API docs are generated with ExDoc and, once published, will be at https://hexdocs.pm/stevedore.

Specifications

License

Apache License 2.0.

Stevedore draws directly on the design and conventions of the cloud-native container ecosystem — Skopeo, cosign, crane, and ORAS — which are themselves Apache-2.0. Matching that license keeps Stevedore compatible with the projects it learns from and interoperates with, and the Apache license's explicit patent grant is the norm for OCI tooling. Copyright 2026 oshlabs.