Back to Dagger

Build a CI Workflow

docs/current_docs/getting-started/quickstarts/ci/index.mdx

0.20.712.8 KB
Original Source

import PartialIde from '@partials/_ide.mdx'; import VideoPlayer from '@components/VideoPlayer';

Build a CI Workflow

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. :::

Requirements

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 /> :::

Get the example application

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:

shell
gh repo create hello-dagger --template dagger/hello-dagger-template --public --clone
cd hello-dagger

Configure Dagger Cloud (optional)

:::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:

shell
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.

shell
$ 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.

Initialize a Dagger module

Bootstrap a new Dagger module in Go, Python, TypeScript, PHP, or Java by running dagger init in the application's root directory.

<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">
shell
dagger init --sdk=go --name=hello-dagger
</TabItem> <TabItem value="python" label="Python">
shell
dagger init --sdk=python --name=hello-dagger
</TabItem> <TabItem value="typescript" label="TypeScript">
shell
dagger init --sdk=typescript --name=hello-dagger
</TabItem> <TabItem value="php" label="PHP">
shell
dagger init --sdk=php --name=hello-dagger
</TabItem> <TabItem value="java" label="Java">
shell
dagger init --sdk=java --name=hello-dagger
</TabItem> </Tabs>

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:

shell
dagger functions

You should see information about two auto-generated Dagger Functions: container-echo and grep-dir.

Construct a workflow

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:

go
</TabItem> <TabItem value="python" label="Python">

Replace the generated .dagger/src/hello_dagger/main.py file with the following code, which adds four Dagger Functions to your Dagger module:

python
</TabItem> <TabItem value="typescript" label="TypeScript">

Replace the generated .dagger/src/index.ts file with the following code, which adds four Dagger Functions to your Dagger module:

typescript
</TabItem> <TabItem value="php" label="PHP">

Replace the generated .dagger/src/HelloDagger.php file with the following code, which adds four Dagger Functions to your Dagger module:

php
</TabItem> <TabItem value="java" label="Java">

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:

java
</TabItem> </Tabs>

In this Dagger module, each Dagger Function performs a different operation:

  • The publish Dagger Function tests, builds and publishes a container image of the application to a registry.
  • The test Dagger Function runs the application's unit tests and returns the results.
  • The 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.
  • The build-env Dagger Function creates a container with the build environment for the application.

Run the workflow

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.

shell
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:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/docker.webm" alt="Running the published container with Docker" />

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" />

Interact with the build environment

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:

shell
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:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/buildenv-terminal.webm" alt="Interactive terminal in build environment" />

Run a container as a local service

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:

shell
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.

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/build-service.webm" alt="Final container as service" />

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.

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/ci/curl.webm" alt="Testing the service" />

Validate your workflow with checks

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.

Create a check

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">
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
}
</TabItem> <TabItem value="python" label="Python">
python
@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()
</TabItem> <TabItem value="typescript" label="TypeScript">
typescript
/**
 * 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()
}
</TabItem> </Tabs>

Note how this check:

  • Has no required arguments (source has defaults, making it optional)
  • Returns an error type (or void/None) not a Container
  • Calls the existing build() function and validates it succeeds

Run checks

Now you can validate your workflow by running all checks:

shell
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:

shell
dagger check -l

Checks integrate seamlessly into your development workflow:

  • Locally: Run dagger check before pushing to catch issues early
  • CI: Add dagger check to your CI pipeline as a quality gate
  • Pre-commit hooks: Configure checks to run automatically on commit

:::tip Learn more about creating advanced checks, filtering, and ignoring checks in the Checks guide. :::

Next steps

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!