Container Images
Dagger allows you to build, publish, and export container images, also known as just-in-time artifacts, as part of your Dagger Functions. This section shows you how to work with container images using Dagger with practical examples.
Publish a container image to a private registry​
The following Dagger Function publishes a just-in-time container image to a private registry.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Publish a container image to a private registry
func (m *MyModule) Publish(
ctx context.Context,
// Registry address
registry string,
// Registry username
username string,
// Registry password
password *dagger.Secret,
) (string, error) {
return dag.Container().
From("nginx:1.23-alpine").
WithNewFile(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
dagger.ContainerWithNewFileOpts{Permissions: 0o400},
).
WithRegistryAuth(registry, username, password).
Publish(ctx, fmt.Sprintf("%s/%s/my-nginx", registry, username))
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def publish(
self,
registry: Annotated[str, Doc("Registry address")],
username: Annotated[str, Doc("Registry username")],
password: Annotated[dagger.Secret, Doc("Registry password")],
) -> str:
"""Publish a container image to a private registry"""
return await (
dag.container()
.from_("nginx:1.23-alpine")
.with_new_file(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
permissions=0o400,
)
.with_registry_auth(registry, username, password)
.publish(f"{registry}/{username}/my-nginx")
)
import { dag, Secret, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Publish a container image to a private registry
*/
@func()
async publish(
/**
* Registry address
*/
registry: string,
/**
* Registry username
*/
username: string,
/**
* Registry password
*/
password: Secret,
): Promise<string> {
return await dag
.container()
.from("nginx:1.23-alpine")
.withNewFile("/usr/share/nginx/html/index.html", "Hello from Dagger!", {
permissions: 0o400,
})
.withRegistryAuth(registry, username, password)
.publish(`${registry}/${username}/my-nginx`)
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\File;
use Dagger\Secret;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Publish a container image to a private registry')]
public function publish(
#[Doc('registry address')]
string $registry,
#[Doc('registry username')]
string $username,
#[Doc('registry password')]
Secret $password,
): string {
return dag()
->container()
->from('nginx:1.23-alpine')
->withNewFile('/usr/share/nginx/html/index.html', 'Hello from Dagger!', 400)
->withRegistryAuth($registry, $username, $password)
->publish("$registry/$username/my-nginx");
}
}
Examples​
Publish a just-in-time container image to Docker Hub, using the account username user and the password set in the PASSWORD environment variable:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'publish docker.io user env://PASSWORD'
publish docker.io user env://PASSWORD
dagger call publish --registry=docker.io --username=user --password=env://PASSWORD
Publish a just-in-time container image to GitHub Container Registry, using the account username user and the GitHub personal access token set in the PASSWORD environment variable:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'publish ghcr.io user env://PASSWORD'
publish ghcr.io user env://PASSWORD
dagger call publish --registry=ghcr.io --username=user --password=env://PASSWORD
Publish a container image to a private registry with multiple tags​
The following Dagger Function tags a just-in-time container image multiple times and publishes it to a private registry.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Tag a container image multiple times and publish it to a private registry
func (m *MyModule) Publish(
ctx context.Context,
// Registry address
registry string,
// Registry username
username string,
// Registry password
password *dagger.Secret,
) ([]string, error) {
tags := [4]string{"latest", "1.0-alpine", "1.0", "1.0.0"}
addr := []string{}
ctr := dag.Container().
From("nginx:1.23-alpine").
WithNewFile(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
dagger.ContainerWithNewFileOpts{Permissions: 0o400},
).
WithRegistryAuth(registry, username, password)
for _, tag := range tags {
a, err := ctr.Publish(ctx, fmt.Sprintf("%s/%s/my-nginx:%s", registry, username, tag))
if err != nil {
return addr, err
}
addr = append(addr, a)
}
return addr, nil
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def publish(
self,
registry: Annotated[str, Doc("Registry address")],
username: Annotated[str, Doc("Registry username")],
password: Annotated[dagger.Secret, Doc("Registry password")],
) -> list[str]:
"""Tag a container image multiple times and publish it to a private registry"""
tags = ["latest", "1.0-alpine", "1.0", "1.0.0"]
addr = []
container = (
dag.container()
.from_("nginx:1.23-alpine")
.with_new_file(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
permissions=0o400,
)
.with_registry_auth(registry, username, password)
)
for tag in tags:
a = await container.publish(f"{registry}/{username}/my-nginx:{tag}")
addr.append(a)
return addr
import { dag, Secret, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Tag a container image multiple times and publish it to a private registry
*/
@func()
async publish(
/**
* Registry address
*/
registry: string,
/**
* Registry username
*/
username: string,
/**
* Registry password
*/
password: Secret,
): Promise<string[]> {
const tags = ["latest", "1.0-alpine", "1.0", "1.0.0"]
const addr: string[] = []
const container = dag
.container()
.from("nginx:1.23-alpine")
.withNewFile("/usr/share/nginx/html/index.html", "Hello from Dagger!", {
permissions: 0o400,
})
.withRegistryAuth(registry, username, password)
for (const tag in tags) {
const a = await container.publish(
`${registry}/${username}/my-nginx:${tags[tag]}`,
)
addr.push(a)
}
return addr
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\File;
use Dagger\Secret;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Tag a container image multiple times and publish it to a private registry')]
public function publish(
#[Doc('registry address')]
string $registry,
#[Doc('registry username')]
string $username,
#[Doc('registry password')]
Secret $password,
): string {
$tags = ['latest', '1.0-alpine', '1.0', '1.0.0'];
$address = [];
$container = dag()
->container()
->from('nginx:1.23-alpine')
->withNewFile('/usr/share/nginx/html/index.html', 'Hello from Dagger!', 400)
->withRegistryAuth($registry, $username, $password);
foreach ($tags as $tag) {
$a = $container->publish("$registry/$username/my-nginx:$tag");
$address[] = $a;
}
return implode(',', $address);
}
}
Examples​
Tag and publish a just-in-time container image to Docker Hub, using the account username user and the password set in the PASSWORD environment variable:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'publish docker.io user env://PASSWORD'
publish docker.io user env://PASSWORD
dagger call publish --registry=docker.io --username=user --password=env://PASSWORD
Tag and publish a just-in-time container image to GitHub Container Registry, using the account username user and the GitHub personal access token set in the PASSWORD environment variable:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'publish ghcr.io user env://PASSWORD'
publish ghcr.io user env://PASSWORD
dagger call publish --registry=ghcr.io --username=user --password=env://PASSWORD
Export a container image to the host​
The following Dagger Function returns a just-in-time container. This can be exported to the host as an OCI tarball.
- Go
- Python
- TypeScript
- PHP
package main
import "dagger/my-module/internal/dagger"
type MyModule struct{}
// Return a container
func (m *MyModule) Base() *dagger.Container {
return dag.Container().
From("alpine:latest").
WithExec([]string{"mkdir", "/src"}).
WithExec([]string{"touch", "/src/foo", "/src/bar"})
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def base(self) -> dagger.Container:
"""Return a container"""
return (
dag.container()
.from_("alpine:latest")
.with_exec(["mkdir", "/src"])
.with_exec(["touch", "/src/foo", "/src/bar"])
)
import { dag, Container, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container
*/
@func()
base(): Container {
return dag
.container()
.from("alpine:latest")
.withExec(["mkdir", "/src"])
.withExec(["touch", "/src/foo", "/src/bar"])
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container')]
public function base(): Container
{
return dag()
->container()
->from('alpine:latest')
->withExec(['mkdir', '/src'])
->withExec(['touch', '/src/foo', '/src/bar']);
}
}
Examples​
Load the container image returned by the Dagger Function into Docker:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'base | export-image myimage'
base | export-image myimage
dagger call base export-image --name myimage
Load the container image returned by the Dagger Function as a tarball to the host fileysystem:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'base | export /home/admin/mycontainer.tgz'
base | export /home/admin/mycontainer.tgz
dagger call base export --path=/home/admin/mycontainer.tgz
Set environment variables in a container​
The following Dagger Function demonstrates how to set a single environment variable in a container.
- Go
- Python
- TypeScript
- PHP
package main
import "context"
type MyModule struct{}
// Set a single environment variable in a container
func (m *MyModule) SetEnvVar(ctx context.Context) (string, error) {
return dag.Container().
From("alpine").
WithEnvVariable("ENV_VAR", "VALUE").
WithExec([]string{"env"}).
Stdout(ctx)
}
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def set_env_var(self) -> str:
"""Set a single environment variable in a container"""
return await (
dag.container()
.from_("alpine")
.with_env_variable("ENV_VAR", "VALUE")
.with_exec(["env"])
.stdout()
)
import { dag, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Set a single environment variable in a container
*/
@func()
async setEnvVar(): Promise<string> {
return await dag
.container()
.from("alpine")
.withEnvVariable("ENV_VAR", "VALUE")
.withExec(["env"])
.stdout()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
// Set a single environment variable in a container
#[DaggerFunction]
public function setEnvVar(): string
{
return dag()
->container()
->from('alpine')
->withEnvVariable('ENV_VAR', 'VALUE')
->withExec(['env'])
->stdout();
}
}
The following Dagger Function demonstrates how to set multiple environment variables in a container.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
type EnvVar struct {
Name string
Value string
}
// Set environment variables in a container
func (m *MyModule) SetEnvVars(ctx context.Context) (string, error) {
return dag.Container().
From("alpine").
With(EnvVariables([]*EnvVar{
{"ENV_VAR_1", "VALUE 1"},
{"ENV_VAR_2", "VALUE 2"},
{"ENV_VAR_3", "VALUE 3"},
})).
WithExec([]string{"env"}).
Stdout(ctx)
}
func EnvVariables(envs []*EnvVar) dagger.WithContainerFunc {
return func(c *dagger.Container) *dagger.Container {
for _, e := range envs {
c = c.WithEnvVariable(e.Name, e.Value)
}
return c
}
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def set_env_vars(self) -> str:
"""Set environment variables in a container"""
return await (
dag.container()
.from_("alpine")
.with_(
self.env_variables(
[
("ENV_VAR_1", "VALUE 1"),
("ENV_VAR_2", "VALUE 2"),
("ENV_VAR_3", "VALUE 3"),
]
)
)
.with_exec(["env"])
.stdout()
)
def env_variables(self, envs: list[tuple[str, str]]):
def env_variables_inner(ctr: dagger.Container):
for key, value in envs:
ctr = ctr.with_env_variable(key, value)
return ctr
return env_variables_inner
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Set environment variables in a container
*/
@func()
async setEnvVars(): Promise<string> {
return await dag
.container()
.from("alpine")
.with(
envVariables([
["ENV_VAR_1", "VALUE 1"],
["ENV_VAR_2", "VALUE 2"],
["ENV_VAR_3", "VALUE_3"],
]),
)
.withExec(["env"])
.stdout()
}
}
function envVariables(envs: Array<[string, string]>) {
return (c: Container): Container => {
for (const [key, value] of envs) {
c = c.withEnvVariable(key, value)
}
return c
}
}
Example​
Set a single environment variable in a container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c set-env-var
set-env-var
dagger call set-env-var
Set multiple environment variables in a container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c set-env-vars
set-env-vars
dagger call set-env-vars
Use volatile variables for exec-time metadata​
withVolatileVariable sets a non-secret environment variable for future withExec calls without invalidating exec cache when only the variable's value changes.
Typical examples include CI and reporting metadata such as:
- commit SHA
- branch or ref
- CI run ID
withVolatileVariable is an expert-only escape hatch. Use it only when you are certain that changing the variable alone must not invalidate cached withExec results. If that assumption is wrong, Dagger may reuse stale or incorrect cached results.
Unlike withEnvVariable, volatile variables:
- are visible only to future
withExeccalls - are not persisted into the container image config
- are not returned by
envVariableorenvVariables - are not available to
expand: true
Use withEnvVariable for normal container configuration, withSecretVariable for sensitive values, and withVolatileVariable only for exec-time metadata that should not decide cache reuse.