Skip to main content


Dagger can be used to perform common CI tasks - testing, containerizing, publishing and more - for any PHP application.

The following code sample demonstrates how these CI tasks can be encapsulated as Dagger Functions in a Dagger module. It assumes:

  • A PHP 8.2.x Web application with:
    • Composer for package management
    • PHPUnit for application testing
  • Credentials to publish the containerized application image to a registry

Bootstrap a new module:

dagger init --name=my-module --sdk=go --source=./dagger

Update the generated dagger/main.go file with the following code:

package main

import (


type MyModule struct{}

// return container image with application source code and dependencies
func (m *MyModule) Build(source *dagger.Directory) *dagger.Container {
return dag.Container().
WithExec([]string{"apt-get", "update"}).
WithExec([]string{"apt-get", "install", "--yes", "git-core", "zip", "curl"}).
WithExec([]string{"docker-php-ext-install", "pdo", "pdo_mysql", "mysqli"}).
WithExec([]string{"sh", "-c", "sed -ri -e 's!/var/www/html!/var/www/public!g' /etc/apache2/sites-available/*.conf"}).
WithExec([]string{"sh", "-c", "sed -ri -e 's!/var/www/!/var/www/public!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf"}).
WithExec([]string{"a2enmod", "rewrite"}).
WithExec([]string{"sh", "-c", "curl -sS | php -- --install-dir=/usr/local/bin --filename=composer"}).
WithDirectory("/var/www", source.WithoutDirectory("dagger"), dagger.ContainerWithDirectoryOpts{
Owner: "www-data",
WithExec([]string{"chmod", "-R", "775", "/var/www"}).
WithMountedCache("/root/.composer", dag.CacheVolume("composer-cache")).
WithMountedCache("/var/www/vendor", dag.CacheVolume("composer-vendor-cache")).
WithExec([]string{"composer", "install"})

// return result of unit tests
func (m *MyModule) Test(ctx context.Context, source *dagger.Directory) (string, error) {
return m.Build(source).
WithEnvVariable("PATH", "./vendor/bin:$PATH", dagger.ContainerWithEnvVariableOpts{
Expand: true,

// return address of published container image
func (m *MyModule) Publish(ctx context.Context, source *dagger.Directory, version string, registryAddress string, registryUsername string, registryPassword *dagger.Secret, imageName string) (string, error) {
return m.Build(source).
WithLabel("org.opencontainers.image.title", "PHP with Dagger").
WithLabel("org.opencontainers.image.version", version).
// uncomment this to use a custom entrypoint file
// .WithExec([]string{"chmod", "+x", "/var/www/"}).
// .WithEntrypoint([]string{"/var/www/"}).
WithRegistryAuth(registryAddress, registryUsername, registryPassword).
Publish(ctx, fmt.Sprintf("%s/%s/%s", registryAddress, registryUsername, imageName))

The code sample above is illustrative only. Modify it to your application's specific requirements.

Here is an example of calling the Dagger Function to run an application's unit tests:

dagger call test --source=.

Here is an example of calling the Dagger Function to publish the application image to Docker Hub. Replace the DOCKER-HUB-USERNAME and DOCKER-HUB-PASSWORD placeholders with your Docker Hub credentials.

dagger call publish \
--source=. \
--version=0.1 \ \
--registry-username=DOCKER-HUB-USERNAME \
--registry-password=env:REGISTRY_PASSWORD \

Some PHP applications may need to perform specific operations on startup, such as running database migrations or reading/writing cache data. This can be accomplished by overriding the container's default entrypoint with a custom entrypoint script for startup operations.

To use such a script, uncomment the relevant lines in the code sample above and create a script named in the application directory. Here is an example of one such script, which runs database migrations for a Laravel Web application at startup:

php artisan migrate