Skip to main content

Building container images

You can use Dagger to build container images. Here's a simple example of a Dockerfile build:

package main

import (
"dagger.io/dagger"
)

dagger.#Plan & {
client: filesystem: "./src": read: contents: dagger.#FS

actions: build: dagger.#Dockerfile & {
// This is the context.
source: client.filesystem."./src".read.contents

// Default is to look for a Dockerfile in the context,
// but let's declare it here.
dockerfile: contents: #"""
FROM python:3.9
COPY . /app
RUN pip install -r /app/requirements.txt
CMD python /app/app.py

"""#
}
}

Building with CUE

Dockerfile files are easy to start, but you can also build images entirely in CUE. The following example produces the same image as above:

package main

import (
"dagger.io/dagger"
"universe.dagger.io/docker"
)

dagger.#Plan & {
client: filesystem: "./src": read: contents: dagger.#FS

actions: build: docker.#Build & {
steps: [
docker.#Pull & {
source: "python:3.9"
},
docker.#Copy & {
contents: client.filesystem."./src".read.contents
dest: "/app"
},
docker.#Run & {
command: {
name: "pip"
args: ["install", "-r", "/app/requirements.txt"]
}
},
docker.#Set & {
config: cmd: ["python", "/app/app.py"]
},
]
}
}

Automation

Building images in CUE gives you greater flexibility. For example, you can automate building multiple versions of an image, and deploy, all in Dagger:

package main

import (
"dagger.io/dagger"
"universe.dagger.io/docker"
)

dagger.#Plan & {
actions: versions: {
"8.0": _
"5.7": _

// This is a template
// See https://cuelang.org/docs/tutorials/tour/types/templates/
[tag=string]: {
build: docker.#Build & {
steps: [
docker.#Pull & {
source: "mysql:\(tag)"
},
docker.#Set & {
config: cmd: [
"--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci",
]
},
]
}
push: docker.#Push & {
image: build.output
dest: "registry.example.com/mysql:\(tag)"
}
}
}
}

Now you can deploy all versions:

dagger do versions

Or just build a specific version, without pushing:

dagger do versions 8.0 build

Multi-stage build

Another common pattern is multi-stage builds. This allows you to have heavier build images during the build process, and copy the built artifacts into a cleaner and lighter image to run in production.

package main

import (
"dagger.io/dagger"
"universe.dagger.io/alpine"
"universe.dagger.io/docker"
"universe.dagger.io/go"
)

dagger.#Plan & {
client: filesystem: "./src": read: contents: dagger.#FS

actions: {
// Build app in a "golang" container image.
build: go.#Build & {
source: client.filesystem."./src".read.contents
}

base: alpine.#Build & {
packages: "ca-certificates": _
}

// Build lighter image,
// without app's build dependencies.
run: docker.#Build & {
steps: [
docker.#Copy & {
input: base.output
// This is the important part, it works like
// `COPY --from=build /output /opt` in a Dockerfile.
contents: build.output
dest: "/opt"
},
docker.#Set & {
config: cmd: ["/opt/app"]
},
]
}

push: docker.#Push & {
image: run.output
dest: "registry.example.com/app"
}
}
}