You may find that you need to run Docker commands inside a target. For those cases Earthly offers WITH DOCKER. WITH DOCKER will initialize a Docker daemon that can be used in the context of a RUN command.
Whenever you need to use WITH DOCKER we recommend (though it is not required) that you use Earthly's own Docker in Docker (dind) image: earthly/dind:alpine-3.18-docker-23.0.6-r4.
Notice WITH DOCKER creates a block of code that has an END keyword. Everything that happens within this block is going to take place within our earthly/dind:alpine-3.18-docker-23.0.6-r4 container.
Pulling an Image
hello:FROM earthly/dind:alpine-3.18-docker-23.0.6-r4 WITH DOCKER --pull hello-worldRUN docker run hello-world END
You can see in the command above that we can pass a flag to WITH DOCKER telling it to pull an image from Docker Hub. We can pass other flags to load in artifacts built by other targets--load or even images defined by docker-compose--compose. These images will be available within the context of WITH DOCKER's docker daemon.
Loading an Image
We can load in an image created by another target with the --load flag.
my-hello-world:FROM ubuntuCMD echo "hello world" SAVE IMAGE my-hello:latesthello:FROM earthly/dind:alpine-3.18-docker-23.0.6-r4 WITH DOCKER --load hello:latest=+my-hello-worldRUN docker run hello:latest END
A Real World Example
One common use case for WITH DOCKER is running integration tests that require other services. In this case we need to set up a redis service for our tests. For this we can user a docker-compose.yml.
VERSION 0.7FROM golang:1.15-alpine3.13WORKDIR /go-workdirdeps:COPY go.mod go.sum ./RUN go mod download SAVE ARTIFACT go.mod AS LOCAL go.mod SAVE ARTIFACT go.sum AS LOCAL go.sumtest-setup:FROM +depsCOPY main.go .COPY main_integration_test.go .ENV CGO_ENABLED=0ENTRYPOINT ["go", "test", "github.com/earthly/earthly/examples/go"] SAVE IMAGE test:latestintegration-tests:FROM earthly/dind:alpine-3.18-docker-23.0.6-r4COPY docker-compose.yml ./ WITH DOCKER --compose docker-compose.yml --load tests:latest=+test-setupRUN docker run --network=default_go/part6_default tests:latest END
When we use the --compose flag, Earthly will start up the services defined in the docker-compose file for us. In this case, we built a separate image that copies in our test files and uses the command to run the tests as its ENTRYPOINT. We can then load this image into our WITH DOCKER command. Note that loading an image will not run it by default, we need to explicitly run the image after we load it.
You'll need to use --allow-privileged (or -P for short) to run this example.
{"name":"api","version":"1.0.0","description":"","main":"index.js","scripts": {"test":"echo \"Error: no test specified\" && exit 1" },"author":"","license":"ISC","dependencies": {"cors":"^2.8.5","express":"^4.17.1","http-proxy-middleware":"^1.0.4","pg":"^8.7.3" }}
./api/package-lock.json (empty)
./api/server.js
constexpress=require('express');constpath=require('path');constcors=require("cors");constapp=express(),bodyParser=require("body-parser");port =3080;app.use(bodyParser.json());app.use(express.static(path.join(__dirname,'../my-app/build')));app.use(cors());constusers= [ {'first_name':'Lee','last_name':'Earth' }]app.get('/api/users', (req, res) => {console.log('api/users called!')res.json(users);});app.listen(port,'0.0.0.0', () => {console.log(`Server listening on the port::${port}`);});
The Earthfile is at the root of the directory.
./Earthfile
VERSION 0.7FROM node:13.10.1-alpine3.11WORKDIR /js-exampleapp-deps:COPY ./app/package.json ./COPY ./app/package-lock.json ./RUN npm install# Output these back in case npm install changes them. SAVE ARTIFACT package.json AS LOCAL ./app/package.json SAVE ARTIFACT package-lock.json AS LOCAL ./app/package-lock.jsonbuild-app:FROM +app-depsCOPY ./app/src ./app/srcRUN mkdir -p ./app/dist && cp ./app/src/index.html ./app/dist/RUN cd ./app && npx webpack SAVE ARTIFACT ./app/dist /dist AS LOCAL ./app/distapp-docker:FROM +app-depsARG tag='latest'COPY +build-app/dist ./app/distEXPOSE 8080ENTRYPOINT ["/js-example/node_modules/http-server/bin/http-server", "./app/dist"] SAVE IMAGE js-example:$tagapi-deps:COPY ./api/package.json ./COPY ./api/package-lock.json ./RUN npm install# Output these back in case npm install changes them. SAVE ARTIFACT package.json AS LOCAL ./api/package.json SAVE ARTIFACT package-lock.json AS LOCAL ./api/package-lock.jsonapi-docker:FROM +api-depsARG tag='latest'COPY ./api/server.js .RUN pwdRUN lsEXPOSE 3080ENTRYPOINT ["node", "server.js"] SAVE IMAGE js-api:$tag# Run your app and api side by sideapp-with-api:FROM earthly/dind:alpine-3.18-docker-23.0.6-r4RUN apk add curl WITH DOCKER \ --load app:latest=+app-docker \ --load api:latest=+api-dockerRUN docker run -d -p 3080:3080 api && \ docker run -d -p 8080:8080 app && \ sleep 5 && \ curl 0.0.0.0:8080 | grep 'Getting Started' && \ curl 0.0.0.0:3080/api/users | grep 'Earth' END
Now you can run earthly -P +app-with-api to run the app and api side-by-side.