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
# 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