Tools: Earthly Has a Free API: Repeatable CI/CD Builds That Work Everywhere (2026)

Tools: Earthly Has a Free API: Repeatable CI/CD Builds That Work Everywhere (2026)

What Is Earthly?

Installation

Earthfile Example

Running Earthly

Python Project Earthfile

Monorepo Setup

Earthly vs Alternatives Earthly is a build automation tool that combines the best of Makefiles and Dockerfiles. It gives you repeatable, containerized builds that work the same on your laptop and in CI. Earthly provides a syntax that combines Dockerfile and Makefile concepts into Earthfiles. Every build step runs in a container, so builds are reproducible regardless of the host environment. No more 'works on my machine' problems. Need to scrape web data for your build pipelines? Check out my web scraping tools on Apify — production-ready actors for Reddit, Google Maps, and more. Questions? Email me at [email protected] Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

# Install Earthly -weight: 500;">curl -sSL https://earthly.dev/get-earthly | sh # Verify earthly --version # Bootstrap (sets up BuildKit) earthly bootstrap # Install Earthly -weight: 500;">curl -sSL https://earthly.dev/get-earthly | sh # Verify earthly --version # Bootstrap (sets up BuildKit) earthly bootstrap # Install Earthly -weight: 500;">curl -sSL https://earthly.dev/get-earthly | sh # Verify earthly --version # Bootstrap (sets up BuildKit) earthly bootstrap # Earthfile VERSION 0.8 # Base image for all targets FROM node:20-alpine WORKDIR /app # Install dependencies (cached) deps: COPY package.json package-lock.json . RUN -weight: 500;">npm ci SAVE ARTIFACT node_modules # Run tests test: FROM +deps COPY . . RUN -weight: 500;">npm test # Build the app build: FROM +deps COPY . . RUN -weight: 500;">npm run build SAVE ARTIFACT dist AS LOCAL ./dist # Create Docker image -weight: 500;">docker: FROM +build EXPOSE 3000 CMD ["node", "dist/server.js"] SAVE IMAGE my-app:latest # Multi-platform build -weight: 500;">docker-multiplatform: BUILD --platform=linux/amd64 --platform=linux/arm64 +-weight: 500;">docker # Integration tests integration-test: FROM earthly/dind:alpine COPY -weight: 500;">docker-compose.test.yml . WITH DOCKER --compose -weight: 500;">docker-compose.test.yml RUN -weight: 500;">npm run test:integration END # CI target: run everything ci: BUILD +test BUILD +build BUILD +-weight: 500;">docker BUILD +integration-test # Earthfile VERSION 0.8 # Base image for all targets FROM node:20-alpine WORKDIR /app # Install dependencies (cached) deps: COPY package.json package-lock.json . RUN -weight: 500;">npm ci SAVE ARTIFACT node_modules # Run tests test: FROM +deps COPY . . RUN -weight: 500;">npm test # Build the app build: FROM +deps COPY . . RUN -weight: 500;">npm run build SAVE ARTIFACT dist AS LOCAL ./dist # Create Docker image -weight: 500;">docker: FROM +build EXPOSE 3000 CMD ["node", "dist/server.js"] SAVE IMAGE my-app:latest # Multi-platform build -weight: 500;">docker-multiplatform: BUILD --platform=linux/amd64 --platform=linux/arm64 +-weight: 500;">docker # Integration tests integration-test: FROM earthly/dind:alpine COPY -weight: 500;">docker-compose.test.yml . WITH DOCKER --compose -weight: 500;">docker-compose.test.yml RUN -weight: 500;">npm run test:integration END # CI target: run everything ci: BUILD +test BUILD +build BUILD +-weight: 500;">docker BUILD +integration-test # Earthfile VERSION 0.8 # Base image for all targets FROM node:20-alpine WORKDIR /app # Install dependencies (cached) deps: COPY package.json package-lock.json . RUN -weight: 500;">npm ci SAVE ARTIFACT node_modules # Run tests test: FROM +deps COPY . . RUN -weight: 500;">npm test # Build the app build: FROM +deps COPY . . RUN -weight: 500;">npm run build SAVE ARTIFACT dist AS LOCAL ./dist # Create Docker image -weight: 500;">docker: FROM +build EXPOSE 3000 CMD ["node", "dist/server.js"] SAVE IMAGE my-app:latest # Multi-platform build -weight: 500;">docker-multiplatform: BUILD --platform=linux/amd64 --platform=linux/arm64 +-weight: 500;">docker # Integration tests integration-test: FROM earthly/dind:alpine COPY -weight: 500;">docker-compose.test.yml . WITH DOCKER --compose -weight: 500;">docker-compose.test.yml RUN -weight: 500;">npm run test:integration END # CI target: run everything ci: BUILD +test BUILD +build BUILD +-weight: 500;">docker BUILD +integration-test # Run a specific target earthly +build # Run tests earthly +test # Build Docker image earthly +-weight: 500;">docker # Run full CI pipeline earthly +ci # Multi-platform build earthly +-weight: 500;">docker-multiplatform # Run a specific target earthly +build # Run tests earthly +test # Build Docker image earthly +-weight: 500;">docker # Run full CI pipeline earthly +ci # Multi-platform build earthly +-weight: 500;">docker-multiplatform # Run a specific target earthly +build # Run tests earthly +test # Build Docker image earthly +-weight: 500;">docker # Run full CI pipeline earthly +ci # Multi-platform build earthly +-weight: 500;">docker-multiplatform VERSION 0.8 FROM python:3.12-slim WORKDIR /app deps: COPY requirements.txt . RUN -weight: 500;">pip -weight: 500;">install -r requirements.txt SAVE ARTIFACT /usr/local/lib/python3.12/site-packages test: FROM +deps COPY . . RUN pytest tests/ -v lint: FROM +deps COPY . . RUN ruff check . RUN mypy src/ build: FROM +deps COPY . . RUN python -m build SAVE ARTIFACT dist/*.whl AS LOCAL ./dist/ -weight: 500;">docker: FROM python:3.12-slim COPY +deps/ /usr/local/lib/python3.12/site-packages COPY . . ENTRYPOINT ["python", "-m", "myapp"] SAVE IMAGE myapp:latest VERSION 0.8 FROM python:3.12-slim WORKDIR /app deps: COPY requirements.txt . RUN -weight: 500;">pip -weight: 500;">install -r requirements.txt SAVE ARTIFACT /usr/local/lib/python3.12/site-packages test: FROM +deps COPY . . RUN pytest tests/ -v lint: FROM +deps COPY . . RUN ruff check . RUN mypy src/ build: FROM +deps COPY . . RUN python -m build SAVE ARTIFACT dist/*.whl AS LOCAL ./dist/ -weight: 500;">docker: FROM python:3.12-slim COPY +deps/ /usr/local/lib/python3.12/site-packages COPY . . ENTRYPOINT ["python", "-m", "myapp"] SAVE IMAGE myapp:latest VERSION 0.8 FROM python:3.12-slim WORKDIR /app deps: COPY requirements.txt . RUN -weight: 500;">pip -weight: 500;">install -r requirements.txt SAVE ARTIFACT /usr/local/lib/python3.12/site-packages test: FROM +deps COPY . . RUN pytest tests/ -v lint: FROM +deps COPY . . RUN ruff check . RUN mypy src/ build: FROM +deps COPY . . RUN python -m build SAVE ARTIFACT dist/*.whl AS LOCAL ./dist/ -weight: 500;">docker: FROM python:3.12-slim COPY +deps/ /usr/local/lib/python3.12/site-packages COPY . . ENTRYPOINT ["python", "-m", "myapp"] SAVE IMAGE myapp:latest # services/api/Earthfile VERSION 0.8 IMPORT ../shared AS shared build: FROM node:20-alpine COPY +shared/lib ./shared COPY . . RUN -weight: 500;">npm run build # Root Earthfile VERSION 0.8 all: BUILD ./services/api+build BUILD ./services/web+build BUILD ./services/worker+build # services/api/Earthfile VERSION 0.8 IMPORT ../shared AS shared build: FROM node:20-alpine COPY +shared/lib ./shared COPY . . RUN -weight: 500;">npm run build # Root Earthfile VERSION 0.8 all: BUILD ./services/api+build BUILD ./services/web+build BUILD ./services/worker+build # services/api/Earthfile VERSION 0.8 IMPORT ../shared AS shared build: FROM node:20-alpine COPY +shared/lib ./shared COPY . . RUN -weight: 500;">npm run build # Root Earthfile VERSION 0.8 all: BUILD ./services/api+build BUILD ./services/web+build BUILD ./services/worker+build - Reproducible containerized builds - Dockerfile + Makefile hybrid syntax - Automatic caching - Parallel execution - Multi-platform builds (ARM, x86) - Works with any CI system - Shared cache via Earthly Cloud - Monorepo-friendly - Earthly Docs - Earthly GitHub — 11K+ stars - Earthfile Reference