2、Docker镜像管理:镜像的获取与查看、镜像的构建(docker build)、Dockerfile基础指令(FROM, RUN, CMD, ENTRYPOINT)、镜像的导出与导入、镜像仓库(Docker Hub与私有仓库)
聊到Docker,镜像就是它的灵魂。没有镜像,容器就是个空壳子。我刚开始接触Docker那会儿,最头疼的就是搞不清镜像和容器的关系。说白了,镜像就是模板,容器就是模板跑起来的实例。今天咱们就把镜像管理这块彻底捋清楚。
2.1 镜像的获取与查看
获取镜像最直接的方式就是拉取。Docker Hub上啥都有,从操作系统到各种中间件,一应俱全。
# 拉取最新版Ubuntu
docker pull ubuntu:latest
# 拉取指定版本
docker pull nginx:1.21-alpine
# 查看本地已有镜像
docker images
# 更详细的查看方式
docker image ls
我个人习惯用 docker images,敲起来快。但如果你要写脚本,建议用 docker image ls,语义更清晰。
查看镜像的详细信息也很重要:
# 查看镜像详情
docker inspect ubuntu:latest
# 只看镜像的历史层
docker history ubuntu:latest
docker history 这个命令我特别喜欢。它能让你看到镜像的每一层是怎么构建出来的。你想想看,如果镜像体积突然变大,用这个命令一查,哪层搞的鬼一目了然。
2.2 镜像的构建(docker build)
自己构建镜像,这才是Docker的核心能力。你写个Dockerfile,然后跑 docker build,一个定制化的镜像就出来了。
# 基本构建命令
docker build -t myapp:v1 .
# 指定Dockerfile路径
docker build -f ./docker/Dockerfile -t myapp:v1 .
# 带构建参数
docker build --build-arg VERSION=1.0 -t myapp:v1 .
这里有个坑,我曾经踩过。那个点号 . 是构建上下文路径,不是随便写的。Docker会把整个上下文目录打包发给守护进程。如果你在根目录执行 docker build .,那酸爽……整个硬盘的文件都会被发过去,构建慢得你想哭。
.dockerignore 文件,把node_modules、.git这些没用的东西过滤掉。曾经有个同事没加这个,构建一次花了15分钟,加了之后15秒搞定。
2.3 Dockerfile基础指令
Dockerfile就是镜像的配方。咱们把最核心的几个指令讲透。
FROM:一切的基础
每个Dockerfile第一行必须是FROM。它指定了基础镜像,相当于你盖房子的地基。
# 最简形式
FROM alpine:3.18
# 多阶段构建
FROM golang:1.21 AS builder
# ... 编译阶段 ...
FROM alpine:3.18
COPY --from=builder /app/myapp /myapp
多阶段构建这个特性,我强烈推荐你用。它能让最终镜像体积缩小80%以上。我做过一个Go项目,没用多阶段构建时镜像1.2GB,优化后只有15MB。你想想看,这差距有多大。
RUN:执行命令
RUN是在构建镜像时执行的命令。每一条RUN都会创建一个新的镜像层。
# 不推荐:每条RUN一层,镜像臃肿
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
# 推荐:合并成一条RUN
RUN apt-get update && \
apt-get install -y curl vim && \
rm -rf /var/lib/apt/lists/*
嗯,这里要注意。合并RUN指令不仅能减少层数,还能清理缓存。我见过有人一个Dockerfile写了20条RUN,镜像体积直接爆炸。记住:层数越少,镜像越小。
CMD vs ENTRYPOINT:启动时的区别
这两个指令容易搞混。我刚开始也分不清,后来用了个简单方法记住:
- CMD:提供默认命令,可以被覆盖
- ENTRYPOINT:固定入口,不容易被覆盖
# CMD示例:启动nginx
FROM nginx:alpine
CMD ["nginx", "-g", "daemon off;"]
# ENTRYPOINT示例:固定入口
FROM python:3.11
ENTRYPOINT ["python"]
CMD ["app.py"]
# 最佳实践:组合使用
FROM alpine:3.18
ENTRYPOINT ["/bin/myapp"]
CMD ["--config", "/etc/myapp/config.yaml"]
为什么推荐组合使用?我举个例子。你写了个CLI工具,用ENTRYPOINT固定入口,用户可以通过CMD传参。这样既保证了入口不变,又给了用户灵活性。我在做微服务网关时就是这么设计的,效果很好。
- docker run myapp --help → 如果ENTRYPOINT是python,CMD是app.py,实际执行的是 python --help
- docker run myapp → 执行 ENTRYPOINT + CMD 的组合
- docker run --entrypoint bash myapp → 强制覆盖ENTRYPOINT
2.4 镜像的导出与导入
有时候网络不通,或者你想把镜像带到离线环境,就需要导出导入。
# 导出镜像为tar文件
docker save -o myapp.tar myapp:v1
# 压缩导出(推荐)
docker save myapp:v1 | gzip > myapp.tar.gz
# 导入镜像
docker load -i myapp.tar
# 从压缩文件导入
gunzip -c myapp.tar.gz | docker load
我曾经在客户现场部署,那地方连Docker Hub都连不上。提前把镜像导出到U盘带过去,用 docker load 导入,几分钟就搞定了。要是没这功能,现场拉镜像能等到天荒地老。
2.5 镜像仓库
镜像不能总在本地待着,得有个地方存。Docker Hub是官方的公共仓库,私有仓库则适合企业内部使用。
Docker Hub:公共仓库
# 登录
docker login
# 推送镜像
docker tag myapp:v1 username/myapp:v1
docker push username/myapp:v1
# 拉取别人的镜像
docker pull nginx:alpine
Docker Hub免费用户只能建一个私有仓库。我建议你把敏感镜像放私有仓库,公开的镜像才放Docker Hub。别问我为什么这么说——我见过有人把数据库密码直接写进镜像里还推到公共仓库的,那场面……
私有仓库:Harbor与Registry
企业级应用,私有仓库是标配。Harbor是目前最流行的选择。
# 启动一个简单的Registry
docker run -d -p 5000:5000 --name registry registry:2
# 推送到私有仓库
docker tag myapp:v1 localhost:5000/myapp:v1
docker push localhost:5000/myapp:v1
# 从私有仓库拉取
docker pull localhost:5000/myapp:v1
Harbor的功能更强大,支持镜像复制、漏洞扫描、访问控制。我在公司搭了一套Harbor,配合Jenkins做CI/CD,开发提代码后自动构建镜像并推送到Harbor,测试环境直接拉取部署。整个流程自动化后,发布效率提升了至少3倍。
好了,镜像管理这块就聊到这儿。说白了就是三板斧:拉取构建、导出导入、仓库管理。把这些搞明白了,Docker就算入门了。下一章咱们聊聊容器运行时的那些事儿。