docs/current_docs/getting-started/quickstarts/ci/index.mdx
import PartialIde from '@partials/_ide.mdx'; import VideoPlayer from '@components/VideoPlayer';
CI workflows often start simple, but eventually transform into a labyrinth of artisanal shell scripts and/or unmanageable YAML code. Dagger lets you replace those artisanal scripts and YAML with a modern API and cross-language scripting engine.
:::note This quickstart will guide you through building a CI workflow for an application using Dagger. :::
This quickstart will take you approximately 10 minutes to complete. You should be familiar with programming in Go, Python, TypeScript, PHP, or Java.
Before beginning, ensure that:
:::tip <PartialIde /> :::
The example application is a skeleton Vue framework application that returns a "Hello from Dagger!" welcome page. Create a Github repository from the hello-dagger-template and set it as the current working directory:
In the GitHub UI:
Clone your new repository and set it as the current working directory.
Or with the gh CLI:
gh repo create hello-dagger --template dagger/hello-dagger-template --public --clone
cd hello-dagger
:::important This step is optional and will create a Dagger Cloud account, which is free of charge for a single user. If you prefer not to sign up for Dagger Cloud, you can skip this section. :::
Dagger Cloud is an online visualization tool for Dagger workflows. It provides a web interface to visualize each step of your workflow, drill down to detailed logs, understand how long operations took to run, and whether operations were cached.
Create a new Dagger Cloud account by running dagger login:
dagger login
The Dagger CLI will invite you to authenticate your device by displaying a link containing a unique key. Click the link in your browser, and verify that you see the same key in the Dagger Cloud Web interface.
$ dagger login
Browser opened to: https://auth.dagger.cloud/activate?user_code=FCNP-SRLM
Confirmation code: FCNP-SRLM
Once you confirm your authentication code, your Dagger CLI will be authenticated and you will get redirected to your newly created Dagger Cloud organization.
After successfully creating your organization, all future Dagger workflows can be inspected in Dagger Cloud.
Bootstrap a new Dagger module in Go, Python, TypeScript, PHP, or Java by running dagger init in the application's root directory.
dagger init --sdk=go --name=hello-dagger
dagger init --sdk=python --name=hello-dagger
dagger init --sdk=typescript --name=hello-dagger
dagger init --sdk=php --name=hello-dagger
dagger init --sdk=java --name=hello-dagger
This will generate a dagger.json module metadata file and a .dagger directory containing some boilerplate Dagger Functions as examples.
To see the generated Dagger Functions, run:
dagger functions
You should see information about two auto-generated Dagger Functions: container-echo and grep-dir.
Replace the generated Dagger module files as described below.
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">Replace the generated .dagger/main.go file with the following code, which adds four Dagger Functions to your Dagger module:
Replace the generated .dagger/src/hello_dagger/main.py file with the following code, which adds four Dagger Functions to your Dagger module:
Replace the generated .dagger/src/index.ts file with the following code, which adds four Dagger Functions to your Dagger module:
Replace the generated .dagger/src/HelloDagger.php file with the following code, which adds four Dagger Functions to your Dagger module:
Replace the generated .dagger/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java file with the following code, which adds four Dagger Functions to your Dagger module:
In this Dagger module, each Dagger Function performs a different operation:
publish Dagger Function tests, builds and publishes a container image of the application to a registry.test Dagger Function runs the application's unit tests and returns the results.build Dagger Function performs a multi-stage build and returns a final container image with the production-ready application and an NGINX Web server to host and serve it.build-env Dagger Function creates a container with the build environment for the application.Dagger Shell is the fastest way to interact with the Dagger API, allowing access to both types and Dagger Functions using a familiar Bash-like syntax. Type dagger to launch Dagger Shell in interactive mode.
publish
This single command runs the application's tests, then builds and publishes it as a container image to the ttl.sh container registry. Here's what you should see:
<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/publish.webm" alt="Publishing the container" />You can test the published container image by pulling and running it with docker run:
If you signed up for Dagger Cloud, the output of the previous command would have also included a link to visualize the workflow run on Dagger Cloud. Click the link in your browser to see a complete breakdown of the steps performed. Here's what you should see:
<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/trace.webm" alt="Visualizing the workflow in Dagger Cloud" />The build-env Dagger Function returns a Container type representing the application's build environment. One of the most interesting built-in functions of this type is terminal, which can be used to open an interactive terminal session with the running container.
To try this, chain an additional function call to terminal on the returned Container:
build-env | terminal --cmd=bash
This command builds the container image and then drops you into an interactive terminal running the bash shell. You can now directly execute commands in the running container, as shown below:
The build Dagger Function returns a Container type representing the built container image. Another interesting built-in function to explore here is the as-service function, which can be used to start a container as a local service and have any exposed ports forwarded to the host machine. This is similar to Docker Compose, except that you're using code instead of YAML to manage your services.
To try this, use the function chain below:
build | as-service | up --ports=8080:80
By default, Dagger will map each exposed container service port to the same port on the host. Since NGINX operates on port 80, which is often a privileged port on the host, the additional --ports 8080:80 argument re-maps container port 80 to unprivileged host port 8080.
You should now be able to access the application by browsing to http://localhost:8080 on the Dagger host (replace localhost with your Dagger host's network name or IP address if accessing it remotely). You should see a "Hello from Dagger!" welcome page, served by NGINX.
Now that you've built a workflow, it's important to validate that your code meets quality standards before committing. Dagger provides a built-in mechanism for this through checks.
Checks are special Dagger Functions that validate commits—they run without required arguments, making them perfect for automated validation at any stage: locally before pushing, on every commit in CI, or on merge to main.
Let's add a simple check to validate that our application builds successfully. Create a new function that runs the build and returns an error if it fails:
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">// Validate that the application builds successfully
// +check
func (m *HelloDagger) ValidateBuild(
ctx context.Context,
// +optional
// +defaultPath="/"
// +ignore=[".git", "node_modules"]
source *dagger.Directory,
) error {
_, err := m.Build(source).Sync(ctx)
return err
}
@function
@check
async def validate_build(
self,
source: Annotated[
dagger.Directory,
DefaultPath("/"),
Ignore([".git", "node_modules"]),
] = None,
) -> None:
"""Validate that the application builds successfully"""
await self.build(source).sync()
/**
* Validate that the application builds successfully
*/
@func()
@check()
async validateBuild(
@argument({ defaultPath: "/", ignore: [".git", "node_modules"] })
source?: Directory,
): Promise<void> {
await this.build(source).sync()
}
Note how this check:
build() function and validates it succeedsNow you can validate your workflow by running all checks:
dagger check
This command automatically discovers and runs all functions marked as checks in parallel. If the build succeeds, the check passes. If it fails, you'll get immediate feedback before committing.
You can also list available checks:
dagger check -l
Checks integrate seamlessly into your development workflow:
dagger check before pushing to catch issues earlydagger check to your CI pipeline as a quality gate:::tip Learn more about creating advanced checks, filtering, and ignoring checks in the Checks guide. :::
Congratulations! You've created your first CI workflow with Dagger.
Now you have the tools to successfully take the next step: adopting Dagger in your project.
The guide Use a Toolchain will show you how to use a toolchain instead of writing code for every single project.
The next guide Build an AI Agent will show you how to add agentic capabilities to your newly Daggerized project.
In the meantime, we encourage you to join our awesome community on Discord to introduce yourself and ask questions. And starring our GitHub repository is always appreciated!