提示:本文原创作品,良心制作,干货为主,简洁清晰,一看就会
文章目录
- 前言
- 一、基础概念
-
- [1.1 dockerfile是什么](#1.1 dockerfile是什么)
- [1.2 docker分层文件系统](#1.2 docker分层文件系统)
- [1.3 docker镜像原理](#1.3 docker镜像原理)
- [1.4 容器一致性](#1.4 容器一致性)
- 二、Dockerfile基础命令
-
- [2.1 dockerfile 简单指令](#2.1 dockerfile 简单指令)
- [2.2 docker构建镜像语法](#2.2 docker构建镜像语法)
- 三、Dockerfile实例
-
- [3.1 单阶段构建前端项目](#3.1 单阶段构建前端项目)
- [3.2 单阶段构建后端项目](#3.2 单阶段构建后端项目)
- [3.3 多阶段构建前端项目](#3.3 多阶段构建前端项目)
- [3.4 多阶段构建后端项目](#3.4 多阶段构建后端项目)
- 四、Dockerfile优化
-
- [4.1 使用更轻量的基础镜像](#4.1 使用更轻量的基础镜像)
- [4.2 合并 RUN 指令,清理缓存文件](#4.2 合并 RUN 指令,清理缓存文件)
- [4.3 使用 .dockerignore 排除无关文件](#4.3 使用 .dockerignore 排除无关文件)
- [4.4 利用多阶段构建](#4.4 利用多阶段构建)
- [4.5 合理使用 COPY/ADD,优先用 COPY](#4.5 合理使用 COPY/ADD,优先用 COPY)
- [4.6 减少镜像层数,合理排序指令](#4.6 减少镜像层数,合理排序指令)
- [4.7 清理不必要的依赖和文件](#4.7 清理不必要的依赖和文件)
- [4.8 使用非root用户运行容器](#4.8 使用非root用户运行容器)
- [4.9 总结](#4.9 总结)
前言
随着容器化技术的普及,Docker 已成为现代应用交付的核心基础设施。而 Dockerfile 作为构建容器镜像的 "代码化蓝图",将环境配置、依赖安装、应用部署等流程固化为可复用的文本,彻底解决了 "在我机器上能跑" 的环境一致性难题。本文将从基础概念到最佳实践,系统讲解 Dockerfile 的编写逻辑,帮助你高效构建安全、轻量的容器镜像,实现从代码到容器的无缝交付
一、基础概念
1.1 dockerfile是什么
Dockerfile 是一个由特定指令构成的纯文本文件,是构建 Docker 镜像的标准化 "图纸"。它通过 FROM、RUN、COPY、CMD 等固定指令,将操作系统、依赖、配置、代码等环境信息逐一定义,让 Docker 按步骤自动构建出可直接运行的镜像,实现环境可重复、可移植、可版本管理
1.2 docker分层文件系统

这张图展示了 docker 容器的分层文件系统结构,从上到下分为三个核心部分
-
可读写层(rw)
作用:这是容器运行时唯一可写入的层。所有在容器内产生的新文件、修改的文件,都存储在这一层。当容器被删除时,这一层的数据也会随之丢失,这也是为什么需要数据卷(Volume)来实现持久化的原因
-
Init层(ro+wh)
作用:这是一个特殊的只读层,由 Docker 自动生成。它主要用于存放
/etc/hosts、/etc/resolv.conf等容器运行时必需的配置文件。它被标记为ro+wh(只读+写时复制),保证了基础镜像的完整性 -
只读层(ro+wh)
作用:这些层来自基础镜像
ubuntu:latest,是完全只读的。它们包含了操作系统的核心文件、库和应用。当容器需要修改这些层中的文件时,AUFS 会触发"写时复制"(Copy-on-Write)机制,将该文件复制到最上层的可读写层中进行修改,从而保证了基础镜像的不可变性
1.3 docker镜像原理
Dockerfile 采用分层构建机制,每一条指令对应镜像的一个只读层。Docker 读取文件后,按顺序逐层执行、缓存复用,最后在顶层生成可读写层形成容器。构建过程基于 UnionFS 联合文件系统,实现层与层之间的叠加、复用与共享,保证镜像轻量化、构建高效化

1.4 容器一致性
容器一致性,就是同一个镜像在任何机器上运行,环境、行为、结果完全一样
容器一致性的核心来源于镜像分层 + 隔离机制
Dockerfile 将系统、依赖、配置、代码全部固化为不可变的镜像层,保证构建内容完全一致。容器运行时,通过 Linux Namespace 和 Cgroups 实现资源隔离,使每个容器拥有独立的进程、网络、文件系统,不受宿主机环境影响。因此无论在任何环境运行,容器的行为都高度统一
二、Dockerfile基础命令
2.1 dockerfile 简单指令
Dockerfile 通过一系列标准化指令定义镜像构建流程,以下是最常用的基础指令,每个指令配核心作用和极简示例:
| 指令 | 核心作用 | 简单示例 |
|---|---|---|
FROM |
指定基础镜像(必选,镜像的"底层操作系统/环境") | FROM ubuntu:22.04(基于Ubuntu) |
RUN |
构建镜像时执行命令(安装依赖、配置环境,如apt/yum安装软件) | RUN apt update && apt install nginx -y |
COPY |
将宿主机文件/目录复制到镜像内(仅本地文件,不能跨机器) | COPY ./app.jar /usr/local/ |
ADD |
增强版COPY,支持解压压缩包、下载远程文件 | ADD https://xxx.tar.gz /tmp/ |
WORKDIR |
设置容器运行/指令执行的工作目录(后续指令默认在此目录操作) | WORKDIR /app(后续指令在/app下) |
ENV |
设置环境变量(容器内可读取,如配置端口、路径) | ENV PORT=8080 |
EXPOSE |
声明容器要暴露的端口(仅声明,不实际映射,提示作用) | EXPOSE 8080 |
CMD |
容器启动时执行的命令(可被docker run参数覆盖,一个Dockerfile仅最后一个生效) |
CMD ["java", "-jar", "app.jar"] |
ENTRYPOINT |
容器启动的固定入口命令(不可被覆盖,需传参时配合CMD) | ENTRYPOINT ["nginx", "-g", "daemon off;"] |
VOLUME |
声明数据卷(指定容器内持久化目录,避免数据丢失) | VOLUME ["/data"] |
LABEL |
为镜像添加元数据标签(键值对形式,用于分类、说明镜像信息,如作者、版本) | LABEL author="test@xxx.com" version="1.0" |
USER |
指定后续指令执行的用户/用户组(默认root,切换非root用户提升安全性) | USER nginx(切换为nginx用户) |
核心补充说明
RUN分两种写法:RUN 命令(shell格式)、RUN ["命令", "参数"](exec格式,推荐,避免shell解析问题)CMD和ENTRYPOINT区别:CMD是"默认命令"可替换,ENTRYPOINT是"固定入口",比如ENTRYPOINT ["ping"] + CMD ["baidu.com"],运行容器时docker run 镜像 google.com会把CMD替换成google.com- 指令执行会生成镜像层,尽量合并
RUN命令(如用&&连接),减少镜像层数,提升效率 USER注意事项:切换的用户需提前在镜像中创建(可通过RUN useradd xxx创建),否则会报错LABEL可多组合并写:LABEL author="test@xxx.com" description="nginx镜像",便于docker inspect查看镜像信息
总结
FROM是基础,RUN做构建,COPY/ADD传文件,WORKDIR定路径;CMD/ENTRYPOINT管启动,EXPOSE/VOLUME处理网络和数据,LABEL加元数据,USER控权限- 指令按执行顺序编写,层越少、逻辑越简洁,镜像越轻量且安全
2.2 docker构建镜像语法
bash
# 基础语法格式
docker build [可选参数] <构建上下文路径/URL>
核心可选参数
| 参数 | 作用 | 示例 |
|---|---|---|
-t/--tag |
给镜像打标签(格式:名称:版本,版本默认latest) |
-t react-demo:v1 |
-f/--file |
指定非默认名称/路径的Dockerfile(默认找上下文里的Dockerfile) | -f ./docker/Dockerfile.prod |
--no-cache |
不使用构建缓存(强制重新执行所有RUN指令,解决缓存导致的依赖更新问题) | --no-cache |
bash
# 在Dockerfile所在目录执行,给镜像打标签react-demo:v1
docker build -t react-demo:v1 .
.代表构建上下文是当前目录,Docker会读取当前目录的`Dockerfile`,构建后镜像标签为`react-demo:v1`
bash
# Dockerfile不在当前目录/名称不是Dockerfile时使用
docker build -t react-demo:prod -f ./docker/Dockerfile.prod .
-f 指定使用./docker/目录下的Dockerfile.prod文件,镜像标签为react-demo:prod
默认只认Dockerfile(首字母大写),非默认名称必须用-f指定
bash
# 不使用缓存,重新安装所有依赖(比如npm包更新时)
docker build -t react-demo:v2 --no-cache .
如果修改了RUN npm install但没生效,加--no-cache重新构建
总结
- 构建镜像核心命令是
docker build -t 镜像名:版本 上下文路径,-t和.是最核心的两个部分 -f用于指定自定义Dockerfile,--no-cache用于强制刷新构建步骤- 构建上下文路径决定了
COPY/ADD能读取的文件范围,优先用.(当前目录)
三、Dockerfile实例
3.1 单阶段构建前端项目
构建 React 前端项目的 Dockerfile
bash
root@docker1:~# docker pull node:16.20.0 #下载基础镜像
root@docker1:~# mkdir -p /data/docker-images/web-react #创建存放镜像的目录
bash
root@docker1:~# vim /data/docker-images/web-react/Dockerfile
# 基础镜像
FROM node:16.20.0
# 全局配置npm淘宝源(优先级最高,避免被项目配置覆盖)
RUN npm config set registry https://registry.npmmirror.com && npm config set fetch-retry-maxtimeout 60000 && npm config set cache /tmp/npm-cache
# 全局安装最新create-react-app(避免旧版本问题)
RUN npm install -g create-react-app@latest
# 创建全新React项目(无旧依赖、无配置污染)
RUN create-react-app my-react-app --use-npm
# 切换到项目目录
WORKDIR /my-react-app
# 强制重新安装依赖(确保用淘宝源)
RUN npm install --force
# 声明端口
EXPOSE 3000
# 启动项目
CMD ["npm", "start"]
bash
root@docker1:~# cd /data/docker-images/web-react
root@docker1:/data/docker-images/web-react# docker build -t react:v1 . #构建镜像
root@docker1:/data/docker-images/web-react# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
react v1 56480e51c2f3 20 seconds ago 1.36GB
node 16.20.0 c56ec223ce19 2 years ago 909MB
root@docker1:/data/docker-images/web-react# docker run -itd --name react-app -p 3000:3000 react:v1
浏览器访问ip:3000

3.2 单阶段构建后端项目
基于阿里云的 JRE 镜像,构建一个包含 Spring Cloud Eureka 服务的 Docker 镜像,并运行这个镜像容器,最终启动一个 Eureka 注册中心服务
bash
# 拉取镜像
root@docker1:~# docker pull registry.cn-hangzhou.aliyuncs.com/hujiaming/jre:8u211-data
# 拉取项目
root@docker1:~# git clone https://gitee.com/luqing000000/java-test.git
root@docker1:~# ls java-test/
Dockerfile spring-cloud-eureka-0.0.1-SNAPSHOT.jar
root@docker1:~# cat java-test/Dockerfile
# 基础镜像可以按需修改,可以更改为公司自有镜像
FROM registry.cn-hangzhou.aliyuncs.com/hujiaming/jre:8u211-data
# 切换工作目录
WORKDIR /app
# jar 包名称改成实际的名称,本示例为 spring-cloud-eureka-0.0.1-SNAPSHOT.jar
COPY spring-cloud-eureka-0.0.1-SNAPSHOT.jar /app
# 端口
EXPOSE 8761
# 启动 Jar 包
CMD ["java","-jar","spring-cloud-eureka-0.0.1-SNAPSHOT.jar"]root@docker1:~#
root@docker1:~# cd java-test/
root@docker1:~/java-test# docker build -t java-test:v1 .
root@docker1:~/java-test# docker run -itd --name java-jar-test -p 8761:8761 java-test:v1
root@docker1:~/java-test# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
37e571b42cd7 java-test:v1 "java -jar spring-cl..." 12 seconds ago Up 8 seconds 0.0.0.0:8761->8761/tcp, :::8761->8761/tcp java-jar-test
浏览器访问ip:8761

3.3 多阶段构建前端项目
bash
# Step 1:拉取代码
root@docker1:~# mkdir /data/docker-images/webserver-vue
root@docker1:~# cd /data/docker-images/webserver-vue
root@docker1:~/webserver-vue# git clone https://gitee.com/luqing000000/docker-test.git
# Step 2:编写Dockecrfile
root@docker1:~/webserver-vue# cat Dockerfile
FROM registry.cn-hangzhou.aliyuncs.com/hujiaming/node:16.15.0 AS builder
COPY ./ /app
WORKDIR /app
RUN npm install --registry=http://registry.npmmirror.com && npm run build
FROM registry.cn-hangzhou.aliyuncs.com/hujiaming/nginx:v1.24.0
RUN mkdir /app
COPY --from=builder /app/dist /app
COPY nginx.conf /etc/nginx/nginx.conf
# Step 3:编写nginx配置文件
root@docker1:~/webserver-vue# cat nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /app;
index index.html;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
# Step 4:构建镜像
root@docker1:~/webserver-vue# docker build -t webserver:v1 .
root@docker1:~/webserver-vue# docker run -itd --name webserver-vue1 -p 82:80 webserver:v1
浏览器访问ip:82

3.4 多阶段构建后端项目
bash
root@docker1:~# git clone https://gitee.com/luqing000000/spring-boot-project.git
root@docker1:~# cd spring-boot-project/
bash
root@docker1:~/spring-boot-project# cat Dockerfile
# ===================== 第一阶段:构建阶段(builder) =====================
# 基于阿里云镜像源的 maven 3.5.3 镜像作为构建基础镜像(包含Maven编译环境)
# AS builder 给这个阶段起别名,方便后续阶段引用
FROM registry.cn-hangzhou.aliyuncs.com/hujiaming/maven:3.5.3 AS builder
# 设置容器内的工作目录为 /app,后续指令都基于此目录执行
WORKDIR /app
# 将宿主机当前目录(Dockerfile所在目录)的所有文件复制到容器的 /app 目录下
COPY ./ .
# 执行Maven命令:清理旧构建产物 → 编译打包 → 跳过单元测试(加快构建速度)
# 最终会在 /app/target 目录生成可执行的jar包
RUN mvn clean install -DskipTests
# ===================== 第二阶段:运行阶段 =====================
# 基于阿里云镜像源的轻量JRE 8u211镜像(仅包含Java运行环境,无编译环境,减小体积)
# 选择jre而非jdk,因为运行jar包仅需运行时环境,进一步精简镜像
FROM registry.cn-hangzhou.aliyuncs.com/hujiaming/jre:8u211-data
# 从第一阶段(builder)的 /app/target 目录复制打包好的jar包到当前阶段的工作目录(默认/)
# 仅复制最终产物,丢弃构建阶段的Maven环境、源码等,大幅减小最终镜像体积
COPY --from=builder /app/target/spring-cloud-eureka-0.0.1-SNAPSHOT.jar .
# 声明容器暴露的端口为8761(Eureka默认端口),仅为声明作用,实际映射需docker run -p指定
EXPOSE 8761
# 容器启动时执行的命令:通过java -jar运行Eureka服务的jar包
CMD ["java","-jar","spring-cloud-eureka-0.0.1-SNAPSHOT.jar"]
bash
root@docker1:~/spring-boot-project# docker build -t spring-boot:v1 .
root@docker1:~/spring-boot-project# docker run -itd --name spring-boot-test -p 8761:8761 spring-boot:v1
浏览器访问ip:8761

四、Dockerfile优化
Dockerfile 的优化核心目标是减小镜像体积、加快构建速度、提升镜像安全性,同时让构建过程更高效、可维护
4.1 使用更轻量的基础镜像
基础镜像是镜像体积的"大头",优先选择官方精简版镜像:
- 避免使用
ubuntu/centos等完整发行版,改用alpine(基于 musl libc,体积通常 < 10MB) - 对于语言类镜像,选择官方的 slim/alpine 变体(如
python:3.12-slim、node:20-alpine)
反例:
dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3
优化后:
dockerfile
FROM python:3.12-alpine # 体积从几百MB降至几十MB
4.2 合并 RUN 指令,清理缓存文件
Docker 每层镜像都会保留修改,过多 RUN 指令会增加层数和体积
反例(多层且未清理缓存):
dockerfile
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y nginx
优化后(合并为一层,即时清理):
dockerfile
FROM ubuntu:22.04
RUN apt-get update && \
apt-get install -y nginx
4.3 使用 .dockerignore 排除无关文件
和 .gitignore 类似,.dockerignore 可以排除不需要加入构建上下文的文件(如 node_modules、.git、日志、测试文件),减少构建上下文体积,加快 COPY/ADD 速度
示例 .dockerignore:
# 排除Node.js依赖
node_modules/
# 排除git相关
.git/
.gitignore
# 排除日志和临时文件
*.log
tmp/
# 排除测试文件
tests/
4.4 利用多阶段构建
多阶段构建可以将"构建环境"和"运行环境"分离,只保留运行所需的文件,大幅减小最终镜像体积(尤其适合编译型语言:Go、Java、C++)
示例
本文的3.3、3.4小节
4.5 合理使用 COPY/ADD,优先用 COPY
COPY仅用于文件/目录复制,功能简单且明确,优先使用ADD会自动解压压缩包、支持URL下载(不推荐),仅在需要解压时使用
反例:
dockerfile
ADD https://example.com/package.tar.gz /app # 不推荐用ADD下载文件
COPY . . # 复制所有文件,包含无关内容
优化后:
dockerfile
# 下载文件用RUN + wget/curl,更可控
RUN wget https://example.com/package.tar.gz -O /app/package.tar.gz && \
tar -xzf /app/package.tar.gz -C /app && \
rm /app/package.tar.gz
COPY main.go go.mod go.sum /app/ # 仅复制编译必需的文件
4.6 减少镜像层数,合理排序指令
Dockerfile 中除 FROM 外,每个指令(RUN/COPY/ADD/ENV 等)都会创建一层,层数越多镜像越臃肿:
- 合并无关的
RUN指令(见第2点) - 把"变化频率低的指令"(如安装依赖)放在前面,利用 Docker 构建缓存
- 变化频率高的指令(如复制业务代码)放在后面,避免缓存失效
示例(合理排序):
dockerfile
FROM python:3.12-alpine
WORKDIR /app
# 先复制依赖文件(变化少),利用缓存
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt # --no-cache-dir 避免缓存pip包
# 再复制业务代码(变化多),仅代码修改时重新构建这层
COPY . .
CMD ["python", "app.py"]
4.7 清理不必要的依赖和文件
- 安装依赖时仅安装"运行时依赖",而非"构建时依赖"(如编译用的
gcc/make,安装后删除) - 移除临时文件、日志、包管理缓存(如
apt-get clean、pip cache purge)
示例(清理构建依赖):
dockerfile
FROM alpine:3.19
RUN apk add --no-cache --virtual .build-deps gcc musl-dev && \
# 安装运行时依赖 + 编译依赖
apk add --no-cache python3 && \
# 编译安装某个包
pip install --no-cache-dir some-package && \
# 删除构建依赖,保留运行时依赖
apk del .build-deps
4.8 使用非root用户运行容器
默认以 root 用户运行容器有安全风险,优化为创建普通用户:
dockerfile
FROM python:3.12-alpine
WORKDIR /app
# 创建普通用户并授权
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --chown=appuser:appgroup . . # 复制文件时指定所属用户
# 切换到普通用户
USER appuser
CMD ["python", "app.py"]
4.9 总结
Dockerfile 优化的核心要点可归纳为:
- 减体积:用轻量基础镜像、多阶段构建、清理缓存/依赖、合并镜像层;
- 提速度 :合理排序指令利用缓存、
.dockerignore减小构建上下文、使用 BuildKit; - 保安全:非root用户运行、清理敏感信息、仅保留必要依赖。
通过以上优化,镜像体积通常能减少 50% 以上,构建速度提升 30%~80%,同时镜像的安全性和可维护性也会显著提高
注:
文中若有疏漏,欢迎大家指正赐教。
本文为100%原创,转载请务必标注原创作者,尊重劳动成果。
求赞、求关注、求评论!你的支持是我更新的最大动力,评论区等你~