COPYonly the minimal amount of files. Avoid copying
COPYentire large directories into the build environment and only using a subset of the files within them. Or worse, copying the entire repository (which might also include
.git) for no good reason.
git fetchmight cause slow commands like
go mod downloadto be re-executed.
COPYcommands (among other things) to mark certain files as inputs to the build. If any file included in a
COPYchanges, then the build will continue from that
COPYcommand onwards. For this reason, you want to be as specific as possible when including files in a
COPYcommand. In some cases, you might even have to list files individually.
.gitor to unrelated files, like
README.md. However, this can be arranged even better, to avoid downloading all the dependencies on every
COPYcommand is to use the
.earthlyignorefile. Note, however, that this is best left as a last resort, as new files added to the project (that may be irrelevant to builds) would need to be manually added to
.earthlyignore, which may be error-prone. It is much better to have to include every new file manually into the build (by adding it to a
COPYcommand), than to exclude every new file manually (by adding it to the
.earthlyignore), as whenever any such new file must be included, then the build would typically fail, making it harder to make a mistake compared to the opposite.
ENVfor image env vars,
ARGfor build configurability
ARGvariables seem similar, however they are meant for different use-cases. Here is a breakdown of the differences, as well as how they differ from the Dockerfile-specific
ENVis needed is when you want the value to be stored as part of the final image's configuration. This causes any
docker runusing that image to inherit the value.
ARGis the way to achieve that.
GIT CLONEif possible
RUN git clone, whenever possible.
SAVE ARTIFACT. This makes the code more readable and maintainable. The fact that an artifact is saved during a build constitutes an explicit API of the repository.
GIT CLONEmight help to provide a faster, yet imperfect solution.
GIT CLONEis better suited is when the operation needs to take place on the whole source repository. For example, performing Git operations, such as tagging, creating branches, or merging.
RUN git clone
GIT CLONEinstruction that can be used to clone a Git repository. It is recommended that
GIT CLONEis used rather than
RUN git clone, for a few reasons:
GIT CLONEas a first-class input (BuildKit source). As such, Earthly caches the repository internally and downloading only incremental differences on changes.
RUN git clonewould not be able to detect that, as it is not recognized as an input. So it would naively reuse the cache when it shouldn't.
GIT CLONEdoes have some limitations, however. It only performs a shallow clone, it does not have the branch information, it does not have origin information, and it does not have the tags downloaded. Even in such cases, it might be better to attempt to reintroduce the information after a
GIT CLONE, whenever possible, in order to gain the caching benefits.
RUN git clone, consider using both in conjunction, to gain the hash awareness benefits.
RUN git clone:
RUN if [...]
IFcommand, which allows for complex control flow within Earthly recipes. However, there is also the possibility of using the shell
ifcommand to accomplish similar behavior. Which one should you use? Here is a quick comparison:
IFis more powerful in that it can include other Earthly commands within it, allowing for rich conditional behavior. Examples might include optionally saving images, using different base images depending on a set of conditions, initializing
ARGs with varying values.
RUN if, however is often simpler, and it only uses one layer.
RUN ifwhenever possible (e.g. only
RUNcommands would be involved), to encourage simplicity, and otherwise to use
FOR ... IN ...vs
RUN for ... in ...
RUN if, there is a similar debate for the Earthly builtin command
RUN for. Here is a quick comparison of the two 'for' flavors:
FORis more powerful in that it can include other Earthly commands within it, allowing for rich iteration behavior. Examples might include iterating over a list of directories in a monorepo and calling Earthly targets within them, performing
SAVE IMAGEover a list of container image tags.
RUN for, however is often simpler, and it only uses one layer.
RUN forwhenever possible (e.g. only
RUNcommands would be involved), to encourage simplicity, and otherwise to use
LOCALLY. Here is an example:
ARGto decide on whether to execute the target on the host or not:
earthly +my-target --run_locally=true, otherwise
earthly +my-targetwill execute in the sandboxed environment (the same way it executes in CI).
IFexpression. For example, let's assume that the company provided Go image only supports the
linux/amd64platform, and therefore, you'd like to use the official golang image when ARM (
linux/arm64) is detected. Here's how this can be achieved:
FROMs within the same target. This is completely valid. On encountering another
FROMexpression, the current build environment is reset and another fresh root is initialized, containing the specified images data.
RUN --pushfor deployment commands
SAVE IMAGE --push).
RUNcommand together with the
--pushwill ensure that:
RUN --no-cacheshould be avoided for this use-case, as it has some potentially dangerous downsides:
ARGs to pass secrets to the build
ARGs for passing secrets is strongly discouraged, as the secrets will be leaked in build logs, the build cache and the possibly in published images.
rmis forgotten, or if the removal is performed under a separate
+buildstarts, the artifact would not have been output yet. In fact,
+buildwill run completely parallel anyway - as Earthly does not know of a dependency between them.
+depno longer needs to save the file locally. Also, the
COPYcommand no longer references the file from the local file system. It has been replaced with an artifact reference from the target
+dep. This reference will tell Earthly that these two targets depend on each other and will therefore schedule the relevant parts to run sequentially.
+alltarget, we no longer have to call both
+build. The system will automatically infer that when building
+depis also required.
+testruns, it will not have the image available, unless it has been pushed in a previous execution (which means that the image may be stale).
--loadinstruction will inform Earthly that the two targets depend on each other and will therefore build the image and load it into the Docker daemon provided by
WITH DOCKER --pull
WITH DOCKERblock, it is important to declare it via
WITH DOCKER --pull, for a few reasons:
WITH DOCKERwipes the state of the Docker daemon (including its cache) after every run.
WITH DOCKERis not logged into registries. Your local Docker login config is not propagated to the daemon. This means that you may run into issues when trying to pull images from private registries, but also, DockerHub rate limiting may prevent you from pulling images consistently from public repositories.
WITH DOCKER --compose, Earthly will automatically pull images declared in the compose file for you, as long as they are not already being loaded from another target via
WITH DOCKER --load. So in this case, you do not need to declare those image with
WITH DOCKER --pull.
COPY +my-target/...to pass files to and from
LOCALLY, it is tempting to skip on using Earthly constructs for passing files between targets. However, this can be problematic.
+buildis not guaranteed. So in some runs, the file
./my-artifact.txtwill be created before the
+buildtarget is executed, and in some runs it will be created after.
SAVE ARTIFACT ... AS LOCAL ...for the transfer of the artifact to the
LOCALLYtarget. As Earthly outputs are written at the end of the build, the target
+run-locallywill not have the file in time (or it might have it from a previous run only, meaning that it might be stale).
COPYcommand using an artifact reference will inform Earthly of the dependency between the two targets, and will therefore cause the transfer of artifact between the two properly.
COPYand an artifact reference.
WITH DOCKER --load=+my-targetto pass images to
LOCALLYtarget, the image needs to be output in the middle of the build.
WITH DOCKER --loadand a target reference:
--loadinstruction will inform Earthly of the dependency and will therefore cause the image to be output right before the
ARGwith values that include randomness
COPY --dirto copy multiple directories
COPYcommand differs from the unix
cpin that it will copy directory contents, not the directories themselves. This requires that copying multiple directories to be split across multiple lines:
COPY --dir, which makes
COPYbehave more like
cpand less like the Dockerfile
--dirflag can be used therefore to copy multiple directories in a single command:
gobinary. After the application binary has been built via
go build, there is no longer a need for the
gobinary. So the production image should not contain it. Here is an example:
SAVE ARTIFACT ... AS LOCAL ...for generated code, not
SAVE ARTIFACT ... AS LOCAL ...via regular Earthly targets, rather than via running the generation command in
LOCALLY. There are multiple reasons for this:
LOCALLYloses the repeatability benefits. This means that the same command could end up generating different code, depending on the system it is being run on. Differences in the environment, such as the version of code generator installed (e.g.
protoc), or certain environment variables (e.g.
GOPATH) could cause the generated code to be different.
LOCALLYwill not be usable in the CI, as the CI script would typically enable
\). Remember to chain multiple shell commands via
&&in order to correctly exit if one of the commands fails.
SAVE ARTIFACTand then copied over using an artifact reference.
some-file.txtis copied from the sibling directory
dir1. This will not work in Earthly as the file is not in the build context of
./dir2/Earthfile(the build context in this case is
./dir2). To address this issue, we can create an Earthfile in
dir1that exports the file
some-file.txtas an artifact.
dir1, where all the files required outside of it are explicitly exported.
LOCALLYis not allowed in
--strictmode (or in
--cimode), as it introduces a dependency from the host machine, which may interfere with the repeatability property of the build.
SAVE IMAGEcommands, respectively. Those artifacts can then be referenced in higher-level Earthfiles via artifact and target references (
COPY ./deep/dir+some-target/an/artifact ...,
.protofiles, in its own Earthfile.
docker build -f ./services/app1.Dockerfile ./app1-src-dir ...and so on.
docker build -foption.
+build-for-windowsitself exports the artifacts means that it can be referenced directly in other targets as
COPY +build-for-windows/output ./.
+build-wrapperto reuse the logic in
+build, but ultimately to create an image that is saved under a different name. This can then be used in a
WITH DOCKER --loadstatement directly (whereas if there was no image pass-through, then
+build-wrappercouldn't have been used).
WITH DOCKER, it is recommended that you use the official
:alpine) for running Docker-in-Docker. Earthly's
WITH DOCKERrequires that the Docker engine is installed already in the image it is running in.
WITH DOCKERwill need to first install it - it usually does so automatically - however, the cache will be inefficient. Consider the following example:
some-other-image:latestdoes not already have Docker engine installed. This means that on the
WITH DOCKERline, Earthly will add a hidden installation step, to add Docker engine. This takes some time to execute, but it will work.
docker-compose.yml. That will cause the build to re-execute without cache from the
COPYcommand onwards, meaning that the installation of Docker engine will be repeated.
earthly/dindimage, if possible.
WITH DOCKERstarts up a transient Docker daemon for that specific instruction and then shuts it down and completely wipes its data afterwards. That does not mean, however, that you cannot export any information from it (or from any container running within), to be used in another part of the build. Although you may not run any non-
WITH DOCKER, you can still use
SAVE ARTIFACT(and any other command) after the
WITH DOCKERinstruction. The Docker daemon's data is wiped - but the rest of the build environment remains intact.
--ciwhen running in CI
LOCALLY, which cause Earthly to be less repeatable, and yet might satisfy very much needed use-cases that are typically out of scope of a CI build.
--ciflag, which simply expands to
--no-output --use-inline-cache --save-inline-cache --strict. The
--ciflag therefore, prevents the use of commands that are not repeatable, enables inline caching and disables outputting artifacts and images on the CI host.
LOCALLYand other non-strict commands
earthly --strict +my-targetor
earthly --ci +my-target), the usage of these commands is not allowed.
--ciis passed in.
LOCALLYcommand skips the sandboxing of the build and executes all commands directly on the host machine.
LOCALLYmay be used are:
mainbranch only, and to disable any pushing on PR builds. Although the
mainbuild will be slower, it will allow for maximum use of cache in PR builds, without the slowdown of further pushes.
earthly --ci --push +target. PR build:
earthly --ci +target.
EARTHLY_PUSH, which may be easier to manipulate in your CI of choice.