Docker镜像

Docker 镜像是一个轻量级、可执行的独立软件包,它包含了运行某个软件所需的一切:代码、运行时环境、库、环境变量和配置文件。它是一切容器运行的基石。

1、核心概念

1.1 分层存储 (Union File System)

这是 Docker 镜像最核心的特性。

只读层 (Read-only Layers): 一个镜像由多个只读层叠加而成。每一层代表 Dockerfile 中的一条指令。例如:

bash 复制代码
FROM ubuntu:22.04 # 会创建一层。

RUN apt-get update && apt-get install -y nginx # 会创建新的一层。

COPY index.html /var/www/html/ # 又会创建新的一层。

容器层 (Container Layer, 即可写层) : 当你基于一个镜像启动容器时,Docker 会在所有只读层之上添加一个薄薄的可写层。所有对运行中容器的修改(如创建新文件、修改现有文件、安装新软件)都只发生在这个可写层中。

写时复制 (Copy-on-Write, CoW) : 这是实现分层存储的关键策略。如果一个文件存在于较低的只读层,而容器想要修改它,Docker 会先将这个文件复制到可写层,然后进行修改。容器看到的是可写层中的文件版本,而只读层中的原始文件保持不变。

1.2 镜像与容器的关系

  • 镜像 (Image): 是一个静态的模板,一个定义文件。类似于面向对象中的""。
  • 容器 (Container): 是镜像的一个运行实例。类似于"类"的"实例化对象"。

一个镜像可以启动任意多个容器。每个容器都有自己的可写层和唯一的容器 ID。

2、Dockerfile

镜像是通过 Dockerfile 构建的。Dockerfile 是一个文本文件,里面包含了一系列用于构建镜像的指令。

2.1 常用指令及其功能

指令 功能说明 示例
FROM 指定基础镜像, 必须是第一条指令。 FROM python:3.9-slim
LABEL 为镜像添加元数据(如维护者信息),推荐替代已废弃的 MAINTAINER。 LABEL maintainer="your-name@example.com"
RUN 在构建过程中执行命令,常用于安装软件包。 RUN apt-get update && apt-get install -y nginx
COPY 将本地文件或目录复制到镜像中(推荐用于本地文件)。 COPY ./app /app
ADD 与 COPY 类似,但支持从 URL 下载文件或自动解压压缩包到镜像。 ADD app.tar.gz /app/
WORKDIR 设置工作目录,后续的 RUN,CMD,ENTRYPOINT,COPY,ADD 等指令都会在此目录下执行。 WORKDIR /app
EXPOSE 声明容器运行时监听的网络端口,但实际映射需在 docker run 通过 -p 参数指定。 EXPOSE 80
ENV 设置环境变量,该变量在构建阶段和容器运行时均可用。 ENV APP_ENV production
CMD 指定容器启动时默认命令,可以被 docker run 后面参数覆盖。 CMD ["python", "app.py"]
ENTRYPOINT 配置容器启动时的入口命令,使其像一个可执行文件,CMD 的内容作为参数传递给它。 ENTRYPOINT ["/nginx","-g","daemon off;"]
USER 指定运行后续指令以及容器运行时的用户名或 UID。 USER appuser
ARG 定义构建时的变量,其值仅在构建阶段可用,不会保留到镜像中。 ARG VERSION=1.0

2.2 示例

构建一个简单的 Nginx 镜像

2.2.1 创建一个项目目录
bash 复制代码
mkdir my-nginx-app
cd my-nginx-app
2.2.2 创建一个自定义的 index.html
bash 复制代码
echo "<h1>Hello, Docker Image Tutorial!</h1>" > index.html
2.2.3 创建 Dockerfile
bash 复制代码
# 使用官方 Nginx 镜像作为基础层 (Base Layer)
FROM nginx:alpine

# 维护者信息(已弃用,可用 LABEL 替代)
LABEL maintainer="your.email@example.com"

# 删除默认的欢迎页面
RUN rm /usr/share/nginx/html/index.html

# 将宿主机的 index.html 文件复制到镜像的指定路径
# 这一步会创建一个新的镜像层
COPY index.html /usr/share/nginx/html/

# 暴露容器运行时监听的端口
EXPOSE 80

# 容器启动时执行的命令
# 因为基础镜像 nginx:alpine 已经定义了启动 nginx 的命令,所以这里可以省略
# CMD ["nginx", "-g", "daemon off;"]

2.3 构建镜像

使用 docker build命令根据 Dockerfile 构建镜像

bash 复制代码
docker build -t my-custom-nginx:1.0 .
# -t my-custom-nginx:1.0 指定镜像名为 my-custom-nginx,标签为 1.0
# . 表示 Dockerfile 和构建上下文(index.html文件)在当前目录

构建过程输出:

text 复制代码
Sending build context to Docker daemon  3.072kB
Step 1/5 : FROM nginx:alpine
alpine: Pulling from library/nginx
... (下载 nginx:alpine 的各个层)
Step 2/5 : LABEL maintainer="your.email@example.com"
 ---> Running in a1b2c3d4e5f6
 ---> 8a7b6c5d4e3f (新生成一层的ID)
Step 3/5 : RUN rm /usr/share/nginx/html/index.html
 ---> Running in f5e4d3c2b1a0
 ---> 1a2b3c4d5e6f (新生成一层的ID)
Step 4/5 : COPY index.html /usr/share/nginx/html/
 ---> a1b2c3d4e5f6 (新生成一层的ID)
Step 5/5 : EXPOSE 80
 ---> Running in 1234567890ab
 ---> abcdef123456 (最终镜像的ID)
Successfully built abcdef123456
Successfully tagged my-custom-nginx:1.0

2.4 ADD 与 COPY 的选择

尽管 ADD功能更多,但出于安全性和清晰度的考虑,建议优先使用 COPY 来复制本地文件,除非你确实需要 ADD的自动解压或从 URL 获取文件的功能。

2.5 RUN、CMD 和 ENTRYPOINT

  1. RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
  2. CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参数替换。
  3. ENTRYPOINT 配置容器启动时运行的命令。
2.5.1 Shell 和 Exec 格式

Shell 格式
<instruction> <command>

例如:

RUN apt-get install python3

CMD echo "Hello world"

ENTRYPOINT echo "Hello world"

当指令执行时,shell 格式底层会调用 /bin/sh -c <command>

Dockerfile片段:

ENV name LaoTie666

ENTRYPOINT echo "Hello, $name"

执行 docker run <image> 将输出:

Hello, LaoTie666

**注意:**环境变量 name 已经被值 LaoTie666 替换。

Exec 格式

<instruction> ["executable", "param1", "param2", ...]

例如:

RUN ["apt-get", "install", "python3"]

CMD ["/bin/echo", "Hello world"]

ENTRYPOINT ["/bin/echo", "Hello world"]

当指令执行时,会直接调用 ,不会被 shell 解析。

Dockerfile 片段:

ENV name LaoTie666

ENTRYPOINT ["/bin/echo", "Hello, $name"]

输出:

Hello, $name

**注意:**环境变量"name"没有被替换。

如果希望使用环境变量,照如下修改

ENV name LaoTie666

ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]

输出:
Hello, LaoTie666

总结:

CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可以。

2.5.2 RUN

RUN 指令通常用于安装应用和软件包。

RUN 在当前镜像的顶部执行命令,并通过创建新的镜像层。Dockerfile 中常常包含多个 RUN 指令。

两种格式

Shell 格式:RUN

Exec 格式:RUN ["executable", "param1", "param2"]

例子:

bash 复制代码
RUN apt-get update && apt-get install -y \  

 bzr \

 cvs \

 git \

 mercurial \

 subversion

注意:apt-get update 和 apt-get install 被放在一个 RUN 指令中执行,这样能够保证每次安装的是最新的包。如果 apt-get install 在单独的 RUN 中执行,则会使用 apt-get update 创建的镜像层,而这一层可能是很久以前缓存的。

2.5.3 CMD

CMD 指令允许用户指定容器的默认执行的命令。

此命令会在容器启动且 docker run 没有指定其他命令时运行。

如果 docker run 指定了其他命令,CMD 指定的默认命令将被忽略

如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 有效。

三种格式:

  • Exec 格式:CMD ["executable","param1","param2"],这是 CMD 的推荐格式。
  • CMD ["param1","param2"] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 Exec 格式。
  • Shell 格式:CMD command param1 param2

第二种格式 CMD ["param1","param2"] 要与 Exec 格式 的 ENTRYPOINT 指令配合使用,其用途是为 ENTRYPOINT 设置默认的参数

Dockerfile片段

CMD echo "Hello world"

运行容器 docker run -it [image] 将输出:

Hello world

但当后面加上一个命令,比如 docker run -it [image] /bin/bash,CMD 会被忽略掉,命令 bash 将被执行:

root@10a32dc7d3d3:/#

2.5.4 ENTRYPOINT

ENTRYPOINT 指令可让容器以应用程序或者服务的形式运行。

ENTRYPOINT 看上去与 CMD 很像,它们都可以指定要执行的命令及其参数。不同的地方在于 ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。

两种格式

  • Exec 格式:ENTRYPOINT ["executable", "param1", "param2"] 这是 ENTRYPOINT 的推荐格式。
  • Shell 格式:ENTRYPOINT command param1 param2

在为 ENTRYPOINT 选择格式时必须小心,因为这两种格式的效果差别很大。

Exec 格式

ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。

ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉

Dockerfile 片段

bash 复制代码
ENTRYPOINT ["/bin/echo", "Hello"]  

CMD ["world"]

当容器通过 docker run -it [image] 启动时,输出为:

Hello world

而如果通过 docker run -it [image] laotie666 启动,则输出为:

Hello laotie666

Shell 格式

ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。

3、镜像管理常用命令

命令 说明 示例
docker imagesdocker image ls 列出本地所有镜像 docker images
docker pull <image>:<tag> 从仓库拉取镜像 docker pull ubuntu:22.04
docker push <image>:<tag> 将镜像推送到仓库 docker push my-registry.com/my-app:1.0
docker rmi <image>docker image rm <image> 删除本地镜像 docker rmi my-custom-nginx:1.0
docker tag <source> <target> 给镜像打一个新标签 docker tag my-app:latest my-app:v1.0
docker history <image> 查看镜像的构建历史和各层信息 docker history my-custom-nginx:1.0
docker inspect <image> 获取镜像的详细信息(元数据) docker inspect nginx:alpine
docker save -o <file.tar> <image> 将镜像保存为 tar 归档文件 docker save -o my-app.tar my-app:latest
docker load -i <file.tar> 从 tar 归档文件加载镜像 docker load -i my-app.tar

4、Docker镜像优化

4.1 使用合适的基础镜像

优先选择 alpine 等轻量级镜像

bash 复制代码
# 好的实践
FROM node:16-alpine  # 约100MB

# 不推荐(过大)
FROM node:16         # 约900MB

4.2 合理组织指令顺序

将频繁变动的文件放在后面,利用缓存

bash 复制代码
# 好的实践
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./  # 不常变动,先复制以利用缓存
RUN npm install
COPY . .               # 经常变动,放在后面

4.3 清理不必要的文件

在同一层清理构建依赖

bash 复制代码
# 好的实践
RUN apt-get update && \
    apt-get install -y gcc && \
    # 编译操作... && \
    apt-get remove -y gcc && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

4.4 合并 RUN 指令

将多个 RUN指令通过 &&连接成一个,减少镜像的层数,从而减小体积。

bash 复制代码
# 不推荐
RUN apt-get update
RUN apt-get install -y package

# 推荐
RUN apt-get update && apt-get install -y package

4.5 多阶段构建

用于需要编译的应用程序(如 Go, Java)。它允许你在一个 Dockerfile 中使用多个 FROM 语句,并且可以选择性地将前一阶段的构建产物复制到后一阶段,而丢弃不需要的庞大编译环境和中间文件。

bash 复制代码
# 第一阶段:构建阶段
FROM golang:1.19 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .

# 第二阶段:运行阶段
FROM alpine:latest
WORKDIR /root/
# 从 builder 阶段只复制编译好的二进制文件
COPY --from=builder /app/myapp .
CMD ["./myapp"]

4.6 使用 .dockerignore 文件

类似于 .gitignore,它可以排除构建上下文中不需要的文件,避免它们被发送到 Docker 守护进程,加速构建过程,并避免将敏感文件意外打入镜像

bash 复制代码
# 忽略版本控制文件
.git
.gitignore

# 忽略依赖目录
node_modules
vendor

# 忽略环境配置文件
.env
.env.local

# 忽略日志和临时文件
logs/
tmp/

# 忽略IDE配置
.idea/
.vscode/

# 忽略构建产物
dist/
build/

5、实战示例:构建一个 Python Flask 应用镜像

5.1 项目结构

复制代码
```
my-flask-app/
├── app.py
├── requirements.txt
└── Dockerfile
```

5.2 Dockerfile 内容

复制代码
```dockerfile
# 使用官方 Python 精简版基础镜像
FROM python:3.9-slim

# 设置元数据和维护者信息(推荐方式)
LABEL maintainer="your-name@example.com"

# 设置工作目录
WORKDIR /app

# 先将依赖文件复制到工作目录
COPY requirements.txt .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 声明容器暴露的端口
EXPOSE 5000

# 定义环境变量
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0

# 容器启动时运行命令
CMD ["flask", "run"]
```

5.3 构建与运行

复制代码
```bash
# 构建镜像
docker build -t my-flask-app:latest .

# 运行容器
docker run -d -p 5000:5000 --name my-flask-container my-flask-app
相关推荐
Dontla5 小时前
Dockerfile解析器指令(Parser Directive)指定语法版本,如:# syntax=docker/dockerfile:1
java·docker·eureka
楠神说软件测试7 小时前
Docker命令(全)
docker
LuiChun7 小时前
docker desktop更新到【4.45.0 】后,旧容器镜像都丢失了
docker·容器·dubbo
子兮曰8 小时前
🔥C盘告急!WSL磁盘暴增?三招秒清20GB+空间
前端·windows·docker
ZLRRLZ8 小时前
【Docker】Docker安装
运维·docker·容器
Dxy123931021610 小时前
Docker常用命令详解
docker·容器·eureka
IvanCodes11 小时前
六、Docker 核心技术:Dockerfile 指令详解
java·数据库·docker
阿小木的愤怒11 小时前
详细解读Docker
docker·容器·容器化·虚拟化技术
wp909011 小时前
Docker命令大全
docker·云原生·eureka