---
slug: /1012/ci
---

# Make your CI workflow portable

## Problems with existing CIs

- The CI code is sticky to the underlying CI infrastructure.
- It's hard to migrate the CI from a runner to another.
- The CI syntax is different with each CI runner.
- Most CI workflows are represented using YAML.

Implement the CI workflow using dagger solves all of those problems.

## Benefits of defining your CI workflow in Dagger

- **Your CI only runs dagger.** It moves all the CI logic from the non-portable
  CI syntax, to a dagger plan, that runs the same way everywhere.
- **Develop and run your CI workflow locally.** No need to create a Pull Request
  to trigger CI, you can run the workflow locally. Since dagger workflows
  are containerized, you can expect the same results no matter where the workflow is executed.
- **Write once, Run anywhere.** The same workflow can run in any CI, goodbye
  vendor lock in.
- **Blazing Fast**: Dagger will automatically build an optimized execution
  graph, completing the job as fast as possible. Spend less time staring at CI to
  complete and reduce costs.
- **Effortless Cache Optimizations**. Dagger will automatically
  re-use cached execution results if no changes are detected. Made a change to the
  frontend? The backend won't be built again.
- **Composable & Reusable**. Define re-usable steps and [share](../learn/1010-dev-cue-package.md) them across all
  your projects. Or with the world. Dagger
  ships with [dozens of re-usable components](../reference/README.md)

## Example

This example illustrates how to use Dagger to test and lint a Go project. The
use of Go is irrelevant and the example can be easily adapted to other languages.

From the project repository, create a file named `ci/main.cue` and add the
following configuration to it.

```cue title="ci/main.cue"
package main

import (
    "alpha.dagger.io/dagger"
    "alpha.dagger.io/os"
    "alpha.dagger.io/docker"
)

// Source directory of the repository. We'll connect this from the CLI using `dagger input`
source: dagger.#Artifact

// Here we define a test phase.
// We're using `os.#Container`, a built-in package that will spawn a container
// We're also using `docker.#Pull` to execute the container from a Docker image
// coming from a registry.
test: os.#Container & {
    image: docker.#Pull & {
        from: "golang:1.16-alpine"
    }
    mount: "/app": from: source
    command: "go test -v ./..."
    dir:     "/app"
}

// Likewise, here we define a lint phase.
lint: os.#Container & {
    image: docker.#Pull & {
        from: "golangci/golangci-lint:v1.39.0"
    }
    mount: "/app": from: source
    command: "golangci-lint run -v"
    dir:     "/app"
}
```

The configuration above defines:

- **source** code of the project. More on this later.
- **test** _task_ which executes `go test` inside the source artifact
  using the `golang` Docker image
- **lint** _task_ which executes `golangci-lint` inside the source artifact
  using the `golangci-lint` Docker image.

Before we can execute the configuration, we need to set up the Dagger project and environment.

```shell
# Initialize a dagger project at the root of your project
dagger init

# Create a CI environment using the CUE files in the `./ci` directory
dagger new ci -p ./ci

# Link the `source` artifact defined in the configuration with the project
# source code.
dagger input dir source .
```

Next, bring up the CI environment (e.g. execute the CI configuration):

```shell
$ dagger up
# ...
7:15PM INF test | computing    environment=ci
7:15PM INF lint | computing    environment=ci
# ...
```

Since `test` and `lint` do not depend on each other, they are executed in
parallel.

Running `dagger up` a second time will return almost immediately: since no
changes were made to `source`, Dagger will re-use the cached results for both `test` nor `lint`.

## Integrating with CI

All it takes to execute a Dagger workflow in CI is to run `dagger up`.

We provide a [GitHub Actions](../learn/1009-github-actions.md) to make
integration with GitHub easier.

## Monorepos

Dagger workflows scale really well with CI complexity.

The following example illustrates how to test two different projects in the same
repository: a _Node_ frontend (located in _./frontend_) along with a _Go_ backend (located in _./backend_).

From the project repository, update the file named `ci/main.cue` with the
following configuration.

```cue title="ci/main.cue"
package main

import (
    "alpha.dagger.io/dagger"
    "alpha.dagger.io/os"
    "alpha.dagger.io/docker"
)

// Source directory of the repository. We'll connect this from the CLI using `dagger input`
source: dagger.#Artifact

backend: {
    // We use `os.#Dir` to grab a sub-directory of `source`
    code: os.#Dir & {
        from: source
        path: "./backend"
    }

    test: os.#Container & {
        image: docker.#Pull & {
            from: "golang:1.16-alpine"
        }
        mount: "/app": from: code
        command: "go test -v ./..."
        dir:     "/app"
    }
}

frontend: {
    // We use `os.#Dir` to grab a sub-directory of `source`
    code: os.#Dir & {
        from: source
        path: "./frontend"
    }

    test: os.#Container & {
        image: docker.#Pull & {
            from: "node:16-alpine"
        }
        mount: "/app": from: code
        command: """
            # Install Dependencies
            yarn

            # Run the test script
            yarn test
            """
        dir: "/app"
    }
}
```

:::tip
Larger configurations can be split into multiple files, for example `backend.cue` and `frontend.cue`
:::

The configuration above defines a _frontend_ and _backend_ structure, each
containing:

- A **code** directory, defined as a subdirectory of **source**
- A language specific **test** task using either `yarn` or `go`

```shell
$ dagger up
7:15PM INF frontend.test | computing    environment=ci
7:15PM INF backend.test | computing    environment=ci
7:15PM INF frontend.test | #8 0.370 yarn install v1.22.5    environment=ci
7:15PM INF frontend.test | #8 0.689 [1/4] Resolving packages...    environment=ci
7:15PM INF frontend.test | #8 1.626 [2/4] Fetching packages...    environment=ci
...
```

`frontend.test` and `backend.test` are running in parallel since there are no
dependencies between each other.

If you were to make changes to the _./frontend_ directory, only
`frontend.test` will be executed.
