TestcontainerEx.ElixirContainer (testcontainer_ex v0.5.1)

Copy Markdown View Source

Provides functionality for creating and managing Elixir/Erlang container configurations — useful for testing distributed Erlang, remote deployment, clustering, and running Elixir releases inside containers.

Basic usage

{:ok, container} = TestcontainerEx.start_container(ElixirContainer.new())

With a specific Elixir version

config =
  ElixirContainer.new()
  |> ElixirContainer.with_image("elixir:1.17-otp-27")

{:ok, container} = TestcontainerEx.start_container(config)

With Erlang distribution enabled

config =
  ElixirContainer.new()
  |> ElixirContainer.with_cookie("my-secret-cookie")
  |> ElixirContainer.with_node_name("app@192.168.1.100")
  |> ElixirContainer.with_distribution_port(9100)

{:ok, container} = TestcontainerEx.start_container(config)

Mount a local Mix project

config =
  ElixirContainer.new()
  |> ElixirContainer.with_project("/path/to/my_app")

{:ok, container} = TestcontainerEx.start_container(config)

Connect from your host machine

After the container starts, the Erlang distribution port and EPMD port are exposed. From your local IEx session:

Node.connect(:"app@<host>")
Node.list()  # => [:"app@<host>"]

Copy a module into a running container and call it remotely

ElixirContainer.copy_module(container, MyModule, conn)
:rpc.call(:"app@<host>", MyModule, :hello, [])

Run a release with remote shell

config =
  ElixirContainer.new()
  |> ElixirContainer.with_image("my_app:latest")
  |> ElixirContainer.with_release("my_app")
  |> ElixirContainer.with_cookie("release-cookie")

{:ok, container} = TestcontainerEx.start_container(config)

Summary

Functions

Connects from the host to the container's Erlang node.

Returns the full node name for connecting from the host.

Copies an Elixir module's source file into the container.

Returns the default distribution port (9100).

Returns the default image name without tag.

Returns the default EPMD port (4369).

Returns the mapped distribution port on the host machine.

Returns the mapped EPMD port on the host machine.

Creates a new ElixirContainer with default configuration.

Evaluates code inside the container's Erlang node via RPC.

Runs a Mix task inside the container.

Sets the check image regex used to validate the image name.

Sets a custom command to run inside the container.

Sets the Erlang distribution cookie.

Sets the fixed Erlang distribution port (default: 9100).

Sets environment variables inside the container.

Sets the container image (e.g. "elixir:1.17-otp-27" or a custom image with Elixir/OTP pre-installed).

Sets the Erlang node name for distribution.

Bind-mounts a local Mix project directory into the container.

Configures the container to run a release.

Sets the arguments passed to the release binary (default: ["start"]).

Enables or disables container reuse.

Sets additional vm.args entries for the Erlang VM.

Sets the wait timeout in milliseconds.

Types

t()

@type t() :: %TestcontainerEx.ElixirContainer{
  check_image: term(),
  cmd: term(),
  cookie: term(),
  dist_port: term(),
  env_vars: term(),
  image: term(),
  node_name: term(),
  project_path: term(),
  release_args: term(),
  release_name: term(),
  reuse: term(),
  vm_args: term(),
  wait_timeout: term(),
  working_dir: term()
}

Functions

connect(container, conn)

Connects from the host to the container's Erlang node.

Requires the container to have been started with with_node_name/2 and with_cookie/2.

{:ok, container} = TestcontainerEx.start_container(
  ElixirContainer.new()
  |> ElixirContainer.with_cookie("secret")
  |> ElixirContainer.with_node_name("app@192.168.1.100")
)

ElixirContainer.connect(container, conn)
# => true

connection_node(container)

@spec connection_node(TestcontainerEx.Container.Config.t()) :: String.t() | nil

Returns the full node name for connecting from the host.

Returns nil if no node_name is configured.

node_str = ElixirContainer.connection_node(container)
# => "app@localhost:9100"
Node.connect(String.to_atom(node_str))

copy_module(container, module, conn, source_path \\ nil)

@spec copy_module(
  TestcontainerEx.Container.Config.t(),
  module(),
  Req.Request.t(),
  String.t() | nil
) ::
  :ok | {:error, term()}

Copies an Elixir module's source file into the container.

The module is compiled inside the container and can then be called via :rpc.call/4.

ElixirContainer.copy_module(container, MyApp.Helper, conn)

:rpc.call(:"app@localhost:9100", MyApp.Helper, :hello, [])
# => :world

The source file is expected to be at lib/<module_path>.ex relative to the project root, or at the given source_path.

default_dist_port()

@spec default_dist_port() :: 9100

Returns the default distribution port (9100).

default_image()

@spec default_image() :: String.t()

Returns the default image name without tag.

epmd_port()

@spec epmd_port() :: 4369

Returns the default EPMD port (4369).

mapped_dist_port(container)

@spec mapped_dist_port(TestcontainerEx.Container.Config.t()) :: integer() | nil

Returns the mapped distribution port on the host machine.

mapped_epmd_port(container)

@spec mapped_epmd_port(TestcontainerEx.Container.Config.t()) :: integer() | nil

Returns the mapped EPMD port on the host machine.

new()

@spec new() :: t()

Creates a new ElixirContainer with default configuration.

Defaults:

  • Image: elixir:latest
  • Wait timeout: 120s
  • No distribution (cookie/node_name not set)
  • No project mounted

remote_eval(container, conn, code)

@spec remote_eval(TestcontainerEx.Container.Config.t(), Req.Request.t(), String.t()) ::
  {:ok, term()} | {:error, term()}

Evaluates code inside the container's Erlang node via RPC.

ElixirContainer.remote_eval(container, conn, "IO.puts(:hello)")
# => "hello"

run_mix(container, conn, task)

@spec run_mix(TestcontainerEx.Container.Config.t(), Req.Request.t(), String.t()) ::
  :ok | {:error, term()}

Runs a Mix task inside the container.

ElixirContainer.run_mix(container, conn, "test")
ElixirContainer.run_mix(container, conn, "ecto.migrate")

with_check_image(config, check_image)

@spec with_check_image(t(), String.t() | Regex.t()) :: t()

Sets the check image regex used to validate the image name.

with_cmd(config, cmd)

@spec with_cmd(t(), [String.t()]) :: t()

Sets a custom command to run inside the container.

Overrides the default IEx or release command. Useful for running custom scripts or one-off commands.

config
|> ElixirContainer.with_cmd(["mix", "test"])

with_cookie(config, cookie)

@spec with_cookie(t(), String.t()) :: t()

Sets the Erlang distribution cookie.

Required for node-to-node communication. Both nodes must use the same cookie.

config
|> ElixirContainer.with_cookie("my-secret-cookie")

with_distribution_port(config, port)

@spec with_distribution_port(t(), pos_integer()) :: t()

Sets the fixed Erlang distribution port (default: 9100).

Erlang uses a random port by default, which is difficult with containers. Setting a fixed port makes it possible to expose it through Docker.

config
|> ElixirContainer.with_distribution_port(9100)

with_env_vars(config, vars)

@spec with_env_vars(t(), map()) :: t()

Sets environment variables inside the container.

These are merged with any distribution-related env vars set automatically.

config
|> ElixirContainer.with_env_vars(%{
  "MIX_ENV" => "prod",
  "DATABASE_URL" => "postgres://..."
})

with_image(config, image)

@spec with_image(t(), String.t()) :: t()

Sets the container image (e.g. "elixir:1.17-otp-27" or a custom image with Elixir/OTP pre-installed).

with_node_name(config, node_name)

@spec with_node_name(t(), String.t()) :: t()

Sets the Erlang node name for distribution.

Use long names (--name) for cross-machine connections, short names (--sname) for same-network connections.

config
|> ElixirContainer.with_node_name("app@192.168.1.100")

When set, EPMD port 4369 and the distribution port are automatically exposed.

with_project(config, path)

@spec with_project(t(), String.t()) :: t()

Bind-mounts a local Mix project directory into the container.

The project is mounted at /app inside the container. Useful for running tests, starting a remote IEx session, or deploying code.

config
|> ElixirContainer.with_project("/path/to/my_app")

with_release(config, name)

@spec with_release(t(), String.t()) :: t()

Configures the container to run a release.

When set, the container starts the release instead of an IEx session. The release binary is expected to be at /app/bin/<release_name>.

config
|> ElixirContainer.with_release("my_app")
|> ElixirContainer.with_release_args(["start"])

with_release_args(config, args)

@spec with_release_args(t(), [String.t()]) :: t()

Sets the arguments passed to the release binary (default: ["start"]).

config
|> ElixirContainer.with_release_args(["start_iex"])

with_reuse(config, reuse)

@spec with_reuse(t(), boolean()) :: t()

Enables or disables container reuse.

with_vm_args(config, args)

@spec with_vm_args(t(), [String.t()]) :: t()

Sets additional vm.args entries for the Erlang VM.

These are appended to the generated vm.args file inside the container. Useful for tuning distribution, heart, or other kernel settings.

config
|> ElixirContainer.with_vm_args([
  "-kernel inet_dist_listen_min 9100",
  "-kernel inet_dist_listen_max 9100"
])

with_wait_timeout(config, timeout)

@spec with_wait_timeout(t(), pos_integer()) :: t()

Sets the wait timeout in milliseconds.