DockerWrapper
A typed Elixir wrapper for the Docker CLI. Struct-based commands, pipeline composition, and BEAM-native extensions for supervised containers and streaming output.
Elixir port of docker-wrapper (Rust).
Features
- 100+ Docker commands covering containers, images, networks, volumes, compose, buildx, swarm, manifests, and more
- Struct-based builders with pipeline composition for every command
- Typed results with
{:ok, result} | {:error, reason}everywhere - JSON parsing for ps, images, inspect, network/volume listing, and search
- Platform auto-detection for docker, podman, and nerdctl
- Telemetry integration for observability
Docker.Supervised-- GenServer for OTP-supervised container lifecycle with health checksDocker.Stream-- Port-based streaming fordocker logs -f,docker events, buildsDocker.Generic-- escape hatch for any command not yet covered- Full Compose support with shared project-level options (
-f,-p,--profile)
Quick start
# Run a container
import Docker.Commands.Run
{:ok, container_id} =
"redis:7-alpine"
|> new()
|> name("my-redis")
|> port(6379, 6379)
|> detach()
|> Docker.run()
# List running containers
{:ok, containers} = Docker.ps()
# Exec into a container
{:ok, output} =
Docker.Commands.Exec.new("my-redis", ["redis-cli", "ping"])
|> Docker.exec_cmd()
# Stop and remove
{:ok, _} = Docker.stop("my-redis")
{:ok, _} = Docker.rm("my-redis")Supervised containers
Run containers under OTP supervision with automatic health checks and cleanup on termination:
run_cmd =
Docker.Commands.Run.new("redis:7-alpine")
|> Docker.Commands.Run.name("supervised-redis")
|> Docker.Commands.Run.port(6379, 6379)
{:ok, pid} = Docker.Supervised.start_link(run_cmd,
health_interval: 5_000,
rm_on_terminate: true
)
Docker.Supervised.container_id(pid) #=> "abc123..."
Docker.Supervised.healthy?(pid) #=> :healthy
# Or under a supervisor
children = [
{Docker.Supervised, {run_cmd, name: MyApp.Redis}}
]
Supervisor.start_link(children, strategy: :one_for_one)Streaming
Stream output from long-running commands via Port:
{:ok, stream} =
Docker.Commands.Logs.new("my-container")
|> Docker.Commands.Logs.follow()
|> Docker.Stream.start_link(subscriber: self())
receive do
{:docker_stream, ^stream, {:stdout, line}} -> IO.puts(line)
{:docker_stream, ^stream, {:exit, 0}} -> :done
endCompose
import Docker.Commands.Compose.Up
new()
|> file("docker-compose.yml")
|> detach()
|> wait()
|> service("redis")
|> service("web")
|> Docker.compose_up()Images and builds
# Pull an image
{:ok, _} = Docker.pull("nginx:alpine")
# Build with buildx for multiple platforms
import Docker.Commands.Builder.Build
"."
|> new()
|> tag("myapp:latest")
|> platform("linux/amd64")
|> platform("linux/arm64")
|> push()
|> Docker.generic() # or use the builder facadeNetworks and volumes
# Create a network
{:ok, _} = Docker.network_create("my-net")
# Create a volume
{:ok, _} = Docker.volume_create("my-data")
# Run a container on the network with the volume
"postgres:16"
|> Docker.Commands.Run.new()
|> Docker.Commands.Run.name("my-pg")
|> Docker.Commands.Run.network("my-net")
|> Docker.Commands.Run.volume("my-data", "/var/lib/postgresql/data")
|> Docker.Commands.Run.env("POSTGRES_PASSWORD", "secret")
|> Docker.Commands.Run.detach()
|> Docker.run()Configuration
The Docker binary, working directory, timeout, and environment can be configured per-call:
config = Docker.Config.new(
binary: "/usr/local/bin/podman",
working_dir: "/my/project",
timeout: 60_000,
env: [{"DOCKER_HOST", "tcp://remote:2375"}]
)
Docker.ps(config: config)
Or set DOCKER_PATH to override the binary globally.
Installation
Add docker_wrapper to your dependencies in mix.exs:
def deps do
[
{:docker_wrapper, "~> 0.1"}
]
endLicense
MIT