Quickstart
Use caching
One of Dagger's most powerful features is its ability to cache data across pipeline runs. This is typically most useful when dealing with package managers such as npm
, maven
, pip
and similar. For these tools to cache properly, they need their own cache data (usually a directory) to be persisted between runs.
Dagger lets you define one or more such directories as cache volumes and persist their contents across runs. This enables you to reuse the contents of the cache volume(s) every time the pipeline runs, and thereby speed up pipeline operations.
Cache volumes are not meant to be shared outside of Dagger; they are for persisting specific parts of the internal state of your pipeline, for optimal use of your tool's native caching features.
You may have noticed that the example pipeline executes the npm install
command to download the application's dependencies every time the pipeline runs. Since these dependencies are usually locked to specific versions in the application's manifest, re-downloading them on every pipeline run is inefficient and time-consuming.
This step is, therefore, a good candidate for a cache volume. Let's update the pipeline accordingly.
The npm install
command is appropriate for a React application, but other applications are likely to use different commands. Modify your Dagger pipeline accordingly.
package main
import (
"context"
"fmt"
"math"
"math/rand"
"os"
"dagger.io/dagger"
)
func main() {
ctx := context.Background()
// initialize Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()
// create a cache volume
nodeCache := client.CacheVolume("node")
// use a node:16-slim container
// mount the source code directory on the host
// at /src in the container
// mount the cache volume to persist dependencies
source := client.Container().
From("node:16-slim").
WithDirectory("/src", client.Host().Directory(".", dagger.HostDirectoryOpts{
Exclude: []string{"node_modules/", "ci/", "build/"},
})).
WithMountedCache("/src/node_modules", nodeCache)
// set the working directory in the container
// install application dependencies
runner := source.WithWorkdir("/src").
WithExec([]string{"npm", "install"})
// run application tests
test := runner.WithExec([]string{"npm", "test", "--", "--watchAll=false"})
// first stage
// build application
buildDir := test.WithExec([]string{"npm", "run", "build"}).
Directory("./build")
// second stage
// use an nginx:alpine container
// copy the build/ directory from the first stage
// publish the resulting container to a registry
ref, err := client.Container().
From("nginx:1.23-alpine").
WithDirectory("/usr/share/nginx/html", buildDir).
Publish(ctx, fmt.Sprintf("ttl.sh/hello-dagger-%.0f", math.Floor(rand.Float64()*10000000))) //#nosec
if err != nil {
panic(err)
}
fmt.Printf("Published image to: %s\n", ref)
}
This revised pipeline now uses a cache volume for the application dependencies.
- It uses the client's
CacheVolume()
method to initialize a new cache volume. - It uses the
Container.WithMountedCache()
method to mount this cache volume at thenode_modules/
mount point in the container. - It uses the
Container.WithExec()
method to define thenpm install
command. When executed, this command downloads and installs dependencies in thenode_modules/
directory. Since this directory is defined as a cache volume, its contents will persist even after the pipeline terminates and can be reused on the next pipeline run.
Run the pipeline by executing the command below from the application directory:
dagger run go run ci/main.go
import { connect } from "@dagger.io/dagger"
connect(
async (client) => {
// create a cache volume
const nodeCache = client.cacheVolume("node")
// use a node:16-slim container
// mount the source code directory on the host
// at /src in the container
// mount the cache volume to persist dependencies
const source = client
.container()
.from("node:16-slim")
.withDirectory(
"/src",
client
.host()
.directory(".", { exclude: ["node_modules/", "ci/", "build/"] }),
)
.withMountedCache("/src/node_modules", nodeCache)
// set the working directory in the container
// install application dependencies
const runner = source.withWorkdir("/src").withExec(["npm", "install"])
// run application tests
const test = runner.withExec(["npm", "test", "--", "--watchAll=false"])
// first stage
// build application
const buildDir = test.withExec(["npm", "run", "build"]).directory("./build")
// second stage
// use an nginx:alpine container
// copy the build/ directory from the first stage
// publish the resulting container to a registry
const imageRef = await client
.container()
.from("nginx:1.23-alpine")
.withDirectory("/usr/share/nginx/html", buildDir)
.publish("ttl.sh/hello-dagger-" + Math.floor(Math.random() * 10000000))
console.log(`Published image to: ${imageRef}`)
},
{ LogOutput: process.stderr },
)
This revised pipeline now uses a cache volume for the application dependencies.
- It uses the client's
cacheVolume()
method to initialize a new cache volume. - It uses the
Container.withMountedCache()
method to mount this cache volume at thenode_modules/
mount point in the container. - It uses the
Container.withExec()
method to define thenpm install
command. When executed, this command downloads and installs dependencies in thenode_modules/
directory. Since this directory is defined as a cache volume, its contents will persist even after the pipeline terminates and can be reused on the next pipeline run.
Run the pipeline by executing the command below from the application directory:
dagger run node ci/index.mjs
import random
import sys
import anyio
import dagger
async def main():
config = dagger.Config(log_output=sys.stdout)
async with dagger.Connection(config) as client:
# create a cache volume
node_cache = client.cache_volume("node")
# use a node:16-slim container
# mount the source code directory on the host
# at /src in the container
# mount the cache volume to persist dependencies
source = (
client.container()
.from_("node:16-slim")
.with_directory(
"/src",
client.host().directory(
".", exclude=["node_modules/", "ci/", "build/"]
),
)
.with_mounted_cache("/src/node_modules", node_cache)
)
# set the working directory in the container
# install application dependencies
runner = source.with_workdir("/src").with_exec(["npm", "install"])
# run application tests
test = runner.with_exec(["npm", "test", "--", "--watchAll=false"])
# first stage
# build application
build_dir = test.with_exec(["npm", "run", "build"]).directory("./build")
# second stage
# use an nginx:alpine container
# copy the build/ directory from the first stage
# publish the resulting container to a registry
image_ref = await (
client.container()
.from_("nginx:1.23-alpine")
.with_directory("/usr/share/nginx/html", build_dir)
.publish(f"ttl.sh/hello-dagger-{random.randrange(10 ** 8)}")
)
print(f"Published image to: {image_ref}")
anyio.run(main)
This revised pipeline now uses a cache volume for the application dependencies.
- It uses the client's
cache_volume()
method to initialize a new cache volume. - It uses the
Container.with_mounted_cache()
method to mount this cache volume at thenode_modules/
mount point in the container. - It uses the
Container.with_exec()
method to define thenpm install
command. When executed, this command downloads and installs dependencies in thenode_modules/
directory. Since this directory is defined as a cache volume, its contents will persist even after the pipeline terminates and can be reused on the next pipeline run.
Run the pipeline by executing the command below from the application directory:
dagger run python ci/main.py
This revised pipeline produces the same result as before.
Run the pipeline a few times. Notice that on the first run, the application dependencies are downloaded as usual. However, since the dependencies are cached, subsequent pipeline runs will skip the download operation and be significantly faster (assuming that there are no other changes to the application code).
In addition to cache volumes, Dagger has a separate cache for pipeline operations. Changes in cache volumes or secrets do not invalidate the Dagger pipeline operations cache.