Now we can update our Earthfile to copy in the go.mod and go.sum.
./Earthfile
VERSION 0.6FROM golang:1.15-alpine3.13WORKDIR /go-workdirbuild:COPY go.mod go.sum .COPY main.go .RUN go build -o output/example main.go SAVE ARTIFACT output/example AS LOCAL local-output/go-exampledocker:COPY +build/example .ENTRYPOINT ["/go-workdir/example"] SAVE IMAGE go-example:latest
This works, but it is inefficient because we have not made proper use of caching. In the current setup, when a file changes, the corresponding COPY command is re-executed without cache, causing all commands after it to also re-execute without cache.
Caching
If, however, we could first download the dependencies and only afterwards copy and build the code, then the cache would be reused every time we changed the code.
./Earthfile
VERSION 0.6FROM golang:1.15-alpine3.13WORKDIR /go-workdirbuild:# Download deps before copying code.COPY go.mod go.sum .RUN go mod download# Copy and build code.COPY main.go .RUN go build -o output/example main.go SAVE ARTIFACT output/example AS LOCAL local-output/go-exampledocker:COPY +build/example .ENTRYPOINT ["/go-workdir/example"] SAVE IMAGE go-example:latest
For a primer into Dockerfile layer caching see this article. The same principles apply to Earthfiles.
Reusing Dependencies
In some cases, the dependencies might be used in more than one build target. For this use case, we might want to separate dependency downloading and reuse it. For this reason, let's consider breaking this out into a separate target called +deps. We can then inherit from +deps by using the command FROM +deps.
./Earthfile
VERSION 0.6FROM golang:1.15-alpine3.13WORKDIR /go-workdirdeps:COPY go.mod go.sum ./RUN go mod download# Output these back in case go mod download changes them. SAVE ARTIFACT go.mod AS LOCAL go.mod SAVE ARTIFACT go.sum AS LOCAL go.sumbuild:FROM +depsCOPY main.go .RUN go build -o output/example main.go SAVE ARTIFACT output/example AS LOCAL local-output/go-exampledocker:COPY +build/example .ENTRYPOINT ["/go-workdir/example"] SAVE IMAGE go-example:latest
Note that in our case, only the JavaScript version has an example where FROM +deps is used in more than one place: both in build and in docker. Nevertheless, all versions show how dependencies may be separated.
./Earthfile
VERSION 0.6FROM node:13.10.1-alpine3.11WORKDIR /js-exampledeps:COPY package.json ./COPY package-lock.json ./RUN npm install# Output these back in case npm install changes them. SAVE ARTIFACT package.json AS LOCAL ./package.json SAVE ARTIFACT package-lock.json AS LOCAL ./package-lock.jsonbuild:FROM +depsCOPY src srcRUN mkdir -p ./dist && cp ./src/index.html ./dist/RUN npx webpack SAVE ARTIFACT dist /dist AS LOCAL distdocker:FROM +depsCOPY +build/dist ./distEXPOSE 8080ENTRYPOINT ["/js-example/node_modules/http-server/bin/http-server", "./dist"] SAVE IMAGE js-example:latest
VERSION 0.6FROM openjdk:8-jdk-alpineRUN apk add --update --no-cache gradleWORKDIR /java-exampledeps:COPY build.gradle ./RUN gradle buildbuild:FROM +depsCOPY src srcRUN gradle buildRUN gradle install SAVE ARTIFACT build/install/java-example/bin AS LOCAL build/bin SAVE ARTIFACT build/install/java-example/lib AS LOCAL build/libdocker:COPY +build/bin binCOPY +build/lib libENTRYPOINT ["/java-example/bin/java-example"] SAVE IMAGE java-example:latest