docs/current_docs/extending/modules/services.mdx
Dagger Functions support service containers, enabling users to spin up additional long-running services (as containers) and communicate with those services from Dagger Functions.
This makes it possible to:
Some common scenarios for using services with Dagger Functions are:
Services instantiated by a Dagger Function run in service containers, which have the following characteristics:
A Dagger Function can create and return a service, which can then be used from another Dagger Function or from the calling host. Services in Dagger Functions are returned using the Service type.
Here is an example of a Dagger Function that returns an HTTP service. This service is used by another Dagger Function, which creates a service binding using the alias www and then accesses the HTTP service using this alias.
Here is an example call for this Dagger Function:
<Tabs groupId="shell"> <TabItem value="System shell"> ```shell dagger -c get ``` </TabItem> <TabItem value="Dagger Shell"> ```shell title="First type 'dagger' for interactive mode." get ``` </TabItem> <TabItem value="Dagger CLI"> ```shell dagger call get ``` </TabItem> </Tabs>The result will be:
Hello, world!
Services returned by Dagger Functions can also be exposed directly to the host. This enables clients on the host to communicate with services running in Dagger.
One use case is for testing, where you need to be able to spin up ephemeral databases against which to run tests. You might also use this to access a web UI in a browser on your desktop.
Here is another example call for the Dagger Function shown previously, this time exposing the HTTP service on the host
<Tabs groupId="shell"> <TabItem value="System shell"> ```shell dagger -c 'http-service | up' ``` </TabItem> <TabItem value="Dagger Shell"> ```shell title="First type 'dagger' for interactive mode." http-service | up ``` </TabItem> <TabItem value="Dagger CLI"> ```shell dagger call http-service up ``` </TabItem> </Tabs>By default, each service port maps to the same port on the host - in this case, port 8080. The service can then be accessed by clients on the host. Here's an example:
curl localhost:8080
The result will be:
Hello, world!
To specify a different mapping, use the additional --ports argument with a list of host/service port mappings. Here's an example, which exposes the service on host port 9000:
:::note
To bind ports randomly, use the --random argument.
:::
Dagger Functions can also receive host services as function arguments of type Service, in the form tcp://<host>:<port>. This enables client containers in Dagger Functions to communicate with services running on the host.
:::note This implies that a service is already listening on a port on the host, out-of-band of Dagger. :::
Here is an example of how a container running in a Dagger Function can access and query a MariaDB database service (bound using the alias db) running on the host.
Before calling this Dagger Function, use the following command to start a MariaDB database service on the host:
docker run --rm --detach -p 3306:3306 --name my-mariadb --env MARIADB_ROOT_PASSWORD=secret mariadb:10.11.2
Here is an example call for this Dagger Function:
<Tabs groupId="shell"> <TabItem value="System shell"> ```shell dagger -c 'user-list tcp://localhost:3306' ``` </TabItem> <TabItem value="Dagger Shell"> ```shell title="First type 'dagger' for interactive mode." user-list tcp://localhost:3306 ``` </TabItem> <TabItem value="Dagger CLI"> ```shell dagger call user-list --svc=tcp://localhost:3306 ``` </TabItem> </Tabs>The result will be:
Host User
% root
localhost mariadb.sys
localhost root
Global hostnames can be assigned to services. This feature is especially valuable for complex networking configurations, such as circular dependencies between services, by allowing services to reference each other by predefined hostnames, without requiring an explicit service binding.
Custom hostnames follow a structured format (<host>.<module id>.<session id>.dagger.local), ensuring unique identifiers across modules and sessions.
For example, you can now run two services that depend on each other, each using a hostname to refer to the other by name:
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">In this example, service A and service B are set up with custom hostnames svca and svcb, allowing each service to communicate with the other by hostname. This capability provides enhanced flexibility for managing service dependencies and interconnections within modular workflows, making it easier to handle complex setups in Dagger.
Here is an example call for this Dagger Function:
<Tabs groupId="shell"> <TabItem value="System shell"> ```shell dagger -c 'services | up --ports 8080:80' ``` </TabItem> <TabItem value="Dagger Shell"> ```shell title="First type 'dagger' for interactive mode." services | up --ports 8080:80 ``` </TabItem> <TabItem value="Dagger CLI"> ```shell dagger call services up --ports 8080:80 ``` </TabItem> </Tabs>Every service has an endpoint, and this endpoint can be obtained via the Dagger API. This feature is typically useful in two scenarios:
/etc/hosts level and not at the DNS level. As a result, a small number of applications might fail to resolve the service using the alias.The following example demonstrates starting an HTTP service manually, retrieving its endpoint, and then querying that service using a standard HTTP GET request:
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">Here is an example call for this Dagger Function:
<Tabs groupId="shell"> <TabItem value="System shell"> ```shell dagger -c get ``` </TabItem> <TabItem value="Dagger Shell"> ```shell title="First type 'dagger' for interactive mode." get ``` </TabItem> <TabItem value="Dagger CLI"> ```shell dagger call get ``` </TabItem> </Tabs>The result will be the NGINX welcome page.
Dagger cancels each service run after a 10 second grace period to avoid frequent restarts. To avoid relying on the grace period, use a cache volume to persist a service's data, as in the following example:
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">This example uses Redis's SAVE command to save the service's data to a cache volume. When a new instance of the service is created, it uses the same cache volume to recreate the original state.
Here is an example of using these Dagger Functions:
<Tabs groupId="shell"> <TabItem value="System shell"> ```shell dagger -c 'set foo 123' dagger -c 'get foo' ``` </TabItem> <TabItem value="Dagger Shell"> ```shell title="First type 'dagger' for interactive mode." set foo 123 get foo ``` </TabItem> <TabItem value="Dagger CLI"> ```shell dagger call set --key=foo --value=123 dagger call get --key=foo ``` </TabItem> </Tabs>The result will be:
123
Services are designed to be expressed as a Directed Acyclic Graph (DAG) with explicit bindings allowing services to be started lazily, just like every other DAG node. But sometimes, you may need to explicitly manage the lifecycle in a Dagger Function.
For example, this may be needed if the application in the service has certain behavior on shutdown (such as flushing data) that needs careful coordination with the rest of your logic.
The following example explicitly starts the Redis service and stops it at the end, ensuring the 10 second grace period doesn't get in the way, without the need for a persistent cache volume:
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">The following example demonstrates how services can be used in Dagger Functions, by creating a Dagger Function for application unit/integration testing against a bound MariaDB database service.
The application used in this example is Drupal, a popular open-source PHP CMS. Drupal includes a large number of unit tests, including tests which require an active database connection. All Drupal 10.x tests are written and executed using the PHPUnit testing framework. Read more about running PHPUnit tests in Drupal.
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">Here is an example call for this Dagger Function:
<Tabs groupId="shell"> <TabItem value="System shell"> ```shell dagger -c test ``` </TabItem> <TabItem value="Dagger Shell"> ```shell title="First type 'dagger' for interactive mode." test ``` </TabItem> <TabItem value="Dagger CLI"> ```shell dagger call test ``` </TabItem> </Tabs>The result will be:
PHPUnit 9.6.17 by Sebastian Bergmann and contributors.
Runtime: PHP 8.2.5
Configuration: /opt/drupal/web/core/phpunit.xml.dist
Testing
.....................S 22 / 22 (100%)
Time: 00:15.806, Memory: 315.00 MB
There was 1 skipped test:
1) Drupal\Tests\pgsql\Kernel\pgsql\KernelTestBaseTest::testSetUp
This test only runs for the database driver 'pgsql'. Current database driver is 'mysql'.
/opt/drupal/web/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificKernelTestBase.php:44
/opt/drupal/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
OK, but incomplete, skipped, or risky tests!
Tests: 22, Assertions: 72, Skipped: 1.
If you're not interested in what's happening in the background, you can skip this section and just trust that services are running when they need to be. If you're interested in the theory, keep reading.
Consider this example:
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">Here's what happens on the last line:
ping container's stdout, which requires the container to run.ping container has a service binding, redisSrv.redisSrv container, which recurses into this same process.redisSrv.ping container with the redis-srv alias magically added to /etc/hosts.Here's what happens on the last line:
ping container's stdout, which requires the container to run.ping container has a service binding, redis_srv.redis_srv container, which recurses into this same process.redis_srv.ping container with the redis-srv alias magically added to /etc/hosts.Here's what happens on the last line:
ping container's stdout, which requires the container to run.ping container has a service binding, redisSrv.redisSrv container, which recurses into this same process.redisSrv.ping container with the redis-srv alias magically added to /etc/hosts.Here's what happens on the last line:
ping container's stdout, which requires the container to run.ping container has a service binding, $redisSrv.$redisSrv container, which recurses into this same process.$redisSrv.ping container with the redis-srv alias magically added to /etc/hosts.Here's what happens on the last line:
ping container's stdout, which requires the container to run.ping container has a service binding, redisSrv.redisSrv container, which recurses into this same process.redisSrv.ping container with the redis-srv alias magically added to /etc/hosts.:::note
Dagger cancels each service run after a 10 second grace period to avoid frequent restarts, unless the explicit Start and Stop APIs are used.
:::
Services are based on containers, but they run a little differently. Whereas regular containers in Dagger are de-duplicated across the entire Dagger Engine, service containers are only de-duplicated within a Dagger client session. This means that if you run separate Dagger sessions that use the exact same services, they will each get their own "instance" of the service. This process is carefully tuned to preserve caching at each client call-site, while prohibiting "cross-talk" from one Dagger session's client to another Dagger session's service.
Content-addressed services are very convenient. You don't have to come up with names and maintain instances of services; just use them by value. You also don't have to manage the state of the service; you can just trust that it will be running when needed and stopped when not.
:::tip If you need multiple instances of a service, just attach something unique to each one, such as an instance ID. :::
Here's a more detailed client-server example of running commands against a Redis service:
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">This example relies on the 10-second grace period, which you should try to avoid. Depending on the 10-second grace period is risky because there are many factors which could cause a 10-second delay between calls to Dagger, such as excessive CPU load, high network latency between the client and Dagger, or Dagger operations that require a variable amount of time to process.
It would be better to chain both commands together, which ensures that the service stays running for both, as in the revision below:
<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">