Ephemeral Services
Dagger Functions support service containers, enabling users to spin up additional services (as containers) and communicate with those services from their pipelines.
This makes it possible to:
- Instantiate and return services from a Dagger Function, and then:
- Use those services in other Dagger Functions (container-to-container networking)
- Use those services from the calling host (container-to-host networking)
- Expose host services for use in a Dagger Function (host-to-container networking).
Services instantiated by a Dagger Function run in service containers, which have the following characteristics:
- Each service container has a canonical, content-addressed hostname and an optional set of exposed ports.
- Service containers are started just-in-time, de-duplicated, and stopped when no longer needed.
- Service containers are health checked prior to running clients.
Some common scenarios for using services with Dagger Functions are:
- Running a database service for local storage or testing
- Running end-to-end integration tests against a service
- Running sidecar services
Here is an example of a Dagger Function that returns an HTTP service, which can then be accessed from the calling host:
- Go
- Python
- TypeScript
package main
import (
"dagger/my-module/internal/dagger"
"dagger.io/dagger/dag"
)
type MyModule struct{}
func (m *MyModule) HttpService() *dagger.Service {
return dag.Container().
From("python").
WithWorkdir("/srv").
WithNewFile("index.html", "Hello, world!").
WithExec([]string{"python", "-m", "http.server", "8080"}).
WithExposedPort(8080).
AsService()
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def http_service(self) -> dagger.Service:
return (
dag.container()
.from_("python")
.with_workdir("/srv")
.with_new_file("index.html", "Hello, world!")
.with_exec(["python", "-m", "http.server", "8080"])
.with_exposed_port(8080)
.as_service()
)
import { dag, object, func, Service } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
httpService(): Service {
return dag
.container()
.from("python")
.withWorkdir("/srv")
.withNewFile("index.html", "Hello, world!")
.withExec(["python", "-m", "http.server", "8080"])
.withExposedPort(8080)
.asService()
}
}
See it in action:
This also works in the opposite direction: containers in Dagger Functions can communicate with services running on the host. Here's an example of how a pipeline running in a Dagger Function can access and query a MariaDB database service running on the host:
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
"dagger.io/dagger/dag"
)
type MyModule struct{}
func (m *MyModule) UserList(
ctx context.Context,
svc *dagger.Service,
) (string, error) {
return dag.Container().
From("mariadb:10.11.2").
WithServiceBinding("db", svc).
WithExec([]string{"/usr/bin/mysql", "--user=root", "--password=secret", "--host=db", "-e", "SELECT Host, User FROM mysql.user"}).
Stdout(ctx)
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def user_list(self, svc: dagger.Service) -> str:
return await (
dag.container()
.from_("mariadb:10.11.2")
.with_service_binding("db", svc)
.with_exec(
[
"/usr/bin/mysql",
"--user=root",
"--password=secret",
"--host=db",
"-e",
"SELECT Host, User FROM mysql.user",
]
)
.stdout()
)
import { dag, object, func, Service } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
async userList(svc: Service): Promise<string> {
return await dag
.container()
.from("mariadb:10.11.2")
.withServiceBinding("db", svc)
.withExec([
"/usr/bin/mysql",
"--user=root",
"--password=secret",
"--host=db",
"-e",
"SELECT Host, User FROM mysql.user",
])
.stdout()
}
}
See it in action: