3. Docker基础
说实话,我刚接触 Docker 那会儿,也被镜像、容器、仓库这几个词绕得晕头转向。后来在项目里被逼着用,才慢慢摸透了它们的脾气。这一章,我就带你把这些核心概念掰开揉碎,再上手跑几个命令,最后亲手打个镜像出来。
一句话总结 Docker 的玩法:镜像就是打包好的“安装包”,容器就是“安装包”跑起来后的“进程实例”,仓库就是存“安装包”的网盘。你只需要把应用和依赖打成镜像,扔到仓库里,谁要谁拉下来跑成容器就行。
3.1 三个核心概念:镜像、容器、仓库
我们先从最基础的开始。这三个东西,你搞懂了,Docker 就算入门了。
3.1.1 镜像(Image)
镜像是一个只读的、轻量级的、可执行的软件包。它包含了运行某个软件所需的一切:代码、运行时、系统工具、系统库、设置。
你可以把它想象成一个 快照 或者 模板。比如一个 Nginx 镜像,里面就装好了 Nginx 程序、它的配置文件、依赖的库,甚至连 Ubuntu 的基础文件系统都打包进去了。
镜像是由一层一层的文件系统叠加起来的,这叫 分层存储。每一层都是只读的。我刚开始学的时候,总觉得分层这个概念很虚,直到有一次我基于一个基础镜像构建了十几个应用镜像,发现它们共享了底层的操作系统层,磁盘占用比想象中小得多,这才真正理解了分层的威力。
3.1.2 容器(Container)
容器是镜像的运行实例。你可以把它理解成一个 轻量级的虚拟机,但它不虚拟硬件,而是共享宿主机的操作系统内核。
容器在镜像的只读层之上,加了一层可写层。你在容器里做的任何修改(比如写日志、改文件),都只存在这一层。容器停止或删除后,这层数据默认就丢了。这就是为什么我总跟团队说:容器是无状态的,重要数据一定要挂载到宿主机或持久化存储里。
我的习惯:每次启动容器前,我都会先想清楚——这个容器的数据要不要保留?如果要,就用 -v 参数挂载卷。不然容器一删,数据全没了,哭都来不及。
3.1.3 仓库(Repository)
仓库就是存放镜像的地方。最出名的是 Docker Hub,官方的公共仓库。你也可以搭建私有的,比如 Harbor、Nexus。
仓库的概念跟 Git 仓库很像:你可以 push 镜像上去,也可以 pull 下来。镜像的名字通常由三部分组成:仓库地址/命名空间/镜像名:标签。比如 nginx:1.25,省略了仓库地址,默认就是 Docker Hub 的官方镜像。
| 概念 | 类比 | 关键特性 |
|---|---|---|
| 镜像 | 类 / 安装包 | 只读、分层、可复用 |
| 容器 | 对象 / 进程 | 可写层、生命周期短、无状态 |
| 仓库 | Git 远程仓库 | 存储、分发、版本管理 |
3.2 Docker 常用命令:够用就行
命令不用全背,记住最常用的几个,剩下的查文档就行。我平时 80% 的时间,用的就是下面这些。
3.2.1 镜像操作
# 搜索镜像
docker search nginx
# 拉取镜像(不写标签默认 latest)
docker pull nginx:1.25
# 列出本地镜像
docker images
# 删除镜像
docker rmi nginx:1.25
# 查看镜像的构建历史
docker history nginx:1.25
我曾经踩过的坑:用 docker rmi 删镜像时,如果这个镜像正在被某个容器使用(哪怕容器已停止),会删不掉。得先 docker rm 删掉容器,或者加 -f 强制删除。但强制删除容易留下孤儿容器,我建议还是先清理容器再删镜像。
3.2.2 容器操作
# 创建并启动容器(最常用)
docker run -d --name my-nginx -p 8080:80 nginx:1.25
# 列出运行中的容器
docker ps
# 列出所有容器(包括已停止的)
docker ps -a
# 停止容器
docker stop my-nginx
# 启动已停止的容器
docker start my-nginx
# 进入容器内部(调试用)
docker exec -it my-nginx bash
# 查看容器日志
docker logs -f my-nginx
# 删除容器
docker rm my-nginx
docker run 的参数很多,但最核心的就几个:-d 后台运行,--name 起名字,-p 端口映射,-v 挂载卷。你想想看,一个 Web 服务,是不是就靠这些参数就能跑起来了?
3.2.3 常用参数速查
| 参数 | 作用 | 示例 |
|---|---|---|
-d |
后台运行(detach) | docker run -d nginx |
-p |
端口映射(宿主机:容器) | -p 8080:80 |
-v |
挂载卷(宿主机路径:容器路径) | -v /data:/app/data |
--name |
指定容器名称 | --name my-app |
-e |
设置环境变量 | -e MYSQL_ROOT_PASSWORD=123 |
--restart |
重启策略 | --restart=always |
3.3 Dockerfile 编写入门:把应用打包成镜像
Dockerfile 就是一个文本文件,里面写着一系列指令,告诉 Docker 怎么构建你的镜像。说白了,就是一份 镜像制作说明书。
3.3.1 核心指令
FROM:指定基础镜像。几乎所有的 Dockerfile 都以FROM开头。比如FROM python:3.11-slim,意思是我基于一个精简的 Python 3.11 镜像开始构建。WORKDIR:设置工作目录。后续的COPY、RUN、CMD都会在这个目录下执行。COPY:把宿主机上的文件复制到镜像里。比如COPY . /app,把当前目录所有文件复制到镜像的/app目录。RUN:在构建镜像时执行命令。通常用来安装依赖、编译代码。比如RUN pip install -r requirements.txt。CMD:指定容器启动时执行的命令。一个 Dockerfile 只能有一个CMD。比如CMD ["python", "app.py"]。EXPOSE:声明容器运行时监听的端口。这只是声明,实际映射还得靠-p参数。
3.3.2 一个简单的 Dockerfile 示例
假设你有一个 Python Flask 应用,文件结构如下:
my-flask-app/
├── app.py
└── requirements.txt
对应的 Dockerfile 可以这样写:
# 1. 选择基础镜像
FROM python:3.11-slim
# 2. 设置工作目录
WORKDIR /app
# 3. 复制依赖文件并安装(利用 Docker 缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. 复制应用代码
COPY app.py .
# 5. 声明端口
EXPOSE 5000
# 6. 启动命令
CMD ["python", "app.py"]
我的优化习惯:把 requirements.txt 和代码分开复制,能充分利用 Docker 的构建缓存。只要 requirements.txt 没变,后面再构建时就不会重新安装依赖,速度能快好几倍。我在项目里经常看到有人把所有文件一次性 COPY . .,每次改一行代码都得重装所有依赖,太浪费了。
3.4 构建第一个 Docker 镜像
好,理论说完了,咱们动手。我带你从零构建一个简单的 Nginx 静态页面镜像。
3.4.1 准备材料
创建一个新目录,里面放两个文件:
mkdir my-nginx-image
cd my-nginx-image
文件1:index.html
<!DOCTYPE html>
<html>
<head><title>我的第一个 Docker 镜像</title></head>
<body>
<h1>Hello from Docker!</h1>
<p>这是我亲手构建的镜像。</p>
</body>
</html>
文件2:Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
3.4.2 构建镜像
在 my-nginx-image 目录下执行:
docker build -t my-nginx:v1 .
解释一下:-t 给镜像打标签,名字是 my-nginx,版本是 v1。最后的 . 表示构建上下文是当前目录。
你会看到类似这样的输出:
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM nginx:alpine
alpine: Pulling from library/nginx
...
Step 2/3 : COPY index.html /usr/share/nginx/html/index.html
...
Step 3/3 : EXPOSE 80
...
Successfully built abc123def456
Successfully tagged my-nginx:v1
3.4.3 运行容器验证
docker run -d --name my-first-container -p 8080:80 my-nginx:v1
打开浏览器,访问 http://localhost:8080,你应该能看到那个 Hello from Docker! 的页面。
注意:如果你在 Windows 或 macOS 上用 Docker Desktop,localhost 可能不行,得用 127.0.0.1 或者 Docker 虚拟机的 IP。我刚开始在 Mac 上就卡在这一步,折腾了半天才发现是端口没真正映射出来。
3.4.4 清理现场
玩完了记得打扫卫生:
# 停止并删除容器
docker stop my-first-container
docker rm my-first-container
# 删除镜像
docker rmi my-nginx:v1
嗯,到这里,你已经亲手完成了从编写 Dockerfile 到构建镜像、再到运行容器的完整流程。别看简单,这套流程就是后面所有服务化部署的基础。你想想看,不管多复杂的应用,最后都是通过这样一个一个的镜像和容器组合起来的。