Earthly:Dockerfile + Makefile 混合 CI 构建¶
为什么要学 Earthly¶
CI/CD 构建系统有两个经典痛点:
- "在 CI 上跑失败了,但本地没问题" —— 因为本地和 CI 环境不同
- Makefile 不可移植,Dockerfile 不支持多步骤编排 —— 两者各有所长但不能互补
Earthly 的核心洞察是:将 Dockerfile 的容器化隔离和 Makefile 的多目标编排合二为一。每个构建步骤都在容器中执行(保证可复现),同时支持依赖声明、缓存、并行执行和跨项目引用。
你写的 Earthfile 无论在本地还是 CI 上,行为完全一致。
核心优势: - 本地和 CI 行为一致(容器化执行) - 语法融合 Dockerfile 和 Makefile 的优点 - 内置缓存(比 Docker layer cache 更智能) - 原生并行执行 - Monorepo 友好 - 不绑定任何 CI 平台
核心概念¶
白话解释¶
| 概念 | 白话说明 |
|---|---|
| Earthfile | 构建配置文件(类似 Dockerfile + Makefile 的混合体) |
| Target | 一个构建目标(类似 Makefile 的 target),用 + 前缀表示 |
| Artifact | 构建产物(文件),可以在 target 之间传递 |
| Image | Docker 镜像输出 |
| FROM | 基础镜像(和 Dockerfile 一样) |
| COPY | 复制文件(扩展了 Dockerfile 语法,可以从其他 target 复制) |
| RUN | 执行命令 |
| SAVE ARTIFACT | 声明构建产物 |
| SAVE IMAGE | 声明输出镜像 |
| BUILD | 调用其他 target(类似 make 的依赖) |
Earthly vs 其他构建系统¶
| 特性 | Earthly | Makefile | Dockerfile | GitHub Actions | Bazel |
|---|---|---|---|---|---|
| 容器化隔离 | 是 | 否 | 是 | 部分 | 沙箱 |
| 本地/CI一致 | 是 | 否 | 部分 | 否 | 是 |
| 学习曲线 | 低 | 低 | 低 | 中 | 高 |
| 缓存 | 智能 | 手动 | 层级 | 手动 | 精确 |
| 并行 | 自动 | 有限 | 无 | 声明式 | 自动 |
| Monorepo | 好 | 差 | 差 | 中 | 好 |
| 语言无关 | 是 | 是 | 是 | 是 | 是 |
安装配置¶
安装¶
# macOS
brew install earthly
# Linux
sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly'
# Windows (WSL)
sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly'
初始化¶
前置要求¶
- Docker(Earthly 使用 BuildKit 后端)
- Linux / macOS / Windows (WSL)
快速上手¶
第一个 Earthfile¶
项目结构:
Earthfile:
VERSION 0.8
# 基础目标:安装依赖
deps:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# 构建目标
build:
FROM +deps
COPY src/ ./src/
SAVE ARTIFACT src /src
# 测试目标
test:
FROM +deps
COPY src/ ./src/
COPY tests/ ./tests/
RUN pytest tests/ -v
# 生成 Docker 镜像
docker:
FROM +deps
COPY src/ ./src/
ENTRYPOINT ["python", "src/main.py"]
SAVE IMAGE my-app:latest
# 一键执行所有
all:
BUILD +test
BUILD +docker
执行¶
输出产物到本地¶
进阶用法¶
1. 多语言 Monorepo¶
monorepo/
├── Earthfile ← 根 Earthfile
├── services/
│ ├── api/
│ │ ├── Earthfile ← 子项目 Earthfile
│ │ └── ...
│ └── worker/
│ ├── Earthfile
│ └── ...
└── libs/
└── shared/
├── Earthfile
└── ...
根 Earthfile:
VERSION 0.8
all:
BUILD ./services/api+docker
BUILD ./services/worker+docker
test:
BUILD ./services/api+test
BUILD ./services/worker+test
BUILD ./libs/shared+test
子项目 services/api/Earthfile:
VERSION 0.8
deps:
FROM python:3.12-slim
WORKDIR /app
# 引用共享库的产物
COPY ../libs/shared+build/lib ./lib/
COPY requirements.txt .
RUN pip install -r requirements.txt
test:
FROM +deps
COPY . .
RUN pytest
docker:
FROM +deps
COPY . .
SAVE IMAGE api:latest
2. 并行构建¶
VERSION 0.8
# 这三个 BUILD 会自动并行执行(无依赖关系)
all:
BUILD +lint
BUILD +test
BUILD +build
lint:
FROM +deps
RUN ruff check .
test:
FROM +deps
COPY tests/ ./tests/
RUN pytest
build:
FROM +deps
COPY src/ ./src/
RUN python -m build
SAVE ARTIFACT dist/* AS LOCAL ./dist/
3. 参数化构建¶
VERSION 0.8
ARG --global PYTHON_VERSION=3.12
ARG --global REGISTRY=ghcr.io/myorg
deps:
FROM python:${PYTHON_VERSION}-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
docker:
ARG tag=latest
FROM +deps
COPY src/ ./src/
ENTRYPOINT ["python", "src/main.py"]
SAVE IMAGE ${REGISTRY}/my-app:${tag}
使用:
4. 缓存优化¶
VERSION 0.8
deps:
FROM node:20-slim
WORKDIR /app
# 先复制 package.json(缓存依赖安装步骤)
COPY package.json package-lock.json .
RUN --mount=type=cache,target=/root/.npm \
npm ci
# 再复制源码
COPY . .
test:
FROM +deps
# 使用持久化缓存加速测试
RUN --mount=type=cache,target=./node_modules/.cache \
npm test
build:
FROM +deps
RUN npm run build
SAVE ARTIFACT dist /dist
5. 多平台构建¶
VERSION 0.8
docker:
# 同时构建 amd64 和 arm64
BUILD --platform=linux/amd64 --platform=linux/arm64 +image
image:
FROM python:3.12-slim
COPY src/ /app/
ENTRYPOINT ["python", "/app/main.py"]
SAVE IMAGE my-app:latest
6. Secret 管理¶
VERSION 0.8
deploy:
FROM +build
# Secret 不会进入缓存或镜像层
RUN --secret AWS_ACCESS_KEY_ID \
--secret AWS_SECRET_ACCESS_KEY \
aws s3 cp dist/ s3://my-bucket/ --recursive
使用:
# 从环境变量传递
earthly --secret AWS_ACCESS_KEY_ID --secret AWS_SECRET_ACCESS_KEY +deploy
# 从文件传递
earthly --secret-file github_token=~/.secrets/github_token +publish
7. CI 集成¶
GitHub Actions:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: earthly/actions-setup@v1
- name: Run tests
run: earthly +test
- name: Build and push
run: earthly --push +docker
env:
EARTHLY_TOKEN: ${{ secrets.EARTHLY_TOKEN }}
GitLab CI:
# .gitlab-ci.yml
build:
image: earthly/earthly:latest
services:
- docker:dind
script:
- earthly --ci +all
8. 集成测试(WITH DOCKER)¶
VERSION 0.8
integration-test:
FROM earthly/dind:alpine-3.19
WORKDIR /app
COPY docker-compose.test.yml .
COPY +build/app ./app
# 在构建步骤中启动 Docker 容器
WITH DOCKER \
--compose docker-compose.test.yml \
--load app:latest=+docker
RUN docker compose up -d && \
sleep 5 && \
./app/run-integration-tests.sh
END
9. 远程构建(Earthly Satellites)¶
# 使用远程构建节点(更快的 CI)
earthly --org myorg --satellite my-satellite +build
# 远程缓存共享(团队成员共享构建缓存)
earthly --remote-cache=ghcr.io/myorg/cache +build
常见问题¶
Q1: 和 Dockerfile 有什么区别¶
| 维度 | Earthfile | Dockerfile |
|---|---|---|
| 多阶段 | 命名 target,可复用 | 有限的 multi-stage |
| 产物导出 | SAVE ARTIFACT 到本地 | 只能导出镜像 |
| 调用其他文件 | BUILD ./sub/+target | 不支持 |
| 并行 | 自动并行 | 无 |
| 参数 | ARG + 命令行 | ARG(有限) |
| 测试 | 可以作为 target | 不适合 |
Q2: Earthly 的 cache 为什么比 Docker 好¶
- Docker 的 layer cache 是按行顺序的,前面一层变了后面全部失效
- Earthly 使用 BuildKit 的高级缓存:mount cache、跨 target 缓存、远程缓存共享
Q3: 需要 Docker 才能用吗¶
是的。Earthly 底层使用 BuildKit(Docker 的构建引擎),需要 Docker 守护进程。但你不需要写 Dockerfile,Earthfile 替代了它。
Q4: 构建很慢¶
# 1. 检查缓存是否生效
earthly --verbose +target
# 2. 使用 mount cache 加速依赖安装
# RUN --mount=type=cache,target=/root/.cache/pip pip install ...
# 3. 分离依赖安装和代码复制
# 4. 使用远程缓存(团队共享)
earthly --remote-cache=registry/cache +build
Q5: 如何调试构建失败¶
# 交互式调试(进入失败步骤的容器)
earthly --interactive +target
# 显示详细日志
earthly --verbose +target
# 不使用缓存(排除缓存问题)
earthly --no-cache +target
参考资源¶
| 资源 | 链接 |
|---|---|
| 官方网站 | https://earthly.dev |
| GitHub 仓库 | https://github.com/earthly/earthly |
| 文档 | https://docs.earthly.dev |
| Earthfile 参考 | https://docs.earthly.dev/docs/earthfile |
| 示例项目 | https://github.com/earthly/earthly/tree/main/examples |
| VS Code 扩展 | marketplace 搜索 "Earthfile Syntax Highlighting" |
| 博客 | https://earthly.dev/blog |
小结: Earthly 用一个优雅的抽象——"容器化的 Makefile"——解决了 CI 构建中最顽固的问题:可复现性。你写的 Earthfile 在任何机器上行为一致,不再有"CI 上挂了但本地跑不出来"的噩梦。如果你厌倦了维护复杂的 CI YAML 和不可靠的构建缓存,Earthly 值得尝试。