Skip to main content

Get Started with Dagger

In this tutorial, you will learn the basics of Dagger by building a Dagger project from scratch. This simple project deploys a React application to your local machine via Docker. In later tutorials, you will learn how to configure Dagger to deploy to remote infrastructure such as EKS and GKE.

This tutorial does involve writing CUE, so if you havent already, be sure to read What is CUE?

In this tutorial we will learn:

  • How to initialize and structure a Dagger project
  • About Dagger concepts such as
    • the plan
    • environments
    • inputs and outputs
  • How to write CUE for Dagger
  • How to deploy an application with Dagger

Deploy an Application Locally

The following instructions assume you are working locally, but could just as easily be run on a remote machine into which you have a shell.

Install Dagger

First, make sure you have installed Dagger. You can run dagger version to ensure you have the latest installed and working.

Create a Dagger Project

First clone the Dagger examples repository, change directories to the todoapp/ and list its contents:

Note that all tutorials will operate from the todoapp directory.

git clone
cd examples/todoapp
ls -la

This React application will use Yarn to build a static website with the following directories and files.

-rw-r--r--   ...     794 Sep  7 10:09 package.json
drwxr-xr-x ... 256 Sep 7 10:09 public
drwxr-xr-x ... 192 Sep 29 11:17 src
-rw-r--r-- ... 465514 Sep 29 11:17 yarn.lock

Now we need to initialize this directory as a Dagger project:

dagger init
ls -la

You will now see 2 new directories:

  • The .dagger directory will store metadata about environments, inputs, and outputs which we will cover later.
  • The cue.mod directory stores libraries such as dagger/universe which can be imported into your Dagger plan.

Dagger will load all .cue files recursively in the current Dagger project. More directories can be added to help organize code.

Note that Dagger, like the CUE CLI command, will only load CUE files from the cue.mod directory in response to import statements.

Write a Dagger Plan

A Dagger plan is written in CUE and defines the resources, dependencies, and logic to deploy an application to an environment. Unlike traditional glue code written in a scripting language such as Bash or PowerShell, a Dagger plan is declarative rather than imperative. This frees us from thinking about the order of operations, since Dagger will infer dependendencies and calculate correct order on its own.

Lets first create a directory to hold our Dagger plan separately from our application code:

mkdir -p ./plans/local

We will now create the following files:

  • plans/todoapp.cue which will define resources common to all environments
  • plans/local/local.cue which will define resources specific to the local environment

Create the file plans/todoapp.cue with the following content:

package todoapp

import (

// Build the source code using Yarn
app: yarn.#Package & {
source: dagger.#Artifact & dagger.#Input

// package the static HTML from yarn into a Docker image
image: os.#Container & {
image: docker.#Pull & {
from: "nginx"

// references our app key above
// which infers a dependency that Dagger
// uses to generate the DAG
copy: "/usr/share/nginx/html": from:

// push the image to a registry
push: docker.#Push & {
// leave target blank here so that different
// environments can push to different registries
target: string

// the source of our push resource
// is the image resource we declared above
source: image

This file will define the resources and relationships between them that are common across all environments. For example, here we are deploying to our local Docker engine in our local environment, but for staging or production as examples, we would deploy the same image to some other container orchestration system such as Kubernetes hosted somewhere out there among the various cloud providers.

Create the file plans/local/local.cue with the following content:

package todoapp

import (

// docker local socket
dockerSocket: dagger.#Stream & dagger.#Input

// run our todoapp in our local Docker engine
run: docker.#Run & {
ref: push.ref
name: "todoapp"
ports: ["8080:80"]
socket: dockerSocket

// run our local registry
registry: docker.#Run & {
ref: "registry:2"
name: "registry-local"
ports: ["5042:5000"]
socket: dockerSocket

// As we pushed the registry to our local docker
// we need to wait for the container to be up
wait: http.#Wait & {
url: "localhost:5042"

// push to our local registry
// this concrete value satisfies the string constraint
// we defined in the previous file
push: target: "\(wait.url)/todoapp"

// Application URL
appURL: "http://localhost:8080/" & dagger.#Output

Notice that both files have the same package todoapp declared on the first line. This is crucial to inform CUE that they are to be loaded and evaluated together in the same context.

Our local.cue file now holds resources specific to our local environment. Also notice that we are defining a concrete value for the target key here. The entire push object is defined in both files and CUE will merge the values into a single struct with key:value pairs that are complete with concrete values.

Create an Environment

Before we can deploy the plan, we need to define an environment which is the specific plan to execute, as well as the context from which inputs are pulled and to which state is stored.

For our each environment we need to tell Dagger what CUE files to load, so lets create a local environment:

dagger new local -p ./plans/local
dagger list

The list command shows the current environments defined:

local ...todoapp/.dagger/env/local

Define Input Values per Environment

Our Dagger plan includes a number of references to dagger.#Input which inform the Dagger engine that the concrete value should be pulled from inputs at runtime. While some values such as the registry target we saw above can be expressed purely in CUE, others such as directories, secrets, and sockets are required to be explicitly defined as inputs to protect against malicious code being injected by third-party packages. If Dagger allowed such things to be stated in CUE, the entire package system could become a source of attacks.

List the inputs Dagger is aware of according to our plan:

dagger -e local input list

You should see the following output:

Input         Value             Set by user  Description
app.source dagger.#Artifact false Application source code
dockerSocket struct false Mount local docker socket

Notice that Set by user is false for both, because we have not yet provided Dagger with those values.

Lets provide them now:

dagger -e local input socket dockerSocket /var/run/docker.sock
dagger -e local input dir app.source ./

This defines the dockerSocket as a socket input type, and the app.source input as a dir input type.

Now lets replay the dagger input list command:

Input         Value             Set by user  Description
app.source dagger.#Artifact true Application source code
dockerSocket struct true Mount local docker socket

Notice that Dagger now reports that both inputs have been set.

Deploy the Appplication

With our plan in place, our environment set, and our inputs defined, we can deploy the application as simply as:

dagger up

Once complete you should get logs, and a final output like this:

Output                 Value                                          Description struct Build output directory
push.ref "localhost:5000/todoapp:latest@sha256:<hash>" Image ref
push.digest "sha256:<hash>" Image digest
run.ref "localhost:5000/todoapp:latest@sha256:<hash>" Image reference (e.g: nginx:alpine) "localhost:5000/todoapp:latest@sha256:<hash>" -
appURL "http://localhost:8080/" Application URL

Congratulations! Youve deployed your first Dagger plan! You can now view the todo app in your browser!