Back to Dagger

Interactive Shell

docs/current_docs/introduction/features/shell.mdx

0.20.75.7 KB
Original Source

import VideoPlayer from '@components/VideoPlayer';

The Dagger CLI includes an interactive shell that translates the familiar Bash syntax to Dagger API requests. It's the simplest and fastest way to run Dagger workflows directly from the command-line.

You can use it for builds, tests, ephemeral environments, deployments, or any other task you want to automate from the terminal.

:::important Dagger Shell commands run as sandboxed functions, accessing host resources (files, secrets, services) only when explicitly provided as arguments. This can make commands slightly more verbose, but also more repeatable, giving you confidence to iterate quickly without second-guessing. :::

Here's an example of Dagger Shell in action:

shell
container | from alpine | with-exec apk add curl | with-exec -- curl -L https://dagger.io | stdout
<VideoPlayer src="/img/current_docs/introduction/features/shell-curl.webm" alt="Dagger Shell" />

Here's another, more complex example:

shell
container |
  from cgr.dev/chainguard/wolfi-base |
  with-exec apk add go |
  with-directory /src https://github.com/golang/example#master |
  with-workdir /src/hello |
  with-exec -- go build -o hello . |
  file ./hello |
  export ./hello-from-dagger
<VideoPlayer src="/img/current_docs/introduction/features/shell-build.webm" alt="Shell build example" />

Behavior

Dagger Shell uses the Bash syntax as a frontend, but its behavior is quite different in the backend:

  • Instead of executing UNIX commands, you execute Dagger Functions_
  • Instead of passing text streams from command to command, you pass typed objects from function to function
  • Instead of available commands being the same everywhere in the pipeline, each command is executed in the context of the previous command's output object. For example, foo | bar really means foo().bar()
  • Instead of using the local host as an execution environment, you use containerized runtimes
  • Instead of being mixed with regular commands, shell builtins are prefixed with . (similar to SQLite)

Besides these differences, all the features of the Bash syntax are available in Dagger Shell, including:

  • Shell variables: container=$(container | from alpine)
  • Shell functions: container() { container | from alpine; }
  • Job control: frontend | test & backend | test & .wait
  • Quoting: single quotes and double quotes have the same meaning as in Bash

Input modes

Dagger Shell supports multiple ways to input commands:

<Tabs groupId="shell"> <TabItem value="Inline execution"> ```shell dagger -c 'container | from alpine | terminal' ``` </TabItem> <TabItem value="Standard input"> ```shell title="First type 'dagger' for interactive mode." dagger <<EOF container | from alpine | terminal EOF ``` </TabItem> <TabItem value="Script"> ```shell #!/usr/bin/env dagger

container | from alpine | with-exec cat /etc/os-release | stdout

</TabItem>
<TabItem value="Interactive REPL">
```shell title="First type 'dagger' for interactive mode."
container | from alpine | terminal
</TabItem> </Tabs>

Modules

Dagger Shell can load modules, inspect their types, and run their functions. This works for both local and remote modules.

<VideoPlayer src="/img/current_docs/introduction/features/shell-module.webm" alt="Dagger Shell module loading" />

For example, try running this command:

shell
dagger -m github.com/dagger/dagger/[email protected]

This loads the module github.com/dagger/dagger/docs at version v0.18.5. Wait for the Dagger Shell interactive prompt, then:

shell
.help

We see new commands available, loaded from the module. Let's try running one:

shell
server | up

This builds the docs server defined in the module, and runs it as an ephemeral service on your machine.

You can load multiple modules in the same session, each scoped to a single pipeline, then combine those pipelines into a bigger pipeline. For example:

shell
dagger <<.
github.com/dagger/dagger/modules/wolfi |
 container --packages=nginx |
 with-directory /var/www $(
  github.com/dagger/dagger/docs | site
 )
.

This loads github.com/dagger/dagger/docs into one pipeline, github.com/dagger/dagger/modules/wolfi into another, then combines them.

This works by executing the module's constructor function. It works exactly like executing any other function:

  • You can inspect it for arguments: .help MODULE
  • You can pass arguments: MODULE --foo=bar --baz
  • You can save it to a variable: foo=$(MODULE)
  • You can use it in a pipeline: MODULE | foo | bar
  • You can use that pipeline as a sub-pipeline: foo --bar=$(MODULE | foo | bar)

Variables

Dagger Shell lets you save the result of any command to a variable, using the standard bash syntax. Values of any type can be saved to a variable, including objects.

Here's an example:

<Tabs groupId="shell"> <TabItem value="System shell"> ```shell dagger <<'.' repo=$(git https://github.com/dagger/hello-dagger | head | tree) env=$(container | from node:23 | with-directory /app $repo | with-workdir /app) build=$($env | with-exec npm install | with-exec npm run build | directory ./dist) container | from nginx | with-directory /usr/share/nginx/html $build | terminal --cmd=/bin/bash . ``` </TabItem> <TabItem value="Dagger Shell"> ```shell title="First type 'dagger' for interactive mode." repo=$(git https://github.com/dagger/hello-dagger | head | tree) env=$(container | from node:23 | with-directory /app $repo | with-workdir /app) build=$($env | with-exec npm install | with-exec npm run build | directory ./dist) container | from nginx | with-directory /usr/share/nginx/html $build | terminal --cmd=/bin/bash ``` </TabItem> </Tabs> <VideoPlayer src="/img/current_docs/introduction/features/shell-variables.webm" alt="Dagger Shell variables" />