🗡️ Dagger
TL;DR:
- Dagger is a bit like Make, but with Docker containers.
- Dagger might be worth a look if you have a complex build or other pipelines.
The idea is nice that you wouldn't have to push to a remote repository to test your CI/CD pipeline. But it's not worth the overhead for simple projects, especially compared to Make or whatever you prefer.
Dagger is CI/CD engine that uses Docker containers to run pipeline steps. This allows you to run all steps locally in the same way as in the CI/CD environment with little overhead and setup.
```bash
dagger init --sdk=python
# and edit the dagger/src/main/__init__.py
import random
import dagger
from dagger import function, dag, object_type
@object_type
class MyDagger:
@function
async def publish(self, source: dagger.Directory) -> str:
# tests, builds and publishes a container image of the application to a registry
await self.test(source)
return await self.build(source).publish(
f"ttl.sh/myapp-{random.randrange(10 ** 8)}"
)
@function
def build(self, source: dagger.Directory) -> dagger.Container:
# performs a multi-stage build and returns a final container image
build = (
self.build_base(source)
.with_exec(["npm", "run", "build"])
.directory("./dist")
)
return (
dag.container()
.from_("nginx:1.25-alpine")
.with_directory("/usr/share/nginx/html", build)
.with_exposed_port(8080)
)
@function
async def test(self, source: dagger.Directory) -> str:
# runs the application's unit tests and returns the results
return await (
self.build_base(source)
.with_exec(["npm", "run", "test:unit", "run"])
.stdout()
)
@function
def build_base(self, source: dagger.Directory) -> dagger.Container:
# creates a container with the build environment for the application
node_cache = dag.cache_volume("node")
return (
dag.container()
.from_("node:21-slim")
.with_directory("/src", source)
.with_mounted_cache("/src/node_modules", node_cache)
.with_workdir("/src")
.with_exec(["npm", "install"])
)
Each Dagger function runs in a separate container. This allows using different languages or version for each pipeline step.
# these call Dagger Functions from the Python definitions
dagger call build-base --source=. terminal --cmd=bash
dagger call test --source=.
dagger call build --source=.
dagger call publish --source=.
Dagger has very limited access to the host system. That is why you need to give the --source=.
to each call.
Dagger can also be used to maintain a local development environment.
dagger call build --source=. as-service up --ports=8080:80
But this does have some downsides as discussed below.
Each Dagger function receives a separate copy of the source code. This is a blessing and a curse; nothing can affect what is running but also means if you change something, you need to rerun the whole Dagger function.
If you have a Dagger function that starts a development server; by default, the source code won't get updated if you change the code in your IDE.
CI/CD platform support is great. It's fairly straightforward to use Dagger through popular CI/CD services like GitHub Actions, GitLab CI/CD, or CircleCI.
name: dagger
on:
push:
branches: [ main ]
jobs:
build:
name: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Call Dagger Function
uses: dagger/dagger-for-github@v5
with:
version: "latest"
verb: call
module: github.com/kpenfound/dagger-modules/golang@v0.2.0
args: build --project=. --args=.