Docker镜像制作实战

C++ 镜像制作实战

实战目的

  • 编写 Dockerfile 完成一个简单 C++ 镜像
  • 了解 Dockerfile 构建流程及优化思路

实战步骤

1. 创建工作目录

复制代码

mkdir -p /data/myworkdir/dockerfile/cpp

cd /data/myworkdir/dockerfile/cpp


2. 创建 C++ 源代码文件 demo.c

复制代码

#include <stdio.h>

int main() {

printf("hello docker!\n");

return 0;

}


3. 创建 Dockerfile

复制代码

指定基础镜像

FROM centos:7

设置版本号

ENV VERSION 1.0

替换为国内源(提高下载速度)

RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \

-e 's|^#baseurl=http://mirror.centos.org/centos\|baseurl=https://mirrors.ustc.edu.cn/centos\|g' \

-i.bak /etc/yum.repos.d/CentOS-Base.repo

设置工作目录

WORKDIR /src

拷贝源文件

COPY demo.c .

安装 gcc 编译器

RUN yum makecache && yum install gcc -y

编译源文件,并清理不必要文件

RUN gcc demo.c -o demo && \

rm -f demo.c && \

yum remove -y gcc

镜像启动时运行可执行文件

CMD ["/src/demo"]

优化点说明:

  • 使用 WORKDIR 避免每次写完整路径
  • 编译后删除源文件和 gcc,减小镜像体积
  • 替换国内源,加快构建速度

4. 构建镜像

复制代码

docker build -t cpp:v0.1 .

  • -t cpp:v0.1 为镜像命名和打标签
  • . 指当前目录为上下文

5. 运行镜像验证

复制代码

docker run --name cpp1 --rm cpp:v0.1

输出:

复制代码

hello docker!

  • --rm:容器停止后自动删除
  • --name cpp1:指定容器名称

CMD 与 ENTRYPOINT 深度实战笔记(完整版)


一、核心本质(先理解底层)

Docker 容器启动本质执行的是:

复制代码

最终执行命令 = ENTRYPOINT + CMD

👉 但前提是:

  • ENTRYPOINT 存在 → CMD 作为参数
  • ENTRYPOINT 不存在 → CMD 作为完整命令

二、四种组合情况(必须搞清楚 ⭐)

情况1:只有 CMD

复制代码

CMD ["echo", "hello"]

👉 执行:

复制代码

docker run image

👉 实际:

复制代码

echo hello

👉 覆盖:

复制代码

docker run image echo world

👉 实际:

复制代码

echo world ✅ CMD 被完全替换


情况2:只有 ENTRYPOINT

复制代码

ENTRYPOINT ["echo", "hello"]

👉 执行:

复制代码

docker run image

👉 实际:

复制代码

echo hello

👉 覆盖:

复制代码

docker run image echo world

👉 实际:

复制代码

echo hello ❌ 不会变

👉 强制覆盖:

复制代码

docker run --entrypoint echo image world

👉 实际:

复制代码

echo world


情况3:ENTRYPOINT + CMD(最重要 ⭐⭐⭐)

复制代码

ENTRYPOINT ["echo"]

CMD ["hello"]

👉 执行:

复制代码

docker run image

👉 实际:

复制代码

echo hello


👉 覆盖 CMD:

复制代码

docker run image world

👉 实际:

复制代码

echo world ✅ 只替换参数


👉 本质:

复制代码

ENTRYPOINT = 程序

CMD = 参数


情况4:Shell 模式(隐藏坑)

复制代码

CMD echo hello

👉 实际执行:

复制代码

/bin/sh -c "echo hello"

👉 等价于:

复制代码

CMD ["/bin/sh", "-c", "echo hello"]


三、Shell vs Exec 深入理解(重点⭐)


1️⃣ Shell 模式

复制代码

CMD ping localhost

👉 实际运行:

复制代码

/bin/sh -c ping localhost

👉 进程结构:

复制代码

PID 1 -> /bin/sh

PID 2 -> ping


❌ 问题(关键点)

1. 信号丢失

复制代码

docker stop container

👉 发送 SIGTERM 给 PID 1(/bin/sh)

👉 shell 不转发给 ping

👉 结果:

  • ping 不退出 ❌
  • 容器被强制 kill ❌

2. 优雅退出失败

  • 无法触发应用的 cleanup
  • 容器管理不规范

2️⃣ Exec 模式(推荐)

复制代码

CMD ["ping", "localhost"]

👉 进程结构:

复制代码

PID 1 -> ping

👉 优点:

  • 信号直接给应用
  • 可以优雅退出
  • Docker 官方推荐

四、ENTRYPOINT vs CMD 深层区别


1️⃣ 设计哲学

指令 设计目的
CMD 默认行为
ENTRYPOINT 固定入口

2️⃣ 行为差异(核心)

行为 CMD ENTRYPOINT
docker run 覆盖 ✅ 完全覆盖 ❌ 仅追加参数
适合场景 默认参数 固定程序
灵活性
控制性

五、组合模式(企业级用法 ⭐⭐⭐)


标准写法

复制代码

ENTRYPOINT ["python"]

CMD ["app.py"]

👉 执行:

复制代码

python app.py


覆盖参数:

复制代码

docker run image test.py

👉 实际:

复制代码

python test.py


再举一个真实例子(🔥常考)

复制代码

ENTRYPOINT ["nginx", "-g"]

CMD ["daemon off;"]

👉 实际:

复制代码

nginx -g "daemon off;"


六、你实验中的关键现象解释(重点理解)


1️⃣ 为什么 CMD 会被覆盖?

复制代码

docker run cmd1:v0.1 echo hello bit2

👉 Docker认为:

复制代码

CMD = 默认命令 → 被用户命令替换


2️⃣ 为什么 ENTRYPOINT 不会被覆盖?

复制代码

docker run cmd1:v0.2 echo hello bit2

👉 Docker认为:

复制代码

ENTRYPOINT = 必须执行的程序

echo hello bit2 = 参数(被忽略或追加)


3️⃣ 为什么可以用 --entrypoint 覆盖?

复制代码

docker run --entrypoint /bin/sh image

👉 这是 Docker 提供的强制替换入口机制


七、最容易踩的坑(非常重要 ⚠️)


❌ 坑1:用 shell 模式

复制代码

CMD python app.py

👉 问题:

  • 信号丢失
  • PID 不是 1

❌ 坑2:ENTRYPOINT 写死参数

复制代码

ENTRYPOINT ["ping", "localhost"]

👉 问题:

  • 用户无法改 host

❌ 坑3:CMD 写成完整程序(配 ENTRYPOINT 时)

复制代码

ENTRYPOINT ["ping"]

CMD ["ping", "localhost"] ❌

👉 会变成:

复制代码

ping ping localhost


八、最佳实践(必须记住 ⭐⭐⭐)


✅ 标准模板

复制代码

ENTRYPOINT ["可执行程序"]

CMD ["默认参数"]


✅ 一定使用 Exec 格式

复制代码

CMD ["node", "app.js"]

❌ 不要:

复制代码

CMD node app.js


✅ 适用场景总结

场景 用法
可变参数程序 ENTRYPOINT + CMD
固定命令 ENTRYPOINT
简单默认命令 CMD

九、终极理解(面试一句话)

👉 你可以这样说(很加分):

复制代码

ENTRYPOINT 用于定义容器的主程序,

CMD 用于提供默认参数。

当两者同时存在时,

CMD 会作为参数传递给 ENTRYPOINT。

为了保证信号正确传递和容器优雅退出,

推荐使用 exec 形式而不是 shell 形式。


🔥 最后一层理解(高手级)

👉 Docker 容器的本质:

复制代码

容器 ≈ 一个进程(PID 1)

👉 你写 CMD / ENTRYPOINT,本质就是在决定:

复制代码

谁是 PID 1

📦 Docker .dockerignore 使用笔记(详细实战版)


一、基础知识

1️⃣ 什么是 build context

Docker 是 C-S 架构

  • Client(你执行 docker build 的机器)
  • Server(Docker daemon)

当你执行:

复制代码

docker build -f Dockerfile -t test .

👉 最后的 . 就是 build context(构建上下文)

📌 本质

build context = 会被发送给 Docker Server 的所有文件


2️⃣ 存在的问题

如果当前目录很大,比如:

复制代码

.

├── node_modules/

├── .git/

├── logs/

├── tmp/

├── src/

❗ 默认行为:

👉 全部发送到 Docker Server

带来问题:

  • 构建慢(文件太多)
  • 镜像变大
  • 泄露敏感文件(.env / 密钥)
  • 无用文件被 COPY 进镜像

3️⃣ .dockerignore 的作用

👉 类似 .gitignore

核心作用:

过滤 build context,不让某些文件发送给 Docker Server

✔ 优点:

  • 🚀 加快 build 速度
  • 📦 减小镜像体积
  • 🔒 防止敏感信息泄露
  • 🧹 保持镜像干净

二、基本语法

1️⃣ 文件名

必须叫:

复制代码

.dockerignore

放在 build context 根目录


2️⃣ 常用规则

忽略某类文件

复制代码

*.txt

👉 忽略所有 .txt


忽略目录

复制代码

node_modules/


忽略多个

复制代码

*.log

*.tmp

.cache/


取反(白名单)

复制代码

*.txt

!important.txt

👉 忽略 txt,但保留 important.txt


忽略隐藏文件

复制代码

.git

.env


三、实战演示(完整步骤)


🧪 实验目标

👉 验证 .dockerignore 会影响 COPY 结果


1️⃣ 创建 Dockerfile

复制代码

使用基础镜像

FROM centos:7

拷贝当前目录所有文件到容器根目录

COPY ./* /


2️⃣ 创建 .dockerignore

复制代码

*.txt

👉 表示忽略所有 .txt 文件


3️⃣ 准备测试文件

复制代码

tree ./

目录结构:

复制代码

.

├── 1.txt

├── 2.txt

├── 3.doc

└── dockerfile


4️⃣ 构建镜像

复制代码

docker build -f dockerfile -t test_ignore:1.0 .

关键日志:

复制代码

Sending build context to Docker daemon 4.096kB

👉 注意:

  • 这里的 context 已经 过滤过了
  • .txt 文件不会发送

5️⃣ 运行容器验证

复制代码

docker run -it test_ignore:1.0

进入容器后:

复制代码

ls /

输出:

复制代码

3.doc dockerfile ...


✅ 结论

文件 是否存在
1.txt ❌ 被忽略
2.txt ❌ 被忽略
3.doc ✅ 存在

👉 .dockerignore 生效 ✔


四、核心理解(重点)

❗ 关键点 1

.dockerignore 影响的是:

❌ 发送到 Docker Server 的文件

不是 COPY 本身


❗ 关键点 2

流程:

复制代码

本地目录

↓(.dockerignore 过滤)

build context

↓(发送)

Docker Server

↓(COPY)

镜像


❗ 关键点 3

如果文件被 ignore:

👉 即使你写了:

复制代码

COPY 1.txt /

👉 也会报错:

复制代码

COPY failed: file not found


五、生产环境最佳实践 ⭐⭐⭐


✅ 1. 必加规则(通用模板)

复制代码

Git

.git

.gitignore

Node

node_modules/

日志

*.log

临时文件

tmp/

*.tmp

环境变量

.env

编译产物

dist/

build/


✅ 2. Java 项目

复制代码

target/

*.jar


✅ 3. Python 项目

复制代码

pycache/

*.pyc

venv/


✅ 4. 前端项目

复制代码

node_modules/

dist/

.cache/


六、常见坑


❌ 1. 忘写 .dockerignore

👉 导致:

  • build 超慢
  • 镜像巨大

❌ 2. 忽略错文件

复制代码

*.conf

👉 结果:

👉 配置文件没进镜像 → 容器启动失败


❌ 3. COPY 不到文件

复制代码

COPY app.jar /

.dockerignore 写了:

复制代码

*.jar

👉 ❌ 直接构建失败


七、一句话总结

.dockerignore = 控制"哪些文件参与构建"的过滤器

👉 本质是优化 build context,而不是 COPY 行为


八、进阶建议(面试加分)

你可以这样说:

👉 Docker 构建性能优化:

  • 使用 .dockerignore 减少 context
  • 分层 COPY(先依赖再源码)
  • 使用缓存机制(layer cache)

🚀 Docker 多阶段构建(Multi-stage Build)笔记


一、为什么需要多阶段构建

1️⃣ 传统构建方式问题

❌ 方式一:全部写在一个 Dockerfile

复制代码

编译 + 测试 + 打包 + 运行 全塞一起

👉 问题:

  • Dockerfile 很长 ❌
  • 镜像层数多 ❌
  • 镜像体积巨大 ❌(几百 MB ~ GB)
  • 部署慢 ❌
  • 包含无用工具(gcc、node、maven 等)❌

❌ 方式二:多个 Dockerfile

复制代码

构建阶段 Dockerfile

运行阶段 Dockerfile

👉 问题:

  • 需要多个文件 ❌
  • 需要脚本整合 ❌
  • 维护复杂 ❌

✅ 解决方案:多阶段构建

👉 Docker 17.05+ 支持

核心思想:

一个 Dockerfile,多个阶段

👉 "构建环境"和"运行环境"分离


二、核心原理 ⭐⭐⭐


🔥 关键机制

复制代码

阶段1:编译(有 gcc)

阶段2:运行(只有可执行文件)

👉 最终镜像:

只保留最后一个阶段的内容


📌 本质一句话

只把"结果"拷贝到最终镜像,而不是整个构建环境


三、实战(完整演示)


🧪 实验目标

👉 用 Docker 编译 C 程序并运行


1️⃣ 准备代码

复制代码

#include <stdio.h>

int main() {

printf("hello docker!\n");

return 0;

}

保存为:

复制代码

demo.c


四、❌ 单阶段构建(问题演示)


Dockerfile

复制代码

FROM centos:7

WORKDIR /src

COPY demo.c .

RUN yum install -y gcc

RUN gcc demo.c -o demo

CMD ["/src/demo"]


构建 + 运行

复制代码

docker build -t multi:v1.0 .

docker run --rm multi:v1.0

输出:

复制代码

hello docker!


❗ 问题

复制代码

docker images

👉 结果:

复制代码

≈ 900MB ❌


📌 原因

  • gcc 在镜像里 ❌
  • yum 缓存 ❌
  • 编译环境 ❌

👉 都被打包进去了!


五、✅ 多阶段构建(核心实战)


Dockerfile(两阶段)

复制代码

===== 第一阶段:构建阶段 =====

FROM centos:7 AS builder

WORKDIR /src

COPY demo.c .

RUN yum install -y gcc && \

gcc demo.c -o demo

===== 第二阶段:运行阶段 =====

FROM centos:7

从第一阶段复制结果

COPY --from=builder /src/demo /src/demo

CMD ["/src/demo"]


构建 + 运行

复制代码

docker build -t multi:v2.0 .

docker run --rm multi:v2.0

输出:

复制代码

hello docker!


📦 镜像大小

复制代码

≈ 200MB ✅

👉 已经明显变小


六、🚀 进一步优化(极致瘦身)


使用更小基础镜像(busybox)


Dockerfile(终极版)

复制代码

===== 第一阶段:构建 =====

FROM centos:7 AS builder

WORKDIR /src

COPY demo.c .

RUN yum install -y gcc && \

gcc demo.c -o demo

===== 第二阶段:运行 =====

FROM busybox

COPY --from=builder /src/demo /src/demo

CMD ["/src/demo"]


构建 + 运行

复制代码

docker build -t multi:v3.0 .

docker run --rm multi:v3.0


📦 镜像大小

复制代码

≈ 4.8MB 🔥🔥🔥


七、为什么能变小(核心理解)


❗ 本质原因

👉 最终镜像只包含:

  • 可执行文件 ✅
  • 运行环境 ✅

❌ 不包含:

  • gcc ❌
  • yum ❌
  • 源码 ❌
  • 编译过程 ❌

📊 对比总结

内容 单阶段 多阶段
gcc
源码
编译工具
最终程序

八、核心语法总结 ⭐⭐⭐


1️⃣ 定义阶段

复制代码

FROM centos:7 AS builder

👉 给阶段起名字


2️⃣ 跨阶段复制

复制代码

COPY --from=builder /src/demo /src/demo

👉 从前一个阶段拷贝文件


3️⃣ 多阶段结构

复制代码

FROM A AS build

...

FROM B

COPY --from=build ...

FROM centos:7 AS builder

这句:

FROM centos:7 AS builder

多阶段构建里的核心语法,我们拆开讲👇


一、逐个拆解

1️⃣ FROM centos:7

👉 表示:

centos:7 作为基础镜像

也就是:

  • 你当前这个阶段运行在 CentOS 7 上
  • 可以用 yumgcc 等工具

2️⃣ AS builder

👉 给这个阶段起一个名字

builder = 当前阶段的"别名"


二、整体含义

复制代码

FROM centos:7 AS builder

👉 等价理解:

"创建一个基于 centos:7 的构建阶段,并命名为 builder"


三、为什么要取名(重点)

👉 因为后面要用到这个阶段的结果!


📌 核心用法

复制代码

COPY --from=builder /src/demo /src/demo

👉 含义:

builder 阶段中,把 /src/demo 拷贝到当前阶段


四、如果不写 AS 会怎样?

也可以写:

复制代码

FROM centos:7

但问题:

👉 后面只能用 数字索引

复制代码

COPY --from=0 /src/demo /src/demo


❌ 不推荐原因

  • 可读性差
  • 容易写错
  • 多阶段时很混乱

✅ 推荐写法

复制代码

FROM centos:7 AS builder

👉 更清晰、可维护


五、完整流程理解(非常重要)

复制代码

阶段1:构建

FROM centos:7 AS builder

RUN gcc demo.c -o demo

阶段2:运行

FROM busybox

COPY --from=builder /demo /demo

Docker 镜像缓存优化笔记

基础知识

  • Docker 在构建镜像时,会根据 Dockerfile 指令顺序 执行,每条指令都会生成一个 镜像层(layer)
  • 构建过程中,Docker 会检查 缓存
    • 如果某一层镜像已经存在且未修改,则直接使用缓存。
    • 一旦某一层被修改,后续层都会重新构建,无法使用缓存。
  • 若不想使用缓存,可以在构建时加 --no-cache=true,但通常建议 合理利用缓存加快构建速度

实战目的

  • 学会编写 Dockerfile 以合理利用缓存,加速镜像构建。

示例:C 语言程序

1. C 源文件 demo.c

复制代码

#include <stdio.h>

int main() {

printf("hello docker!\n");

return 0;

}


2. Dockerfile 初版

复制代码

FROM centos:7

WORKDIR /src

COPY demo.c .

设置国内源

RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \

-e 's|^#baseurl=http://mirror.centos.org/centos\|baseurl=https://mirrors.ustc.edu.cn/centos\|g' \

-i.bak /etc/yum.repos.d/CentOS-Base.repo

RUN yum makecache

安装 gcc

RUN yum install -y gcc

编译并清理

RUN gcc demo.c -o demo && \

rm -f demo.c && \

yum erase -y gcc

CMD ["/src/demo"]

3. 构建镜像

复制代码

docker image build -t cache:1.0 .

  • 第一次构建没有缓存,全程约 80s

4. 修改源文件 demo.c

复制代码

#include <stdio.h>

int main() {

printf("hello bit!\n");

return 0;

}

5. 再次构建

复制代码

docker image build -t cache:2.0 .

  • 由于修改了 demo.c,COPY 后面的所有层都无法使用缓存。
  • 构建耗时 85s,比第一次还长。

优化缓存策略

调整 Dockerfile 顺序

原则:不常修改的内容放前面,经常修改的内容放后面

复制代码

FROM centos:7

设置国内源

RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \

-e 's|^#baseurl=http://mirror.centos.org/centos\|baseurl=https://mirrors.ustc.edu.cn/centos\|g' \

-i.bak /etc/yum.repos.d/CentOS-Base.repo

RUN yum makecache

安装 gcc

RUN yum install -y gcc

WORKDIR /src

COPY demo.c .

编译并清理

RUN gcc demo.c -o demo && \

rm -f demo.c && \

yum erase -y gcc

CMD ["/src/demo"]

构建优化后的镜像

复制代码

docker image build -t cache:3.0 .

  • 首次构建仍无法使用缓存,但时间缩短到 37.8s

再次修改 demo.c 并构建

复制代码

docker image build -t cache:4.0 .

  • 此时大部分层已使用缓存,仅重新编译源文件。
  • 构建时间缩短到 2.5s,效率显著提高。

总结

  1. Docker 缓存原则
    • 每条指令生成一层镜像。
    • 一旦某层改变,后续层全部重建。
  2. 优化策略
    • 不常改动的指令放前面(系统依赖、工具安装、国内源配置)。
    • 常改动的指令放后面(COPY 源代码、编译)。
  3. 效果
    • 利用缓存可将镜像构建时间从几十秒缩短到几秒。
相关推荐
ZzzZZzzzZZZzzzz…2 小时前
MySQL还原备份方法3----gtid
linux·运维·数据库·mysql·还原备份
黑金IT2 小时前
从“视觉断言”到“自动化指挥”:Qwen3-V2 如何终结 AI 的随机性
运维·人工智能·自动化
浮尘笔记2 小时前
Docker中安装Kafka以及基本配置和用法、踩坑记录
后端·docker·容器·kafka·php
大卡片2 小时前
IO缓存区
linux·运维·缓存
志栋智能2 小时前
超自动化巡检:洞察未知隐患,助您事前不出事
大数据·运维·网络·数据库·自动化
杼蛘2 小时前
Kali下载与简单使用/MariaDB安装/Docker安装/MySQL镜像安装
mysql·docker·kali·mariadb
出海干货炒鱿鱼2 小时前
IP大科普:住宅IP、机房IP、原生IP、双ISP
运维·服务器
CDN3602 小时前
高防服务器带宽跑满、业务掉线?流量限制与清洗优化
运维·服务器
似水এ᭄往昔2 小时前
【Linux】--程序地址空间
linux·运维·服务器