Personal Dagger SDK experiments #
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:
- Gleam: development, tests, and code generation
- PureScript: development, tests, and code generation
- Clojure: development, tests, and code generation
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.
- Go 47%
- Python 19%
- Gleam 16%
- PureScript 11%
- Clojure 7%
- 4 more languages <1%