How do I migrate from Jenkins to Dagger?
You do not rewrite your Jenkinsfiles as Dagger code. Dagger’s official modules find the tests, linters, and builds in your repository and run them as checks. The checks run the same way on a laptop and in CI. A small Dang module holds each step that no official module covers, such as a Docker image push. After the move, Cloud Checks can run the checks on every push and pull request with no CI file. Jenkins can also stay the trigger and call dagger check from a short Jenkinsfile.
Do I have to rewrite my Jenkinsfiles?
No. You do not have to rewrite the work that an official module does. A Dagger module is a package of functions that build and test a project. The Dagger team maintains official modules for common tools. Each one finds the files of its tool in your repository. For example, the Go module finds each go.mod, and makes the tests of each package a check. A check is a function that passes or fails, such as a test run or a linter (Checking your code).
The module reference lists the official modules, such as Go, golangci-lint, Jest, pytest, ShellCheck, and Helm. For Maven projects, see How do I run Maven builds the same way locally and in CI?.
You write code only for work that no official module does, such as a release or a deploy. You do not copy the stages or the Groovy of the Jenkinsfile.
The examples in this guide use a Go service named orders. Its Jenkinsfile tests with the race detector, builds a binary, and publishes a Docker image from main:
pipeline {
agent { label 'go && docker' }
stages {
stage('Test') {
steps {
sh 'go test -race ./...'
}
}
stage('Build') {
steps {
sh 'CGO_ENABLED=0 go build -o dist/orders ./cmd/orders'
archiveArtifacts artifacts: 'dist/orders', fingerprint: true
}
}
stage('Publish') {
when { branch 'main' }
environment {
REGISTRY_CREDS = credentials('registry')
}
steps {
sh 'echo $REGISTRY_CREDS_PSW | docker login registry.example.com -u $REGISTRY_CREDS_USR --password-stdin'
sh 'docker build -t registry.example.com/orders:$GIT_COMMIT .'
sh 'docker push registry.example.com/orders:$GIT_COMMIT'
}
}
}
}
Install the official modules for your repository
First, install the Dagger CLI. Then go to the root of your repository and create the workspace:
dagger init
The workspace is the Dagger configuration of the repository, in a file named dagger.toml (Quickstart). In a terminal, dagger init asks which modules to install. Without a terminal, it prints the next commands:
Workspace configuration initialized at ./dagger.toml
To continue setup, run these commands in order:
#!/bin/sh
# 1. Find and install suitable modules
dagger module recommend
# 2. Enable cloud checks
dagger cloud checks on
Install the modules that Dagger recommends. To skip the prompt, add -y:
dagger module recommend -y
In the orders repository, the command installs the Go module and adds it to dagger.toml:
[modules.go]
source = "dagger.io/go"
The command also writes dagger.lock. This file pins each module and image that the workspace uses to an exact version. Commit it with dagger.toml. If Dagger recommends no module for one of your tools, search for one by name, such as dagger module search eslint.
Run the checks on your laptop
List the checks that the modules found:
dagger check -l
MODULE GO-PACKAGE GO-TEST CHECK DESCRIPTION
go * stale staleness check: run go generate in this package
go * * test Run this test.
# Use --all to expand collections and list each item.
A * stands for each package or test that the module found. Run all the checks with dagger check. The command exits with a non-zero code when a check fails, as a failed sh step does in Jenkins.
Compare the list with the stages of the Jenkinsfile. The test check replaces go test ./.... The stale check is new. It fails when go generate would change a committed file.
The Jenkins stage uses -race. Put the flag in the goflags setting of the Go module. The value starts with a dash, so put -- before it:
dagger module settings go goflags -- -race
By default, the Go module runs tests in a golang Alpine image. The race detector needs cgo, and that image has no C compiler, so the test check fails:
CHECKS ✘ 1 failed ✔ 1 passed
✘ dag://?check=test&go-package=store 1.4s ERROR
✘ withExec otelgotest ./store (experimentalPrivilegedNesting: true) 0.0s ERROR
┃ go: -race requires cgo; enable cgo by setting CGO_ENABLED=1
! exit code: 2
✔ dag://?check=go/packages/generate/stale 1.3s OK
RUN LOCALLY
dagger check "dag://?check=test&go-package=store"
On the Jenkins agent, the C compiler came from the machine. With Dagger, it comes from a container that your own module supplies.
Move custom steps into a small Dang module
Write a module for the parts that the official modules cannot know about your project. In orders, these are a test image with a C compiler, the Docker image, and the push. Use Dang, the native language of Dagger for modules. It has no code generation and no build step. Install the Dang SDK and create the module:
dagger module install dagger.io/sdk/dang
dagger module init dang -y
dagger module init with no name creates .dagger/modules/orders-dev, named after the project directory. It also makes the module the workspace main module, so commands accept its functions without the module name. Replace .dagger/modules/orders-dev/main.dang with this code. OrdersDev stands in for the type name that dagger module init generated:
"""
Build steps of the orders service that no official module covers.
"""
type OrdersDev {
"""
A Debian-based Go image. It has gcc, which the race detector needs.
"""
pub testRuntime: Container! {
container.from("golang:1.26-bookworm")
}
"""
The service image, built from the Dockerfile at the repository root.
"""
pub image(ws: Workspace!): Container! {
ws.directory("/", exclude: [".git", ".dagger"]).dockerBuild
}
"""
Build the image and push it to a registry. Returns the address with its digest.
"""
pub publish(ws: Workspace!, address: String!, username: String!, password: Secret!): String! {
image(ws)
.withRegistryAuth(address, username, password)
.publish(address)
}
}
Give the test image to the base setting of the Go module. Dagger writes a module reference, dag://orders-dev/test-runtime, to dagger.toml:
dagger module settings go base test-runtime
Run the checks again:
dagger check
CHECKS ✔ 3 passed
✔ dag://?check=dang-sdk/generate/stale 1.0s OK
✔ dag://?check=go/packages/generate/stale 1.6s OK
✔ dag://?check=test&go-package=store 2.8s OK
TESTS
✔ example.com/orders/store 2 passed
✔ 2 passed
The Dang SDK adds its own stale check for the generated files of the module. The image function builds the same Dockerfile that docker build used, on the Dagger engine.
How Jenkins concepts map to Dagger
Each Jenkins concept has a place in Dagger, but the work moves out of the Jenkinsfile and into modules. The table summarizes the map, and the paragraphs after it give the details and sources.
| Jenkins | Dagger |
|---|---|
| Jenkinsfile stages and steps | Checks from official modules, and functions in your module |
| Shared library | A Dagger module in its own Git repository, installed at a version |
| Plugins for build tools | Official modules, or modules from dagger module search |
| Credentials | Dagger secrets from a provider, such as a file or Vault |
| Agents and labels | Containers that modules define, on a Dagger engine |
archiveArtifacts |
export of a file that a function returns |
post notifications |
Stay in Jenkins while Jenkins is the trigger |
Stages and steps. A Jenkins stage groups steps, such as sh 'go test -race ./...'. In Dagger, the test check of the Go module replaces that stage. A stage with project-specific commands becomes a function in your Dang module.
Shared libraries. A Jenkins shared library is Groovy code in its own repository, which a Jenkinsfile loads with @Library('name@version'). The Dagger equivalent is a module in its own Git repository. Tag it, and install it in each repository with dagger module install github.com/yourorg/yourrepo/path@v0.1.0 (Dang SDK: packaging and release).
Plugins. Jenkins has “over a thousand different plugins”, which an administrator installs on the controller. In Dagger, a module takes the place of a plugin that runs a build tool. Each repository lists its modules in dagger.toml and pins their versions in dagger.lock.
Agents and labels. A Jenkins label selects an agent with the right tools, such as go && docker. With Dagger, the tools are in containers that modules define, such as testRuntime. When the agent connects to a remote engine, it needs only the Dagger CLI.
archiveArtifacts. The archiveArtifacts step saves files from the workspace on the Jenkins controller. The binary function of the Go module builds a main package, and export writes it to the host:
dagger -c 'go | packages | get cmd/orders | binary | export ./dist/orders'
post. The post section runs steps after a run, such as a mail on failure. Dagger reports each test with its status, duration, and logs, in the terminal and in Dagger Cloud (test-native CI).
Move secrets and deployments
Keep your secrets where they are. A Dagger function receives a password as a Secret, and Dagger reads it from a provider when the function needs it. Providers include environment variables, files, HashiCorp Vault, 1Password, and AWS Secrets Manager (Secrets). Dagger redacts secret values from logs, never writes them to container layers, and leaves them out of cache keys.
To publish from a laptop, call the publish function. This example reads the password from a file and pushes to ttl.sh, a temporary public registry:
dagger api call publish --address=ttl.sh/orders-dev:1h --username=ci --password=file:$HOME/.registry-password
The function prints the address with the digest of the pushed image:
ttl.sh/orders-dev:1h@sha256:6af0310d72e7dc8bce0985aa8ed0aa9f45023f3beeb4475197239b39ddcca785
A deploy is a function that you call, not a check, so dagger check never publishes. In Jenkins, when { branch 'main' } selects the branch. A module can also find out if it runs on the main branch, and deploy to production only from main (branch filtering).
Run the checks with Cloud Checks
When the repository is on GitHub, Cloud Checks run your checks on every push and pull request, with no CI file (Set up Cloud Checks). They run the same checks as dagger check on your laptop, on Cloud Engines. Cloud Engines are engines that Dagger Cloud runs for you. The origin remote of the repository must point to GitHub. From the repository root, run:
dagger cloud checks on
In a terminal, the command helps you sign in to Dagger Cloud and connect your GitHub organization. Then commit dagger.toml, dagger.lock, and .dagger, and push. The results show as GitHub check statuses and in Dagger Cloud (Cloud Checks). Cloud Engines bill the CPU, memory, and data that each pipeline uses (pricing).
Keep Jenkins as the trigger with a thin Jenkinsfile
A team with many Jenkins jobs can keep Jenkins as the trigger, and move the work into Dagger first. The Jenkinsfile then only calls Dagger. Set DAGGER_ENGINE in every job. If it is not set, the CLI starts an engine on the agent, and an ephemeral agent loses the cache of that engine after each job (Self-Hosting CI with Dagger).
This Jenkinsfile connects to a self-hosted engine. The registry-password credential is a Jenkins secret file. The credentials() helper sets the variable to the path of a temporary copy of that file, and the file: provider reads it:
pipeline {
agent { label 'dagger' }
environment {
DAGGER_ENGINE = 'tcp://dagger-engine.internal:1234'
}
stages {
stage('Check') {
steps {
sh 'dagger check'
}
}
stage('Binary') {
steps {
sh "dagger -c 'go | packages | get cmd/orders | binary | export ./dist/orders'"
archiveArtifacts artifacts: 'dist/orders', fingerprint: true
}
}
stage('Publish') {
when { branch 'main' }
environment {
REGISTRY_PASSWORD = credentials('registry-password')
}
steps {
sh 'dagger api call publish --address=registry.example.com/orders:$GIT_COMMIT --username=ci --password=file:$REGISTRY_PASSWORD'
}
}
}
}
The self-hosting docs explain how to run engines with Docker or the Dagger Helm chart. A tcp:// address has no authentication, so make the port available only on the private network of the agents. Install the CLI on each agent at the same version as the engine.
To run the jobs on Cloud Engines instead, set DAGGER_ENGINE to cloud and give the job a Dagger Cloud token. Store the token as a Jenkins secret text credential:
environment {
DAGGER_ENGINE = 'cloud'
DAGGER_CLOUD_TOKEN = credentials('dagger-cloud-token')
}
With this token, the CLI also sends the trace of each job to Dagger Cloud.
When to keep Jenkins as the trigger
Jenkins runs on your own infrastructure, and its plugins connect it to many tools. Keep Jenkins as the trigger in these cases, and move the work inside each job to Dagger:
- The repository is not on GitHub. Cloud Checks need a GitHub repository. For another Git host, a CI system such as Jenkins starts
dagger check. - A job has a trigger other than a push or pull request. Examples are a
cronorupstreamtrigger. - A stage waits for a person. The
inputdirective pauses a stage until an allowed user approves it. - Your team depends on Jenkins reports and notifications. Examples are the test trends from the
junitstep and notifications inpost.
Move one stage at a time
Keep Jenkins on while you set up Dagger, and switch one stage at a time. For each stage, do these steps:
- Run the Dagger checks next to the Jenkins stage that they replace, from Cloud Checks or from a new Jenkins stage.
- Compare the results for some days, for passes and for failures.
- If the repository is on GitHub, make the Dagger checks required status checks.
- Remove the Jenkins status from the required list.
- Remove the stage from the Jenkinsfile.
When a check fails in CI, run it on your laptop with the dagger check command that the report prints under RUN LOCALLY.
What stays the same
Your source control and code review stay the same. Your tests stay the same, because official modules run them with no change to the test code (test-native CI). Your build files, such as go.mod and the Dockerfile, stay the source of truth. Your secret managers stay the source of your secrets. If Jenkins stays the trigger, your credentials, triggers, and post notifications stay in Jenkins too.
Bottom line: To migrate from Jenkins to Dagger, do not rewrite your Jenkinsfiles. Install the official modules that dagger module recommend finds, and put the custom steps in a small Dang module. Then run dagger check with Cloud Checks or from a thin Jenkinsfile, and remove each Jenkins stage when its Dagger checks give the same results.
Related guides
- How do I reproduce a Jenkins build locally?: run a Jenkins build in the same image on a laptop and in Jenkins.
- How do I run Maven builds the same way locally and in CI?: the Maven module, for Java projects that you move from Jenkins.
- How do I migrate from GitHub Actions to Dagger?: the same migration, for GitHub Actions workflows.