Get Started with the Dagger Elixir SDK
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
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
andDagger.Host.directory/3
to obtain a reference to the host directory. It additionally uses theexclude
filter inDagger.Host.directory/3
to filter unwanted files and directories. - It uses the client's
Dagger.Client.container/2
andDagger.Container.from/2
to initialize a new container from thehexpm/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 asmix deps get
, which downloads dependencies andmix test
, which runs unit tests. Each invocation ofwith_exec
returns a revisedContainer
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 ✔ 20 ∅ 5
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 ✔ 30 ∅ 9
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.