gitignore与dockerignore文件的用法

在日常开发里,.gitignore.dockerignore 都是“排除文件”的配置,但它们服务的对象并不一样:

  • .gitignore 影响 Git 是否追踪某些文件。
  • .dockerignore 影响 Docker 构建镜像时,哪些文件不会被发送到构建上下文。

看起来只是少提交几个文件、少复制几个文件,实际它们会直接影响仓库干净程度、镜像构建速度、镜像安全性,以及多人协作时的稳定性。

为什么需要 ignore 文件

一个项目目录中通常会同时存在源码、构建产物、依赖缓存、本地配置、日志、临时文件等内容。

例如:

project/
├── src/
├── node_modules/
├── dist/
├── .env
├── app.log
└── Dockerfile

其中 src/ 是应该进入版本库的源码,node_modules/ 可以通过包管理器重新安装,dist/ 可能是构建输出,.env 里可能包含数据库密码或访问密钥,app.log 是运行时日志。

如果不做排除,Git 仓库会变得臃肿,Docker 构建上下文也会变大,甚至可能把敏感信息打进镜像。

.gitignore 的作用

.gitignore 用来告诉 Git:哪些未被追踪的文件不需要出现在 git status 中,也不应该被 git add 默认加入暂存区。

常见适合写入 .gitignore 的内容有:

  • 编译产物:dist/build/target/
  • 依赖目录:node_modules/vendor/
  • 日志文件:*.log
  • IDE 配置:.idea/.vscode/
  • 系统文件:.DS_StoreThumbs.db
  • 本地环境变量:.env.env.local

一个常见的 .gitignore 可以这样写:

# dependencies
node_modules/

# build output
dist/
build/

# logs
*.log
npm-debug.log*

# local env
.env
.env.local

# system files
.DS_Store
Thumbs.db

# IDE
.idea/
.vscode/

这样做以后,新产生的这些文件不会干扰 git status,也不容易被误提交。

.dockerignore 的作用

.dockerignore 用来告诉 Docker:执行 docker build 时,哪些文件不需要发送给 Docker daemon 作为构建上下文。

这和 .gitignore 的目标不同。Git 关心的是版本控制,Docker 关心的是构建镜像时能看到哪些文件。

例如执行:

docker build -t my-app .

命令最后的 . 表示当前目录会作为构建上下文。Docker 会把这个目录下的文件打包发送给构建进程,然后 Dockerfile 里的 COPYADD 才能使用这些文件。

如果没有 .dockerignore,一些完全不需要进镜像的内容也会被发送过去,比如 .git/node_modules/、测试报告、日志文件、密钥文件等。

一个常见的 .dockerignore 可以这样写:

.git
.gitignore

node_modules
npm-debug.log

dist
coverage

.env
.env.*

Dockerfile
docker-compose.yml

.DS_Store
README.md

注意,是否忽略 DockerfileREADME.md 取决于项目需求。它们不一定会进入镜像,但从“减少构建上下文”的角度看,如果构建阶段不需要它们,就可以排除。

基本匹配规则

.gitignore.dockerignore 的语法非常接近,常见规则如下。

忽略文件

debug.log

表示忽略名为 debug.log 的文件。

忽略目录

logs/

结尾带 / 时,表示忽略目录。

使用通配符

*.log
*.tmp

* 可以匹配任意字符。上面的配置会忽略所有 .log.tmp 后缀的文件。

指定根目录

/dist

开头带 / 时,表示从当前 ignore 文件所在目录的根位置开始匹配。

如果写成:

dist/

则可能匹配任意层级下名为 dist 的目录。

反向保留

*.log
!important.log

! 表示不要忽略。上面的规则表示忽略所有 .log 文件,但保留 important.log

不过这里有一个容易踩坑的地方:如果父目录已经被忽略,里面的文件通常不能仅靠 ! 重新加入,需要先把目录本身保留下来。

例如:

logs/*
!logs/important.log

这表示忽略 logs/ 目录下的大部分文件,但保留 logs/important.log

二者的关键区别

.gitignore.dockerignore 不应该简单复制粘贴,因为它们的使用场景不同。

文件作用对象主要目的
.gitignoreGit 工作区避免提交不需要进入版本库的文件
.dockerignoreDocker 构建上下文减少构建上下文,避免无关文件影响镜像构建

例如 dist/ 是否应该忽略,就要看项目构建方式:

  • 如果 dist/ 是 CI 或本地构建产物,通常应该写入 .gitignore
  • 如果 Dockerfile 会在镜像里重新执行构建,dist/ 通常也应该写入 .dockerignore
  • 如果 Dockerfile 只负责把已经构建好的 dist/ 拷贝进 Nginx 镜像,那 .dockerignore 就不能忽略 dist/

再例如 node_modules/

  • 对 Git 来说,通常应该忽略。
  • 对 Docker 来说,如果镜像里会执行 npm install,也应该忽略。
  • 但如果某些离线构建场景必须复制本地依赖,就不能盲目忽略。

已经被 Git 追踪的文件怎么办

.gitignore 只影响“尚未被 Git 追踪”的文件。如果一个文件已经被提交过,后来再加入 .gitignore,Git 仍然会继续追踪它的变化。

这时需要从 Git 索引中移除,但保留本地文件:

git rm --cached .env

如果是目录:

git rm -r --cached dist/

然后提交这次变更:

git add .gitignore
git commit -m "Update gitignore rules"

这样文件会从版本库中移除,但本地文件仍然存在。

Docker 构建上下文的坑

.dockerignore 最容易被忽视的问题是:构建上下文过大。

例如一个项目里有几百 MB 的 node_modules/,如果没有写入 .dockerignore,每次 docker build 都可能要把它发送给 Docker。即使 Dockerfile 没有 COPY node_modules,这个传输动作也已经发生了。

另一个问题是敏感文件。

假设 .env 里有生产数据库密码,如果它进入了构建上下文,就存在被 COPY . . 打进镜像的风险。即使后续某一层删除了这个文件,也不代表它没有出现在镜像历史层中。

因此 .dockerignore 中通常应该明确排除:

.env
.env.*
*.pem
*.key
id_rsa
id_rsa.pub

Node 项目的示例

.gitignore

node_modules/
dist/
coverage/
*.log
.env
.env.local
.DS_Store

.dockerignore

.git
node_modules
coverage
*.log
.env
.env.*
.DS_Store
README.md

配合 Dockerfile:

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

CMD ["npm", "start"]

这里忽略本地 node_modules 是合理的,因为镜像会通过 npm ci 安装依赖。

Java 项目的示例

.gitignore

target/
*.class
*.log
.idea/
.settings/
.classpath
.project

.dockerignore

.git
target
*.log
.idea
README.md

如果 Dockerfile 负责在镜像里执行 Maven 构建,那么 target/ 可以忽略。

如果 Dockerfile 是下面这种只复制 jar 包的形式:

FROM eclipse-temurin:17-jre

WORKDIR /app
COPY target/app.jar app.jar

CMD ["java", "-jar", "app.jar"]

那就不能在 .dockerignore 里忽略 target/,否则 COPY target/app.jar app.jar 会找不到文件。

排查 ignore 是否生效

Git 可以用 check-ignore 查看某个文件为什么被忽略:

git check-ignore -v .env

如果想看当前未追踪但没有被忽略的文件:

git status --short

Docker 侧可以通过观察构建输出中的上下文大小来判断 .dockerignore 是否明显生效:

docker build -t my-app .

如果看到发送的 build context 很大,就应该优先检查 .dockerignore

也可以临时写一个 Dockerfile 来验证某个文件是否还在构建上下文里:

FROM alpine
WORKDIR /app
COPY . .
RUN find . -maxdepth 2 -type f | sort

如果某个文件已经被 .dockerignore 排除,COPY . . 后就不会出现在列表里。

实践建议

.gitignore 的核心原则是:凡是可以由源码、配置或依赖管理工具重新生成的内容,都尽量不要提交。

.dockerignore 的核心原则是:凡是 Docker 构建不需要的内容,都尽量不要进入构建上下文。

两者可以有相似规则,但不要机械保持一致。每次写 ignore 文件时,都可以问两个问题:

  • 这个文件是否应该成为项目历史的一部分?
  • 这个文件是否必须参与镜像构建?

第一个问题决定 .gitignore,第二个问题决定 .dockerignore

总结

.gitignore 让 Git 仓库保持干净,.dockerignore 让 Docker 构建更快、更安全。

前者关注版本控制,后者关注构建上下文。它们语法接近,但职责不同。写好这两个文件以后,项目会少很多无意义的文件变更,镜像也更不容易混入本地缓存、日志和密钥。