Ephemeral Services
Dagger Functions support service containers, enabling users to spin up additional services (as containers) and communicate with those services from their workflows.
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).
 
Use cases
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
 
Service containers
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.
 
Example
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
 - PHP
 - Java
 
package main
import (
	"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) HttpService() *dagger.Service {
	return dag.Container().
		From("python").
		WithWorkdir("/srv").
		WithNewFile("index.html", "Hello, world!").
		WithExposedPort(8080).
		AsService(dagger.ContainerAsServiceOpts{Args: []string{"python", "-m", "http.server", "8080"}})
}
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_exposed_port(8080)
            .as_service(args=["python", "-m", "http.server", "8080"])
        )
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!")
      .withExposedPort(8080)
      .asService({ args: ["python", "-m", "http.server", "8080"] })
  }
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction};
use Dagger\Service;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
    #[DaggerFunction]
    public function httpService(): Service
    {
        return dag()
            ->container()
            ->from('python')
            ->withWorkdir('/srv')
            ->withNewFile('index.html', 'Hello, world!')
            ->withExposedPort(8080)
            ->asService(args: ['python', '-m', 'http.server', '8080']);
    }
}
package io.dagger.modules.mymodule;
import static io.dagger.client.Dagger.dag;
import io.dagger.client.Container;
import io.dagger.client.Service;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
import java.util.List;
@Object
public class MyModule {
  @Function
  public Service httpService() {
    return dag().container()
        .from("python")
        .withWorkdir("/srv")
        .withNewFile("index.html", "Hello world!")
        .withExposedPort(8080)
        .asService(
            new Container.AsServiceArguments()
                .withArgs(List.of("python", "-m", "http.server", "8080")));
  }
}
See it in action:
Host services
This also works in the opposite direction: containers in Dagger Functions can communicate with services running on the host.
Example
Here's an example of how a workflow running in a Dagger Function can access and query a MariaDB database service running on the host:
- Go
 - Python
 - TypeScript
 - PHP
 - Java
 
package main
import (
	"context"
	"dagger/my-module/internal/dagger"
)
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()
  }
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction};
use Dagger\Service;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
    #[DaggerFunction]
    public function httpService(Service $svc): string
    {
        return 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();
    }
}
package io.dagger.modules.mymodule;
import static io.dagger.client.Dagger.dag;
import io.dagger.client.exception.DaggerQueryException;
import io.dagger.client.Service;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
import java.util.List;
import java.util.concurrent.ExecutionException;
@Object
public class MyModule {
  @Function
  public String userList(Service svc) throws ExecutionException, DaggerQueryException, InterruptedException {
    return dag().container()
        .from("mariadb:10.11.2")
        .withServiceBinding("db", svc)
        .withExec(
            List.of(
                "/usr/bin/mysql",
                "--user=root",
                "--password=secret",
                "--host=db",
                "-e",
                "SELECT Host, User FROM mysql.user"))
        .stdout();
  }
}
See it in action:
Learn more
- [Use services as function arguments]
 - [Create and return services from a function]
 - [Expose host services to a function]
 - [Start and stop services]
 - [Persist service state]