Skip to content

Targets

The Forge CI system automatically scans and executes a preset number of Earthly targets. This section describes each of those targets including any special logic associated with it. Each target includes a brief example using the Go programming language. This is only done for simplicity as each target can be used to support multiple languages/paradigms.

Check

The check target is used for performing static analysis on a project's code. This includes operations like validating formatting, linting, or scanning for vulnerable code. It can also include running analyzers on supporting artifacts of a project, like spellchecking a project's README.

The goal of the check target is to provide quick feedback on the validity of a project. This is why it is the first target that is run in the CI pipeline. As such, the speed of the check target invocation should be prioritized above all else. If possible, it should avoid building the underlying project or doing any computationally expensive tasks. The faster the target runs, the quicker developers can get feedback on "easy" fixes within the codebase.

Example

check:
    # Assume we've copied the source code in a previous target
    FROM +src

    # Validate that the code is formatted correctly
    RUN gofmt -l . | grep . && exit 1 || exit 0

    # Run a simple lint on the code
    RUN go vet ./...

Build

The build target is used for building the artifacts of a project. The concept of building is unique to a project and can include anything ranging from compiling a binary, to archiving interpreted code into a portable artifact, or generating static assets for a web frontend. In some cases, like with library code, making use of this target does not make sense and it should be skipped.

The primary purpose of the build target is to optimize caching. By forcing the project to build during this target, all future targets which rely on the build target will be optimized via caching. For example, the test target often relies on the project being built prior to running tests. Since the test target occurs after the build target it will immediately benefit from the build being cached.

The build target should prefer to always produce an artifact, where possible. Meaning, the target should use SAVE ARTIFACT on the resulting build at the end of execution. Dependent projects will often pull the artifact directly from this target.

Example

build:
    # Assume we've copied the source code in a previous target
    FROM +src

    # Compile our binary
    RUN go build -o bin/program cmd/main.go

    # Save the resulting binary as an artifact
    SAVE ARTIFACT bin/program program

Package

The package target is used for packaging dependent projects together. In some cases, a larger piece of software may be broken up into multiple projects inside of a repository. The package target should be used to create the final artifact for the projects.

For example, there may be one project reponsible for building static assets and another project building the server binary that depends on those assets. The server binary should utilize the package target to pull in the static assets and package them with the binary into a final artifact.

Example

package:
    FROM scratch

    RUN mkdir dist
    COPY +build/program dist/program
    COPY ../assets+build dist/assets

    SAVE ARTIFACT dist

Test

The test target is used for running both unit and integration tests. The target is often run in privileged mode to make use of docker-in-docker for running complex integration tests. This target is intentionally run after both the build and package targets in order to allow using the resulting artifacts and maximize caching.

The test target is the final validation step in the CI pipeline. The targets that follow assume that if the test target passes, the project is ready to be released, published, or deployed. It is therefore recommended that all necessary validation logic is included between the check and test targets.

Example

test:
    # Assume we've copied the source code in a previous target
    FROM +src

    # Run our unit tests
    RUN go test ./...

Publish

The publish target is used to generate the container for a project. It is an error to define the target and not generate an image and the CI system expects only a single image. The contents of the image are project-specific, but it's often the artifacts generated by the project in a deployable form. For example, an API server may copy the server binary into a vanilla image and automatically set it as the default entrypoint.

The publish target must expose two arguments: container and image. These arguments are given values at runtime by the CI system. The arguments should be used when saving the final image name and tag (see the example below). Omitting these arguments will cause the CI system to fail to find the saved image.

After the publish target is ran for a project, the CI system automatically publishes the resulting container image to all configured container registries. In the case where multiple platforms are specified for a project, the CI system will first upload each platform-specific image to all registries and then create a single multi-platform manifest that points to the images. Each image is tagged based on the tagging strategy defined in the blueprint global settings.

Example

publish:
    FROM debian:bookworm-slim

    ARG container
    ARG tag

    COPY +build/program program

    ENTRYPOINT ["/program"]
    SAVE IMAGE ${container}:${tag}

Release

The release target is used for creating GitHub Releases for a project. The target must produce at least one artifact. The contents of the artifact are specific to the project. For example, the artifact may be the binary file for a compiled project or a directory of assets for a frontend project.

After the release target is ran for a project, the CI system will check if there's a git tag present in the current run context. If a tag is found it will be compared against the current project to ensure it matches. In the case of a match, the CI system will then create a new GitHub release using the git tag as the name. Finally, the CI system will archive and compress the artifact produced by the release target and attach it as an asset to the release.

In the case where multiple platforms are specified for a project, the system will archive and compress each artifact separately. The generated archive name will have the associated platform added as a suffix in the file name. All archives will then be attached to the generated release as assets.

Example

release:
    FROM scratch

    COPY +build/program program

    SAVE ARTIFACT program

Docs

Warning

Only one docs target should be specified per repository. Defining more than one of these targets leads to undefined behavior. In the case where documentation is contained within a project, it should be copied from that project and included in the final artifact created by the target.

The docs target is a special target used for uploading documentation to GitHub Pages. The target should produce a single directory that contains all of the static assets for the documentation. The CI system will automatically publish the generated documentation to the default gh-pages branch.

In the case where the target is running outside of the default branch (i.e., main or master) the generated documentation will be published to a subfolder within the gh-pages branch. The subfolder will be named after the branch name. This allows previewing documentation generated by a branch by appending the branch name to the URL configured within GitHub Pages.

Example

docs:
    FROM +build

    SAVE ARTIFACT dist