Dagger & Dockerfile
We build packages, use Dagger commands, but why reinvent the wheel?
We are used to Dockerfiles, and all our projects already utilize them. So, let’s reuse this code with Dagger!
Let’s try to build our Dockerfile and publish it to a private registry like Scaleway.
Dockerfile
For now, I’ll start with a very simple Dockerfile. (Yes, it’s not using a Node.js user, but it’s just for the demo.)
Here it is:
FROM node:lts
WORKDIR /app
COPY . /app
EXPOSE 8080
CMD [ "npm", "run", "start" ]
Daggerization
Initialization
To daggerize our project, we’ll start by initializing Dagger with the TypeScript SDK:
dagger init --sdk=typescript --source=./dagger
Then, in dagger.json
, I’ll exclude node_modules
to avoid copying them during development.
This can cause issues between different OS and Node.js versions.
"exclude": [ "**/node_modules" ]
Build
We’ll first create a function to install dependencies.
@func() dev(source: Directory): Directory { const nodeCache = dag.cacheVolume("node") const npmInstall = dag .container() // start from a base Node.js container .from("node:lts") // add the source code at /src .withDirectory("/app", source) // mount the cache volume at /root/.npm .withMountedCache("/root/.npm", nodeCache) // change the working directory to /src .withWorkdir("/app") // run npm install to install dependencies .withExec(["npm", "install"]) // Return directory return npmInstall.directory("/app") }
This function allows us to return the directory with the installed dependencies for later use.
Let’s test it:
dagger call dev --source=.
Great! Now let’s build the Dockerfile.
Building the Dockerfile
Before trying to publish, let’s test the container live from Dagger.
Is that possible?
Yes! You can create services and expose them on your host. If you don’t expose them, you can use the service as an HTTP endpoint for your other functions.
@func() build(source: Directory): Service { const devDirectory = this.dev(source) return dag .container() .withDirectory("/app", devDirectory) .build(devDirectory, {dockerfile: "Dockerfile"}) .asService() }
I want to expose this as a service to verify that it works and the Dockerfile is properly applied.
If we simply run the following command, the service will start but won’t be accessible on our host:
dagger call build --source=.
Let’s expose the service using --ports
:
dagger call build --source=. up --ports 9002:8080
Our container is responding properly!
*[main][~/Documents/dev-aidalinfo/tmp/node-api-demo]$ curl http://localhost:9002Ready !!! 🔥🔥🔥
Perfect! Now, let’s revert it to container mode:
@func() build(source: Directory): Container { const devDirectory = this.dev(source) return dag .container() .withDirectory("/app", devDirectory) .build(devDirectory, {dockerfile: "Dockerfile"}) }
And let’s test exposing it on our host in container mode:
It works! 😲
Conclusion
We now have a function that builds using the Dockerfile. For publishing to a private registry, check out the next page!
I prefer to separate topics to keep the information digestible and well-structured.
Full Dagger Code
Here’s the complete index.ts
for Dagger:
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()export class NodeApiDemo {
@func() build(source: Directory): Container { const devDirectory = this.dev(source) return dag .container() .withDirectory("/app", devDirectory) .build(devDirectory, {dockerfile: "Dockerfile"}) }
@func() dev(source: Directory): Directory { const nodeCache = dag.cacheVolume("node") const npmInstall = dag .container() // start from a base Node.js container .from("node:lts") // add the source code at /src .withDirectory("/app", source) // mount the cache volume at /root/.npm .withMountedCache("/root/.npm", nodeCache) // change the working directory to /src .withWorkdir("/app") // run npm install to install dependencies .withExec(["npm", "install"]) // Return directory return npmInstall.directory("/app") }
}
GitHub Repository
https://github.com/Killian-Aidalinfo/node-api-demo-dockerfile