Dagger 1.0.0 beta SDK experiments
dagger purescript gleam clojure

Files
README.md

Personal Dagger SDK experiments #

SDK tests

Targets Dagger v1.0.0-beta.21. Use the matching CLI and engine.

My experimental Dagger SDKs for Gleam, PureScript, and Clojure.

These are personal projects, not official Dagger SDKs. They are not maintained or endorsed by the Dagger team. I'm exploring what an idiomatic, typed Dagger experience can look like in each language. APIs, generated bindings, and module support may change as the experiments evolve.

These SDKs let you build container pipelines and write Dagger modules in their respective languages.

Choose an SDK #

SDK Runtime API style Documentation
Gleam Erlang/OTP Pipe-friendly builders, typed options, and Result Getting started · Writing modules
PureScript Node.js Immutable builders, option records, and Aff Getting started · Writing modules
Clojure JVM / JDK 21 Threading macros, option maps, with-open, and metadata exports Getting started · Examples

These SDKs are not published to their languages’ package registries yet. The individual guides explain how to use a local checkout as a dependency.

Setup and examples #

All SDKs target Dagger 1.0.0-beta.21. From the repository root:

mise trust
mise install
mise exec -- dagger version

Use mise exec -- or activate mise in your shell to select the pinned CLI. Clojure additionally pins its JDK and CLI in clojure/mise.toml.

Each directory includes a container pipeline and a module exposing ordinary language functions. Their guides include prerequisites, commands, and expected output.

Continuous integration #

GitHub Actions runs independent SDK jobs for unit tests, generated-code checks, and live pipeline examples on Linux. Module smoke tests are available through the workflow’s manual dispatch option and locally with mise exec -- python3 scripts/test-module.py <sdk>.

What can I do with them? #

  • Build pipelines using containers, directories, files, caches, and other Dagger objects.
  • Compose lazy operations and execute them when you request a result.
  • Define Dagger modules with typed arguments, return values, constructors, and state.
  • Generate client bindings from a Dagger introspection schema.
  • Test application code with an injected transport before connecting to an engine.

Each SDK follows its language's conventions. They share the same goals, but their APIs and supported features are not identical; check the language-specific guide for details and limitations.

Call a module function from code #

The examples use the hello-gleam module generated by this repository's Gleam SDK. It exposes hello(name: String): String and containerEcho(message: String): Container. Its implementation language does not restrict callers: all SDKs can call the same module.

Create the dependency inside this checkout, whose dagger.toml registers both SDKs:

export DAGGER_SDK_DIR=/absolute/path/to/dagger-sdk
cd "$DAGGER_SDK_DIR"
dagger module init gleam --name hello-gleam --path ./hello-gleam -y

Each caller below installs it as a dependency. dagger generate -y generates dagger/hello_gleam or Dagger.HelloGleam and the matching root client accessor. These bindings are specific to the installed dependency; they are not part of the SDK's bundled core schema. Do not substitute a same-named remote module without checking its API.

Gleam caller #

From the same parent directory:

dagger module init gleam --name caller-gleam --path ./caller-gleam -y
cd caller-gleam
dagger module client add ../hello-gleam --sdk=gleam -y
dagger generate -y

Replace src/dagger_main.gleam with:

import dagger/client
import dagger/container
import dagger/hello_gleam
import dagger/mod
import dagger/mod/argument
import dagger/mod/codec
import gleam/result

pub fn main() {
  let assert Ok(_) = mod.serve(definition(), [])
}

pub fn definition() -> mod.Object {
  mod.object("CallerGleam")
  |> mod.with_function(mod.function(
    "hello",
    argument.required("name", codec.string()),
    codec.string(),
    fn(context, name) {
      context.client
      |> client.hello_gleam
      |> hello_gleam.hello(name)
      |> result.map_error(codec.SdkError)
    },
  ))
  |> mod.with_function(mod.function(
    "echo_message",
    argument.required("message", codec.string()),
    codec.string(),
    fn(context, message) {
      context.client
      |> client.hello_gleam
      |> hello_gleam.container_echo(message)
      |> container.stdout
      |> result.map_error(codec.SdkError)
    },
  ))
}

hello_gleam.hello calls the dependency and returns a Result(String, Error). hello_gleam.container_echo returns a lazy container; container.stdout executes the selection and retrieves its output. Handler errors are converted to the module runtime's error type with result.map_error(codec.SdkError).

dagger api call -m . hello --name developer
dagger api call -m . echo-message --message "Called from Gleam"

PureScript caller #

Return to the parent directory containing hello-gleam, then create another caller:

dagger module init purescript --name caller-purescript --path ./caller-purescript -y
cd caller-purescript
dagger module client add ../hello-gleam --sdk=purescript -y
dagger generate -y

Replace src/Main.purs with:

module Main where

import Prelude
import Dagger.Container as Container
import Dagger.HelloGleam as HelloGleam
import Dagger.Modules as Module
import Dagger.Query as Query
import Effect (Effect)

main :: Effect Unit
main = Module.runModule
  [ Module.object_ "CallerPurescript" {}
      [ Module.function "hello" "Call the dependency's greeting function."
          (Module.argument "name")
          (\client name -> Query.helloGleam client # HelloGleam.hello name)
      , Module.function "echoMessage" "Read output from a dependency's container."
          (Module.argument "message")
          (\client message -> Query.helloGleam client
            # HelloGleam.containerEcho message
            # Container.stdout)
      ]
  ]

HelloGleam.hello returns Aff String. HelloGleam.containerEcho returns a lazy Container, and Container.stdout returns Aff String. Module.runModule provides the connected client to each handler.

dagger api call -m . hello --name developer
dagger api call -m . echo-message --message "Called from PureScript"

The CLI commands invoke your caller module. The calls to the dependency happen inside the language code above, through generated SDK bindings. Neither example launches a CLI subprocess to call the dependency.

These walkthroughs require Dagger and a working engine; the custom SDK runtime installs the language toolchain. For local compilation, tests, option records, and object arguments, see the Gleam guide and PureScript guide.

Experimental status #

Local tests cover client behavior and module protocols, but mocked tests are not a substitute for engine integration. See each SDK's README for its validation status, engine requirements, and remaining limitations.

The bundled bindings target the released v1.0.0-beta.21 schema. Module scopes generate dependency bindings with dagger generate -y; commit those files. Runtime execution uses committed code. Standalone clients can regenerate their core bindings using the instructions in each language guide. Workspace-managed standalone client scopes are not yet supported.

Work on the SDKs #

Start with the relevant project's development guide:

After changing a bootstrap adapter, run node scripts/bootstrap-sdks.mjs with the beta CLI to refresh its committed Go/Python bindings. Run node scripts/test-modules.mjs for live scaffolding and invocation checks for both languages. Set DAGGER_CLI_BIN to use a CLI outside PATH.

Change generators rather than editing generated bindings directly. For bug reports, include the SDK, compiler/runtime and Dagger versions, and a small reproduction. Ideas for making each API feel more natural in its language are welcome.

License #

Original SDK code is MIT licensed. Upstream Dagger schemas and schema-derived documentation retain their Apache-2.0 licensing. See the Gleam notice, PureScript notice, and Clojure notice for attribution.