【Dockerfile 从入门到实战:核心概念、基础命令、前后端项目构建实例与镜像优化全解析】

提示:本文原创作品,良心制作,干货为主,简洁清晰,一看就会

文章目录

  • 前言
  • 一、基础概念
    • [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 容器的分层文件系统结构,从上到下分为三个核心部分

  1. 可读写层(rw)

    作用:这是容器运行时唯一可写入的层。所有在容器内产生的新文件、修改的文件,都存储在这一层。当容器被删除时,这一层的数据也会随之丢失,这也是为什么需要数据卷(Volume)来实现持久化的原因

  2. Init层(ro+wh)

    作用:这是一个特殊的只读层,由 Docker 自动生成。它主要用于存放 /etc/hosts/etc/resolv.conf 等容器运行时必需的配置文件。它被标记为 ro+wh(只读+写时复制),保证了基础镜像的完整性

  3. 只读层(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用户)

核心补充说明

  1. RUN 分两种写法:RUN 命令(shell格式)、RUN ["命令", "参数"](exec格式,推荐,避免shell解析问题)
  2. CMDENTRYPOINT 区别:CMD是"默认命令"可替换,ENTRYPOINT是"固定入口",比如 ENTRYPOINT ["ping"] + CMD ["baidu.com"],运行容器时docker run 镜像 google.com会把CMD替换成google.com
  3. 指令执行会生成镜像层,尽量合并RUN命令(如用&&连接),减少镜像层数,提升效率
  4. USER 注意事项:切换的用户需提前在镜像中创建(可通过RUN useradd xxx创建),否则会报错
  5. LABEL 可多组合并写:LABEL author="test@xxx.com" description="nginx镜像",便于docker inspect查看镜像信息

总结

  1. FROM 是基础,RUN 做构建,COPY/ADD 传文件,WORKDIR 定路径;
  2. CMD/ENTRYPOINT 管启动,EXPOSE/VOLUME 处理网络和数据,LABEL 加元数据,USER 控权限
  3. 指令按执行顺序编写,层越少、逻辑越简洁,镜像越轻量且安全

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重新构建

总结

  1. 构建镜像核心命令是docker build -t 镜像名:版本 上下文路径-t.是最核心的两个部分
  2. -f用于指定自定义Dockerfile,--no-cache用于强制刷新构建步骤
  3. 构建上下文路径决定了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-slimnode: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 cleanpip 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 优化的核心要点可归纳为:

  1. 减体积:用轻量基础镜像、多阶段构建、清理缓存/依赖、合并镜像层;
  2. 提速度 :合理排序指令利用缓存、.dockerignore 减小构建上下文、使用 BuildKit;
  3. 保安全:非root用户运行、清理敏感信息、仅保留必要依赖。

通过以上优化,镜像体积通常能减少 50% 以上,构建速度提升 30%~80%,同时镜像的安全性和可维护性也会显著提高


注:

文中若有疏漏,欢迎大家指正赐教。

本文为100%原创,转载请务必标注原创作者,尊重劳动成果。

求赞、求关注、求评论!你的支持是我更新的最大动力,评论区等你~

相关推荐
returnthem1 天前
DockerFile命令
dockerfile
予枫的编程笔记1 个月前
【Docker进阶篇】从入门到精通:Java应用Docker打包,最佳实践与多阶段构建详解
java·docker·容器化·dockerfile·多阶段构建·docker最佳实践·java镜像优化
予枫的编程笔记1 个月前
【Docker基础篇】从0到1写Dockerfile:FROM/COPY/CMD/ENTRYPOINT指令详解+Hello World实战
人工智能·docker·云计算·dockerfile·容器技术·docker入门·docker实战
纯洁的小魔鬼2 个月前
Dockerfile 指令
docker·镜像·dockerfile
眠りたいです2 个月前
Docker核心技术和实现原理第一部分-Docker镜像制作
运维·docker·容器·集群·镜像·dockerfile
key2world3 个月前
Dockerfile 制作镜像和推送
docker·dockerfile·docker push
XHW___0013 个月前
Dockerfile生成镜像
dockerfile
Mr.Ja4 个月前
【Docker 从入门到实战】——解决跨环境部署痛点的完整指南
运维·docker·容器·dockerfile·dockerimage
Garfield20054 个月前
Kubeflow 运行容器时 ENTRYPOINT 被覆盖导致环境变量未生效问题分析与解决
k8s·dockerfile·kubeflow·entrypoint