Skip to main content

Get Started with the Dagger Elixir SDK

Experimental
warning

The Dagger Elixir SDK is currently experimental and is subject to change.

Introduction

This tutorial teaches you the basics of using Dagger in Elixir. You will learn how to:

  • Install the Elixir SDK
  • Create an Elixir CI tool to test an application
  • Improve the Elixir CI tool to test the application against multiple Elixir and OTP versions

Requirements

This tutorial assumes that:

  • You have a basic understanding of the Elixir programming language. If not, read the Elixir documentation.
  • You have an Elixir development environment with Elixir 1.14 or later and Erlang/OTP 25 or later. If not, install Elixir and Erlang/OTP.
  • You have the Dagger CLI installed on the host system. If not, install the Dagger CLI.
  • You have Docker installed and running on the host system. If not, install Docker.

Step 1: Create an Elixir project

Create an Elixir project with mix:

mix new elixir_with_dagger

Step 2: Install the Dagger Elixir SDK

note

This Dagger Elixir SDK requires Elixir 1.14 or later with Erlang OTP 23 or later. Using Erlang OTP version 25 is recommended.

In your project directory, open mix.exs and add {:dagger, "~> 0.8"} to the list in the deps function:

def deps do
[
{:dagger, "~> 0.8", only: [:dev, :test]}
]
end

Run mix deps.get to fetch the Elixir SDK from Hex.pm:

mix deps.get

Step 3: Create the Mix task

Create a new module and Mix task to test the project at lib/mix/tasks/elixir_with_dagger.test.ex:

defmodule Mix.Tasks.ElixirWithDagger.Test do
use Mix.Task

@impl Mix.Task
def run(_args) do
Application.ensure_all_started(:dagger)

client = Dagger.connect!()

app =
client
|> Dagger.Client.host()
|> Dagger.Host.directory(".", exclude: ["deps", "_build"])

{:ok, _} =
client
|> Dagger.Client.container()
|> Dagger.Container.from("hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0")
|> Dagger.Container.with_mounted_directory("/app", app)
|> Dagger.Container.with_workdir("/app")
|> Dagger.Container.with_exec(~w"mix local.hex --force")
|> Dagger.Container.with_exec(~w"mix local.rebar --force")
|> Dagger.Container.with_exec(~w"mix deps.get")
|> Dagger.Container.with_exec(~w"mix test")
|> Dagger.Sync.sync()

Dagger.close(client)

IO.puts("Tests succeeded!")
end
end

This module performs the following operations:

  • It starts all applications related to the Dagger Elixir SDK with Application.ensure_all_started(:dagger).
  • It creates a Dagger client with Dagger.connect!/1.
  • It uses the client's Dagger.Client.host/1 and Dagger.Host.directory/3 to obtain a reference to the host directory. It additionally uses the exclude filter in Dagger.Host.directory/3 to filter unwanted files and directories.
  • It uses the client's Dagger.Client.container/2 and Dagger.Container.from/2 to initialize a new container from the hexpm/elixir:1.15.4-erlang-25.3.2.5-ubuntu-bionic-20230126 base image.
  • It uses Dagger.Container.with_mounted_directory/3 to mount the project's source files into the container.
  • It uses Dagger.Container.with_exec/3 to define the commands to be executed in the container - in this case, commands such as mix deps get, which downloads dependencies and mix test, which runs unit tests. Each invocation of with_exec returns a revised Container with the results of command execution.
  • It uses Dagger.Container.stdout/1 to get the output of the last execution command.
  • It uses Dagger.close/1 to close the client connection.

Run the Mix task by executing the command below from the project directory:

dagger run mix elixir_with_dagger.test

The dagger run command executes the specified command in a Dagger session and displays live progress. Here is an example of the output:

❯ dagger run mix elixir_with_dagger.test
┣─╮
│ ▽ init
│ █ [0.76s] connect
│ ┣ [0.52s] starting engine
│ ┣ [0.18s] starting session
│ ┻
[1.75s] mix elixir_with_dagger.test
┃ Tests succeeded!
┣─╮
│ ▽ host.directory .
│ █ [0.23s] upload .
│ ┣ [0.15s] transferring eyJvd25lcl9jbGllbnRfaWQiOiIwNWNmN2E4YTF4dDZ0dG52amUwbG1yeTYxIiwicGF0aCI6Ii4iLCJpbmNsdWRlX3BhdHRlcm5zIjpudWxsLCJleGNsdWRlX3BhdHRlcm5zIjpbImRlcHMiLCJfYnVpbGQiXSwiZm9sbG93X3BhdGhzIjpudWxsLCJyZWFkX3NpbmdsZV9maWxlX29ubHkiOmZhbHNlLCJtYXhfZmlsZV9zaXplIjowfQ==:
│ █ CACHED copy . (exclude deps, _build)
│ ┣─╮ copy . (exclude deps, _build)
│ ┻ │
┣─╮ │
│ ▽ │ from hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0
│ █ │ [0.18s] resolve image config for docker.io/hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0
│ █ │ [0.04s] pull docker.io/hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0
│ ┣ │ [0.04s] resolve docker.io/hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0@sha256:d77ef43aeb585ec172e290c7ebc171a16e21ebaf7c9ed09b596b9db55c848f00
│ ┣─┼─╮ pull docker.io/hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0
│ ┻ │ │
█◀──┴─╯ CACHED exec mix local.hex --force
█ CACHED exec mix local.rebar --force
█ CACHED exec mix deps.get
█ CACHED exec mix test

• Engine: fd814943769d (version v0.8.7)
2.51s ✔ 205

Step 4: Test against multiple Elixir and Erlang/OTP versions

Now that the Elixir CI tool can test the application against a specified Elixir and Erlang/OTP version, the next step is to extend it for multiple Elixir and Erlang/OTP versions.

Replace the lib/mix/tasks/elixir_with_dagger.test.ex file from the previous step with the version below:

defmodule Mix.Tasks.ElixirWithDagger.Test do
use Mix.Task

@impl Mix.Task
def run(_args) do
Application.ensure_all_started(:dagger)

client = Dagger.connect!()

app =
client
|> Dagger.Client.host()
|> Dagger.Host.directory(".", exclude: ["deps", "_build"])

[
{"1.14.4", "erlang-25.3.2", "alpine-3.18.0"},
{"1.15.0-rc.2", "erlang-26.0.1", "alpine-3.18.2"}
]
|> Task.async_stream(
fn {elixir_version, erlang_version, os_version} ->
elixir =
client
|> Dagger.Client.container()
|> Dagger.Container.from(
"hexpm/elixir:#{elixir_version}-#{erlang_version}-#{os_version}"
)
|> Dagger.Container.with_mounted_directory("/app", app)
|> Dagger.Container.with_workdir("/app")
|> Dagger.Container.with_exec(~w"mix local.hex --force")
|> Dagger.Container.with_exec(~w"mix local.rebar --force")
|> Dagger.Container.with_exec(~w"mix deps.get")
|> Dagger.Container.with_exec(~w"mix test")

IO.puts("Starting tests for Elixir #{elixir_version} with Erlang OTP #{erlang_version}")
{:ok, _} = Dagger.Sync.sync(elixir)
IO.puts("Tests for Elixir #{elixir_version} with Erlang OTP #{erlang_version} succeeded!")
end,
timeout: :timer.minutes(10)
)
|> Stream.run()


Dagger.close(client)

IO.puts("All tasks have finished")
end
end

This version has additional support for testing and building against multiple Elixir and Erlang/OTP versions:

  • It defines the test matrix, consisting of a list of Elixir and Erlang/OTP version pairs.
  • It uses Task.async_stream/3 to run tests against each version pair concurrently.
  • It uses Stream.run/1 to await all tasks.

Run the tool again by executing the command below:

dagger run mix elixir_with_dagger.test

The tool tests the application, logging its operations to the console as it works. If all tests pass, it displays the final output below:

❯ dagger run mix elixir_with_dagger.test
┣─╮
│ ▽ init
│ █ [0.59s] connect
│ ┣ [0.53s] starting engine
│ ┣ [0.01s] starting session
│ ┻
[2.09s] mix elixir_with_dagger.test
┃ Starting tests for Elixir 1.14.4 with Erlang OTP erlang-25.3.2
┃ Starting tests for Elixir 1.15.0-rc.2 with Erlang OTP erlang-26.0.1
┃ Tests for Elixir 1.14.4 with Erlang OTP erlang-25.3.2 succeeded!
┃ Tests for Elixir 1.15.0-rc.2 with Erlang OTP erlang-26.0.1 succeeded!
┃ All tasks have finished
┣─╮
│ ▽ host.directory .
│ █ [0.39s] upload .
│ ┣ [0.16s] transferring eyJvd25lcl9jbGllbnRfaWQiOiJsNHJlemx0cW10dHd3MHhrNzJ6N3l1eGg1IiwicGF0aCI6Ii4iLCJpbmNsdWRlX3BhdHRlcm5zIjpudWxsLCJleGNsdWRlX3BhdHRlcm5zIjpbImRlcHMiLCJfYnVpbGQiXSwiZm9sbG93X3BhdGhzIjpudWxsLCJyZWFkX3NpbmdsZV9maWxlX29ubHkiOmZhbHNlLCJtYXhfZmlsZV9zaXplIjowfQ==:
│ █ CACHED copy . (exclude deps, _build)
│ ┣─╮ copy . (exclude deps, _build)
│ ┻ │
┣─╮ │
│ ▽ │ from hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0
│ █ │ [0.22s] resolve image config for docker.io/hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0
┣─┼─┼─╮
│ │ │ ▽ from hexpm/elixir:1.15.0-rc.2-erlang-26.0.1-alpine-3.18.2
│ │ │ █ [0.35s] resolve image config for docker.io/hexpm/elixir:1.15.0-rc.2-erlang-26.0.1-alpine-3.18.2
│ █ │ │ [0.04s] pull docker.io/hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0
│ ┣ │ │ [0.03s] resolve docker.io/hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0@sha256:d77ef43aeb585ec172e290c7ebc171a16e21ebaf7c9ed09b596b9db55c848f00
│ ┣─┼─┼─╮ pull docker.io/hexpm/elixir:1.14.4-erlang-25.3.2-alpine-3.18.0
│ ┻ │ │ │
█◀──┤─┼─╯ CACHED exec mix local.hex --force
█ │ │ CACHED exec mix local.rebar --force
█ │ │ CACHED exec mix deps.get
█ │ │ CACHED exec mix test
│ │ █ [0.04s] pull docker.io/hexpm/elixir:1.15.0-rc.2-erlang-26.0.1-alpine-3.18.2
│ │ ┣ [0.03s] resolve docker.io/hexpm/elixir:1.15.0-rc.2-erlang-26.0.1-alpine-3.18.2@sha256:20eb9af6c46749c7d4a18de9aa36950f591ffa0e19e219ac6b21c58d01cfb07f
│ ╭─┼─┫ pull docker.io/hexpm/elixir:1.15.0-rc.2-erlang-26.0.1-alpine-3.18.2
│ │ │ ┻
█◀┴─╯ CACHED exec mix local.hex --force
█ CACHED exec mix local.rebar --force
█ CACHED exec mix deps.get
█ CACHED exec mix test

• Engine: fd814943769d (version v0.8.7)
2.69s ✔ 309

Conclusion

This tutorial introduced you to the Dagger Elixir SDK. It explaned how to install the SDK and use it with an Elixir project. It also provided a working example of a CI tool powered by the SDK, demonstrating how to test a project against multiple Elixir and Erlang/OTP versions in parallel.

Use the HexDocs SDK Reference to learn more about the Dagger Elixir SDK.