ocibuild (ocibuild v0.10.4)
View Sourceocibuild - Build and publish OCI container images from the BEAM.
This module provides the public API for building OCI-compliant container images without requiring Docker or any container runtime.
Quick Start
%% Build from a base image
{ok, Image0} = ocibuild:from(~"docker.io/library/alpine:3.19"),
%% Add your application
Image1 = ocibuild:copy(Image0, [{~"myapp", AppBinary}], ~"/app"),
%% Configure the container
Image2 = ocibuild:entrypoint(Image1, [~"/app/myapp", ~"start"]),
Image3 = ocibuild:env(Image2, #{~"MIX_ENV" => ~"prod"}),
%% Push to a registry (GHCR uses username + token as password)
Auth = #{username => ~"github-username", password => ~"github-token"},
ok = ocibuild:push(Image3, ~"ghcr.io", ~"myorg/myapp:v1", Auth).Memory Requirements
This library processes layers entirely in memory. Ensure your VM has sufficient memory to hold your release files, compressed layer data, and any downloaded base image layers. As a rule of thumb, allocate at least 2x your release size plus base image layers. For very large images (>1 GB), consider breaking content into multiple smaller layers or increasing VM memory limits.
Summary
Functions
Add a layer to the image from a list of files.
Files are specified as {Path, Content, Mode} tuples
Add a layer to the image from a list of files with options.
Add an annotation to the image manifest.
Set the default command arguments. CMD provides default arguments to the entrypoint
Copy files to a destination directory in the image. This is a convenience function that creates a layer with files placed under the specified destination path
Set the entrypoint for the container. The entrypoint is the command that will be executed when the container starts.
Set environment variables. Environment variables are specified as a map
Export the image as an OCI layout directory.
Expose a port.
Start building an image from a base image reference. The reference can be either a binary string or a tuple
Start building an image from a base image with authentication.
Start building an image from a base image with authentication and options.
Add a label to the image config.
Parse a platform string into a platform map.
Parse a comma-separated list of platforms.
Push the image to a container registry.
Push the image to a container registry with authentication.
Push the image to a container registry with authentication and options.
Push multiple platform-specific images as a single multi-platform image.
Push multiple images as a multi-platform image with options.
Save the image as a tarball.
Start building an image from scratch (no base image). Use this when you want complete control over the image contents, typically for statically compiled binaries.
Set the user to run as.
Set the working directory.
Types
-type layer() :: #{media_type := binary(), digest := binary(), diff_id := binary(), size := non_neg_integer(), data := binary()}.
Functions
Add a layer to the image from a list of files.
Files are specified as {Path, Content, Mode} tuples:
Image1 = ocibuild:add_layer(Image, [
{~"/app/myapp", AppBinary, 8#755},
{~"/app/config.json", ConfigJson, 8#644}
]).
-spec add_layer(image(), [{Path :: binary(), Content :: binary(), Mode :: integer()}], map()) -> image().
Add a layer to the image from a list of files with options.
Options:
layer_type: Type of layer content (erts, deps, app) for progress displaycompression: Compression algorithm (gzip,zstd, orauto). Default:auto
Image1 = ocibuild:add_layer(Image, Files, #{layer_type => app}).
%% With explicit compression
Image2 = ocibuild:add_layer(Image, Files, #{compression => zstd}).Raises an error if compression fails (e.g., zstd requested on OTP 27).
Add an annotation to the image manifest.
Annotations are key-value metadata stored in the manifest (not the config). Some registries like GHCR display these on the package page.
Common OCI annotations:
org.opencontainers.image.description- Human-readable descriptionorg.opencontainers.image.source- URL to source codeorg.opencontainers.image.version- Version of the packaged softwareorg.opencontainers.image.authors- Contact details of maintainers
Set the default command arguments. CMD provides default arguments to the entrypoint:
Image1 = ocibuild:cmd(Image, [~"--port", ~"8080"]).
Copy files to a destination directory in the image. This is a convenience function that creates a layer with files placed under the specified destination path:
Image1 = ocibuild:copy(Image, [
{~"myapp", AppBinary},
{~"config.json", ConfigJson}
], ~"/app").Files will be created as /app/myapp and /app/config.json.
Set the entrypoint for the container. The entrypoint is the command that will be executed when the container starts.
Image1 = ocibuild:entrypoint(Image, [~"/app/myapp", ~"start"]).
Set environment variables. Environment variables are specified as a map:
Image1 = ocibuild:env(Image, #{
~"MIX_ENV" => ~"prod",
~"PORT" => ~"4000"
}).
-spec export(image(), file:filename()) -> ok | {error, term()}.
Export the image as an OCI layout directory.
Creates the standard OCI directory structure:
ok = ocibuild:export(Image, "./myimage").
%% Creates:
%% ./myimage/oci-layout
%% ./myimage/index.json
%% ./myimage/blobs/sha256/...
Expose a port.
Start building an image from a base image reference. The reference can be either a binary string or a tuple:
%% As a string (will be parsed)
{ok, Image} = ocibuild:from(~"docker.io/library/alpine:3.19").
%% As a tuple
{ok, Image} = ocibuild:from({~"ghcr.io", ~"hexpm/elixir", ~"1.16"}).
Start building an image from a base image with authentication.
-spec from(binary() | base_ref(), auth(), map()) -> {ok, image()} | {ok, [image()]} | {error, term()}.
Start building an image from a base image with authentication and options.
Options:
progress: A callback functionfun(ProgressInfo) -> okthat receives progress updates. ProgressInfo is a map with keys:phase(manifest|config|layer),bytes_received,total_bytes.platform: A specific platform to pull (e.g.,#{os => ~"linux", architecture => ~"amd64"}).platforms: A list of platforms for multi-platform builds. Returns a list of images.
Example with progress callback:
Progress = fun(#{phase := Phase, bytes_received := Recv, total_bytes := Total}) ->
io:format("~p: ~p/~p bytes~n", [Phase, Recv, Total])
end,
{ok, Image} = ocibuild:from(~"alpine:3.19", #{}, #{progress => Progress}).Example with multiple platforms (returns list of images):
{ok, Images} = ocibuild:from(~"alpine:3.19", #{}, #{
platforms => [#{os => ~"linux", architecture => ~"amd64"},
#{os => ~"linux", architecture => ~"arm64"}]
}).
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
Add a label to the image config.
Parse a platform string into a platform map.
Accepts formats like:
- "linux/amd64"
- "linux/arm64"
- "linux/arm64/v8" (with variant)
{ok, #{os := ~"linux", architecture := ~"amd64"}} =
ocibuild:parse_platform(~"linux/amd64").
Parse a comma-separated list of platforms.
{ok, [#{os := ~"linux", architecture := ~"amd64"},
#{os := ~"linux", architecture := ~"arm64"}]} =
ocibuild:parse_platforms(~"linux/amd64,linux/arm64").
-spec push(image(), Registry :: binary(), RepoTag :: binary()) -> {ok, Digest :: binary()} | {error, term()}.
Push the image to a container registry.
{ok, Digest} = ocibuild:push(Image, ~"ghcr.io", ~"myorg/myapp:v1.0.0").Returns {ok, Digest} where Digest is the sha256 digest of the pushed manifest.
-spec push(image(), Registry :: binary(), RepoTag :: binary(), auth()) -> {ok, Digest :: binary()} | {error, term()}.
Push the image to a container registry with authentication.
%% GHCR uses username + token as password
Auth = #{username => ~"github-user", password => ~"github-token"},
{ok, Digest} = ocibuild:push(Image, ~"ghcr.io", ~"myorg/myapp:v1.0.0", Auth).Returns {ok, Digest} where Digest is the sha256 digest of the pushed manifest.
-spec push(image(), Registry :: binary(), RepoTag :: binary(), auth(), map()) -> {ok, Digest :: binary()} | {error, term()}.
Push the image to a container registry with authentication and options.
Options:
chunk_size: Size in bytes for chunked uploads (default: 5MB). Blobs >= this size use OCI chunked upload. Set to a large value to disable chunked uploads.progress: A callback function for upload progress updates.
Example:
Auth = #{username => ~"user", password => ~"pass"},
Opts = #{chunk_size => 10 * 1024 * 1024}, % 10MB chunks
{ok, Digest} = ocibuild:push(Image, ~"ghcr.io", ~"myorg/myapp:v1", Auth, Opts).Returns {ok, Digest} where Digest is the sha256 digest of the pushed manifest.
-spec push_multi([image()], Registry :: binary(), RepoTag :: binary(), auth()) -> {ok, Digest :: binary()} | {error, term()}.
Push multiple platform-specific images as a single multi-platform image.
This function pushes all images and creates an OCI image index that references them. When clients pull the image, they receive the appropriate platform variant.
Each image in the list must have a platform field set.
Example:
%% Build for multiple platforms
{ok, Images} = ocibuild:from(~"alpine:3.19", #{}, #{
platforms => [#{os => ~"linux", architecture => ~"amd64"},
#{os => ~"linux", architecture => ~"arm64"}]
}),
%% Add layers to each image
Images2 = [ocibuild:copy(I, Files, ~"/app") || I <- Images],
%% Push as multi-platform image
Auth = #{username => ~"user", password => ~"pass"},
{ok, Digest} = ocibuild:push_multi(Images2, ~"ghcr.io", ~"myorg/myapp:v1", Auth).Returns {ok, Digest} where Digest is the sha256 digest of the pushed image index.
-spec push_multi([image()], Registry :: binary(), RepoTag :: binary(), auth(), map()) -> {ok, Digest :: binary()} | {error, term()}.
Push multiple images as a multi-platform image with options.
Returns {ok, Digest} where Digest is the sha256 digest of the pushed image index.
-spec save(image(), file:filename()) -> ok | {error, term()}.
Save the image as a tarball.
The resulting tarball can be loaded with docker load or podman load:
ok = ocibuild:save(Image, "myimage.tar.gz").
ok = ocibuild:save(Image, "myimage.tar.gz", #{tag => ~"myapp:1.0"}).Options:
tag: Image tag for Docker format (required for proper image naming)format:docker(default) oroci
-spec save(image(), file:filename(), map()) -> ok | {error, term()}.
-spec scratch() -> {ok, image()}.
Start building an image from scratch (no base image). Use this when you want complete control over the image contents, typically for statically compiled binaries.
{ok, Image} = ocibuild:scratch(),
Image1 = ocibuild:copy(Image, [{~"myapp", Binary}], ~"/"),
Image2 = ocibuild:entrypoint(Image1, [~"/myapp"]).
Set the user to run as.
Set the working directory.