1. 这不是“学命令”,而是掌握现代软件交付的底层操作系统
你打开终端,敲下docker run hello-world,屏幕上跳出一行绿色文字——这短短几秒背后,是过去十年整个软件工程交付范式的一次静默革命。Docker 不是又一个 Linux 工具,它是让“在我机器上能跑”这句话真正失效的第一块基石。我从 2014 年在阿里云 ECS 上手动编译 Nginx + PHP + MySQL 三件套开始做运维,到后来用 Ansible 脚本批量部署,再到今天给客户交付整套 AI 推理服务时,只发一个docker-compose.yml文件加三行说明,中间省掉的不是时间,而是沟通成本、环境差异、权限冲突和凌晨三点的告警电话。
核心关键词docker、部署、基础命令,拆开看:docker是容器运行时引擎,是隔离进程的“轻量级虚拟机内核”;部署在这里已不是“把代码拷到服务器”,而是“把可执行环境连同代码一起打包、验证、分发、启动”的端到端闭环;基础命令更不是背诵清单,而是理解容器生命周期的六个关键控制点——拉取(pull)、运行(run)、查看(ps)、进入(exec)、日志(logs)、停止(stop)。这六条命令,覆盖了 90% 的日常操作场景,而剩下 10%,是它们组合出来的逻辑延伸。
适合谁?如果你还在为“测试环境好好的,一上生产就报错”挠头;如果你每次交接项目都要写三页《环境依赖说明》;如果你用 Python 写了个小工具,却要教同事装 pip、virtualenv、requests 库;或者你正打算本地跑 Dify、Ollama、vLLM 这类大模型服务——那你不是在学 Docker,你是在升级自己的软件交付操作系统。它不挑语言、不挑框架、不挑操作系统,Windows WSL2、Mac M系列芯片、Ubuntu 服务器、甚至树莓派,只要装了 Docker Engine,你就拥有了统一的部署语言。这不是锦上添花的技能,而是当前技术栈里最值得优先投入的“基础设施认知”。
2. 为什么必须从“部署”切入,而不是“镜像构建”或“网络配置”
很多教程一上来就讲Dockerfile语法、FROM指令、COPY和RUN的区别,结果新手学完只会写个打印 “Hello World” 的镜像,一碰到真实项目就卡在“怎么把我的 Flask 服务跑起来”“数据库连不上怎么办”。这是典型的本末倒置。Docker 的核心价值,第一层是环境一致性交付,第二层才是可复现的构建流程。对绝大多数人来说,前者的 ROI(投资回报率)远高于后者。
2.1 部署视角下的 Docker 本质:进程沙盒 + 环境快照
你可以把docker run理解成 Linux 的chroot+cgroups+namespaces三件套的自动化封装。它不虚拟硬件,只虚拟“进程能看到的世界”:
- 文件系统视图:容器内看到的
/目录,其实是镜像层叠加 + 一个干净的可写层,宿主机的/etc/passwd、/usr/bin对它完全不可见; - 进程视图:容器内
ps aux只能看到自己启动的那几个进程,宿主机的 nginx、mysql、cron 全部隐身; - 网络视图:默认使用 bridge 网络,容器有独立 IP(如 172.17.0.2),通过 NAT 访问外网,宿主机端口需显式映射(
-p 8080:80); - 用户与权限:容器内 root 用户默认等同于宿主机 root 权限(除非用
--user降权),但受限于命名空间隔离,无法操作宿主机进程或挂载点。
这种隔离不是为了安全(容器 ≠ 安全沙箱),而是为了消除环境变量。我曾接手一个遗留 Java 项目,开发用 JDK 11,测试用 JDK 17,生产用 JDK 8——光是 JVM 版本不一致就导致LocalDateTime序列化失败。用 Docker 后,我们直接指定openjdk:17-jre-slim镜像,所有环境用同一份二进制,问题消失。这才是部署层面的“一次构建,处处运行”。
2.2 基础命令不是孤立动作,而是容器生命周期的六个控制点
| 命令 | 对应生命周期阶段 | 关键参数与实操意图 | 新手最常踩的坑 |
|---|---|---|---|
docker pull | 获取可执行单元 | docker pull nginx:alpine—— 拉取精简版 Nginx,比nginx:latest小 60MB;--platform linux/amd64强制指定架构,解决 Apple Silicon Mac 上 x86 镜像兼容问题 | 盲目拉latest标签,导致线上环境因镜像更新意外变更行为;不加--platform在 M1/M2 Mac 上运行 x86 镜像失败 |
docker run | 创建并启动实例 | docker run -d -p 8080:80 --name my-nginx nginx:alpine——-d后台运行,-p端口映射,--name指定易记名称;-v $(pwd)/html:/usr/share/nginx/html:ro挂载本地 HTML 目录为只读静态资源 | 忘加-d导致终端被占住;端口映射写反(-p 80:8080错写成宿主机 80 映射容器 8080);挂载目录权限错误,Nginx 因无读权限报 403 |
docker ps | 查看运行态实例 | docker ps -a查看所有容器(含已退出);docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"自定义输出格式,替代grep筛选 | 用docker ps查不到容器就认为没运行,其实该容器可能已退出(docker ps -a才能看到);不加--format时字段过长,grep匹配名称易出错 |
docker exec | 进入运行中容器 | docker exec -it my-nginx sh——-it分配伪 TTY 并保持 STDIN 打开;sh替代bash(Alpine 镜像默认无 bash);-u root切换用户(某些镜像默认非 root) | 在 Alpine 镜像里敲bash报错“not found”,实际该用sh;忘记-it导致进入后无法输入命令;用docker attach误操作导致容器前台进程被阻塞 |
docker logs | 观察运行时输出 | docker logs -f my-nginx实时跟踪日志;docker logs --tail 100 my-nginx查看最后 100 行;docker logs -t my-nginx加时间戳,便于排查时序问题 | 日志滚动太快,-f后无法退出(按Ctrl+C即可);不加--tail直接查全量日志,大日志文件卡死终端;忽略-t时间戳,多个容器日志混在一起无法定位时间线 |
docker stop | 安全终止实例 | docker stop my-nginx发送 SIGTERM 信号,等待 10 秒后强制 SIGKILL;docker stop -t 30 my-nginx延长等待时间,确保应用优雅关闭(如 Spring Boot 的 shutdown hook) | 直接docker kill强制终止,导致数据库连接未释放、文件未刷盘;stop后不检查状态,误以为容器已停,实际因应用未响应 SIGTERM 仍在运行 |
提示:这六个命令构成最小可行闭环。你不需要记住全部 50+ 个 Docker 子命令,先吃透这六个,再根据需求自然延伸。比如
docker rm(删除已停止容器)是docker stop的后续动作;docker images(查看本地镜像)是docker pull的配套检查;它们都是生命周期链条上的自然节点。
2.3 部署场景决定命令组合逻辑:从单容器到多服务协同
真实项目极少单容器运行。一个典型 Web 应用至少需要 Web 服务 + 数据库 + 缓存。此时docker run的复杂度指数上升:
- 网络:需创建自定义 bridge 网络
docker network create myapp-net,让容器间用服务名互通(web容器访问db容器只需mysql://db:3306); - 数据卷:数据库数据必须持久化,
docker volume create mysql-data创建命名卷,避免--rm或容器删除后数据丢失; - 环境变量:
-e MYSQL_ROOT_PASSWORD=123456传参,比硬编码到镜像更安全灵活; - 依赖顺序:必须先启
db,再启web,否则 Web 服务启动时连不上数据库报错退出。
这个过程手动敲命令极易出错。于是docker-compose.yml成为部署事实标准——它把上述所有参数声明式地写在一个 YAML 文件里,一条docker compose up -d全部搞定。但请注意:Compose 不是新工具,它是docker run命令的高级封装。你完全可以用 20 行docker run命令实现 Compose 的功能,只是没人愿意维护。所以,学 Compose 前,务必亲手敲一遍等效的docker run组合,否则你永远不知道depends_on解决了什么问题,healthcheck如何防止服务假启动。
3. 基础命令实操详解:从零搭建一个可验证的本地部署环境
我们以部署一个极简但完整的 Python Flask API 服务为例,全程只用基础命令,不碰Dockerfile或docker-compose.yml。目标:本地启动服务,访问http://localhost:5000/health返回{"status":"ok"},并能实时查看日志、进入容器调试、安全停止服务。这个过程将覆盖所有核心命令的真实上下文。
3.1 环境准备:三步确认 Docker 已就绪
第一步永远不是敲命令,而是验证环境。很多人卡在第一步却以为是命令错了。
确认 Docker Engine 正在运行:
# Linux/macOS systemctl is-active docker # 返回 active 表示正常 # Windows (WSL2) service docker status # 或在 PowerShell 中运行 `Get-Service docker`注意:如果提示
Unit docker.service could not be found,说明 Docker Desktop 未安装或未启动。Ubuntu 用户注意,apt install docker.io安装的是旧版社区版,推荐用官方脚本:curl -fsSL https://get.docker.com | sh。验证用户权限:
docker run hello-world如果报错
Got permission denied while trying to connect to the Docker daemon socket,说明当前用户不在docker用户组。执行:sudo usermod -aG docker $USER # 退出终端重新登录,或执行 newgrp docker 生效检查镜像源(国内用户必做):
默认 Docker Hub 国内拉取极慢。编辑/etc/docker/daemon.json(Linux/macOS)或 Docker Desktop 设置(Windows/macOS),添加国内镜像加速器:{ "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://hub-mirror.c.163.com" ] }保存后重启 Docker:
sudo systemctl restart docker。验证是否生效:docker info | grep "Registry Mirrors"应显示上述地址。
3.2 构建并运行 Flask 服务:pull→run→ps闭环
我们不自己构建镜像,直接使用官方 Python 镜像运行脚本。这是最快验证部署流程的方式。
创建项目目录与代码:
mkdir flask-demo && cd flask-demo # 创建 app.py cat > app.py << 'EOF' from flask import Flask import os app = Flask(__name__) @app.route('/health') def health(): return {"status": "ok", "host": os.getenv('HOSTNAME', 'unknown')} if __name__ == '__main__': app.run(host='0.0.0.0:5000', port=5000, debug=False) EOF拉取 Python 运行时镜像:
docker pull python:3.11-slim # 查看镜像列表,确认拉取成功 docker images | grep python # 输出应类似:python 3.11-slim abc123... 2 weeks ago 123MB运行容器并映射端口:
docker run -d \ --name flask-api \ -p 5000:5000 \ -v $(pwd):/app \ -w /app \ -e FLASK_ENV=production \ python:3.11-slim \ sh -c "pip install flask && python app.py"参数详解:
-d:后台运行,返回容器 ID;--name flask-api:指定易记名称,替代随机字符串;-p 5000:5000:宿主机 5000 端口映射到容器 5000 端口;-v $(pwd):/app:将当前目录挂载为容器内/app目录,代码实时生效;-w /app:设置工作目录为/app,pip install和python命令在此目录执行;-e FLASK_ENV=production:设置环境变量,禁用 Flask 调试模式(安全必需);- 最后两段:在容器内执行
sh -c "pip install flask && python app.py",即安装依赖并启动服务。
验证容器是否运行:
docker ps | grep flask-api # 正常输出应包含:flask-api ... Up 2 seconds 0.0.0.0:5000->5000/tcp # 测试访问 curl http://localhost:5000/health # 应返回:{"status":"ok","host":"<容器ID前缀>"}
实操心得:第一次运行时,
pip install flask会耗时 10-20 秒,期间curl可能返回 connection refused。这是正常现象,因为容器已启动,但 Python 进程尚未监听端口。稍等几秒再试即可。不要因此怀疑命令失败。
3.3 调试与维护:exec→logs→stop完整链路
服务跑起来了,但如何确认它真的健康?如何修改代码?如何安全关闭?
进入容器查看运行状态:
docker exec -it flask-api sh # 进入后执行: ps aux | grep python # 应看到 python app.py 进程 netstat -tuln | grep :5000 # 应看到 0.0.0.0:5000 LISTEN exit # 退出容器注意:Alpine 镜像(如
python:3.11-slim)默认无bash,必须用sh。如果提示sh: not found,说明镜像不基于 Alpine,可尝试bash。实时跟踪日志并定位问题:
# 开一个终端窗口,实时跟踪日志 docker logs -f flask-api # 在另一个终端修改代码,触发重启(稍后介绍) echo 'print("Server started")' >> app.py # 日志窗口应立即输出:Server started # 按 Ctrl+C 退出日志跟踪安全停止并清理容器:
# 发送 SIGTERM,等待应用优雅关闭 docker stop flask-api # 验证已停止 docker ps -a | grep flask-api # STATUS 应为 Exited (0) ... # 删除已停止容器(可选,释放名称) docker rm flask-api # 删除挂载的卷(本例未用命名卷,无需此步)
常见问题:
docker stop后容器状态仍是Up X seconds?这是docker ps缓存问题,执行docker ps -a查看真实状态。若容器长时间不退出(超过 10 秒),说明应用未响应 SIGTERM,此时docker kill flask-api强制终止。
3.4 进阶技巧:用基础命令模拟 Compose 的核心能力
虽然我们主张先学命令,但 Compose 的便利性无可否认。下面用纯docker run实现 Compose 的两个关键特性:
服务依赖与健康检查:
Compose 的depends_on只控制启动顺序,不保证依赖服务已就绪。真实方案是让 Web 服务启动时检测 DB 是否可连。我们用wait-for-it.sh脚本模拟:# 下载 wait-for-it.sh 到项目目录 curl -o wait-for-it.sh https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh chmod +x wait-for-it.sh # 启动 MySQL 容器(使用命名卷持久化数据) docker run -d \ --name mysql-db \ -e MYSQL_ROOT_PASSWORD=123456 \ -v mysql-data:/var/lib/mysql \ -p 3306:3306 \ mysql:8.0 # 启动 Flask 容器,等待 MySQL 就绪后再启动 docker run -d \ --name flask-with-db \ -p 5000:5000 \ -v $(pwd):/app \ -w /app \ --link mysql-db:mysql \ python:3.11-slim \ sh -c "./wait-for-it.sh mysql:3306 -- pip install flask && python app.py"这里
--link是旧语法(已被用户定义网络取代),但用于单机演示足够清晰。wait-for-it.sh会轮询mysql:3306,直到端口可连才执行后续命令。多容器网络互通:
创建自定义网络,让容器用服务名通信:docker network create myapp-net # 启动 MySQL 并加入网络 docker run -d \ --name mysql-db \ --network myapp-net \ -e MYSQL_ROOT_PASSWORD=123456 \ -v mysql-data:/var/lib/mysql \ mysql:8.0 # 启动 Flask,并加入同一网络 docker run -d \ --name flask-api-net \ --network myapp-net \ -p 5000:5000 \ -v $(pwd):/app \ -w /app \ python:3.11-slim \ sh -c "pip install flask && python -c \"import socket; print(socket.gethostbyname('mysql-db'))\" && python app.py"socket.gethostbyname('mysql-db')会成功解析为容器 IP,证明网络互通。这是docker-compose.yml中networks配置的底层原理。
4. 常见问题与排查技巧实录:那些文档不会写的“血泪经验”
以下问题均来自我过去三年带团队、做客户交付时高频遇到的真实场景。它们不会出现在官方文档里,但能帮你节省数小时无效排查。
4.1 端口映射失效:宿主机端口被占用 or 容器内服务未监听正确地址
现象:docker run -p 8080:80 nginx:alpine启动后,curl http://localhost:8080返回Connection refused。
排查步骤:
- 确认容器是否真在运行:
docker ps | grep nginx。如果没输出,说明容器已退出,查日志:docker logs nginx-container-name; - 确认容器内 Nginx 是否监听
0.0.0.0:80而非127.0.0.1:80:docker exec -it nginx-container-name sh -c "netstat -tuln | grep :80"。Alpine 镜像需先apk add net-tools; - 确认宿主机端口未被占用:
sudo lsof -i :8080(macOS/Linux)或netstat -ano | findstr :8080(Windows); - 检查防火墙:Ubuntu
ufw status,CentOSfirewall-cmd --list-ports,临时关闭测试:sudo ufw disable; - Windows/macOS 用户特别注意:Docker Desktop 的 Linux VM 可能未启动,检查 Docker Desktop 图标是否为绿色。
实操心得:Nginx 默认配置监听
0.0.0.0:80,但某些定制镜像或配置文件可能改成了127.0.0.1。最简单验证法:docker exec nginx-container-name curl -s http://localhost:80,如果容器内能通,说明服务正常,问题在端口映射或宿主机网络。
4.2 挂载目录权限拒绝:容器内进程无法读写宿主机文件
现象:Flask 应用挂载代码目录后启动报错PermissionError: [Errno 13] Permission denied: '/app/app.py'。
根本原因:Linux 容器内进程以 UID/GID 运行,若与宿主机文件权限不匹配,则无权访问。例如,Alpine 镜像中python进程默认以 UID 0(root)运行,但宿主机文件属主是普通用户(UID 1000),且文件权限为600(仅属主可读写)。
解决方案:
- 方案一(推荐):修改宿主机文件权限
chmod 644 app.py # 改为所有者可读写,组和其他人只读 chmod 755 . # 当前目录需有执行权限(允许进入) - 方案二:以匹配 UID 启动容器
docker run -u $(id -u):$(id -g) -v $(pwd):/app python:3.11-slim python app.py - 方案三:在 Dockerfile 中创建匹配用户(长期项目)
FROM python:3.11-slim RUN groupadd -g 1001 -f user && useradd -S -u 1001 -U -m -d /home/user user USER user WORKDIR /app COPY --chown=user:user . . CMD ["python", "app.py"]
注意:Windows/macOS 用户通常无此问题,因为 Docker Desktop 的文件共享机制自动处理了权限映射。该问题高发于 Linux 服务器部署。
4.3 镜像拉取失败:网络超时 or 镜像不存在
现象:docker pull ubuntu:22.04卡住,或报错manifest for ubuntu:22.04 not found。
排查与解决:
- 检查镜像标签是否存在:访问 Docker Hub Ubuntu 页面 ,确认
22.04标签是否列出。很多镜像只提供latest、jammy(22.04 代号)、focal(20.04)等标签,而非数字版本; - 检查网络与镜像源:
curl -v https://docker.mirrors.ustc.edu.cn/v2/,看是否返回401 Unauthorized(正常)或超时(网络问题); - 清除本地缓存重试:
docker system prune -a(谨慎,会删所有镜像和容器); - 离线环境方案:在有网机器拉取镜像,
docker save -o ubuntu.tar ubuntu:jammy,拷贝到目标机器,docker load -i ubuntu.tar。
实操心得:永远优先用发行版代号(
jammy,focal)而非数字版本(22.04,20.04),因为 Docker Hub 官方镜像维护策略更稳定。latest标签慎用,它可能指向不稳定分支。
4.4 容器内时区错误:日志时间比宿主机快8小时
现象:docker logs -t输出的时间戳比宿主机date快8小时(如宿主机2024-05-20 10:00,日志显示2024-05-20 18:00)。
原因:Docker 官方镜像默认使用 UTC 时区,而宿主机是 CST(UTC+8)。容器内date命令显示 UTC 时间,但日志时间戳由 Docker Daemon 注入,也基于 UTC。
永久解决:
# 启动容器时挂载宿主机时区文件 docker run -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro ... # 或在 Dockerfile 中设置 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone提示:对于日志分析,建议统一用 UTC 时间,避免时区转换混乱。ELK、Prometheus 等监控系统默认按 UTC 处理时间戳。
4.5 Windows WSL2 环境常见陷阱:Virtualization Support Not Detected
现象:Docker Desktop 启动失败,报错Virtualization support not detected或Docker Engine failed to start because V。
根因与修复:
- WSL2 内核未启用:PowerShell 以管理员身份运行:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart wsl --update wsl --set-default-version 2 - BIOS 中 Virtualization Technology (VT-x/AMD-V) 未开启:重启电脑进 BIOS(通常 Del/F2/F10),找到
Advanced→CPU Configuration→Intel Virtualization Technology,设为Enabled; - Hyper-V 冲突:Windows 专业版/企业版默认启用 Hyper-V,与 WSL2 冲突。禁用:
dism.exe /online /disable-feature /featurename:Microsoft-Hyper-V /all /norestart,然后重启。
实操心得:WSL2 是目前 Windows 上 Docker 开发体验最好的方案。Docker Desktop for Windows 本质就是管理 WSL2 中的 Linux 发行版。放弃旧的“Docker Toolbox”(基于 VirtualBox),那是 2015 年的技术。
5. 从基础命令到生产就绪:三个必须跨越的认知台阶
学完命令,你会发现自己能跑通 demo,但一到真实项目就手足无措。这不是技术问题,而是认知断层。我带过的上百个新人,几乎都卡在这三道坎上。跨过去,你就从“会用 Docker”变成“懂容器化”。
5.1 台阶一:从“运行容器”到“理解镜像分层与存储驱动”
docker images显示的镜像大小,和docker system df显示的磁盘占用,往往差几倍。为什么?因为镜像不是单个文件,而是由多个只读层(layer)叠加而成,共享底层数据。docker pull下载的是这些层,docker run启动时在顶部加一个可写层(container layer)。
- 分层原理:每条
Dockerfile指令(FROM,RUN,COPY)生成一层。RUN apt update && apt install nginx是一层,COPY nginx.conf /etc/nginx/是另一层。层是只读的,按顺序叠加,上层文件覆盖下层同名文件; - 存储驱动影响:Linux 默认用
overlay2,高效共享层;Windows 用windowsfilter;macOS 通过 HyperKit VM 间接使用overlay2。docker info | grep "Storage Driver"查看; - 清理磁盘:
docker system prune -a删除所有未使用的镜像、容器、网络、构建缓存。但注意:它会删掉所有dangling(悬空)镜像,即没有标签且未被任何容器引用的层。生产环境慎用。
实操心得:构建镜像时,把变化少的指令(如
apt install)放前面,变化多的(如COPY . /app)放后面。这样代码修改后,只有最后几层需要重建,大幅提升 CI/CD 构建速度。这是Dockerfile优化的核心逻辑。
5.2 台阶二:从“单机部署”到“理解容器编排的本质”
docker run是单机命令,docker-compose是单机编排,Kubernetes是集群编排。它们不是替代关系,而是抽象层级递进:
docker run解决“一个进程怎么隔离运行”;docker-compose解决“一组相关进程怎么协同启动、网络互通、依赖管理”;Kubernetes解决“上千个容器怎么跨多台机器调度、自动扩缩容、故障自愈、服务发现”。
一个典型误区:用docker-compose部署生产环境。Compose 设计初衷是开发和测试,它的restart: always不能替代 Kubernetes 的livenessProbe(存活探针)。当容器内进程假死(CPU 100% 但不响应请求),Compose 不会重启它,而 K8s 会。
真实案例:某客户用 Compose 部署 Redis 集群,某节点内存溢出 OOM,容器退出。Compose 自动重启,但新容器启动后因配置错误再次 OOM,形成无限重启循环,而监控系统只看到“容器频繁重启”,无法定位根本原因。迁移到 K8s 后,配置
livenessProbe检测redis-cli ping,配合OOMKill事件告警,问题迎刃而解。
5.3 台阶三:从“部署服务”到“构建可审计、可追溯的交付流水线”
docker run是手工操作,docker build是构建环节,docker push是发布环节。真正的生产就绪,是把这三步串成自动化流水线,并确保每个环节可审计。
- 镜像签名与验证:用
cosign对镜像签名,docker trust启用内容信任,确保拉取的镜像是经授权构建的; - SBOM(软件物料清单)生成:
syft扫描镜像,生成 JSON 格式依赖清单,供安全团队审计漏洞; - CI/CD 集成:GitHub Actions 中,
on: push触发docker build -t $IMAGE_NAME:$GITHUB_SHA .,docker push $IMAGE_NAME:$GITHUB_SHA,然后kubectl set image deployment/myapp container=$IMAGE_NAME:$GITHUB_SHA更新 K8s。每次部署都绑定 Git Commit ID,回滚只需kubectl rollout undo deployment/myapp。
我的体会:Docker 命令本身很简单,难的是建立一套与之匹配的工程规范。没有规范的
docker run,和没有版本管理的git commit一样危险。当你开始思考“这个镜像谁构建的?何时构建的?基于哪个 Git 提交?包含哪些已知漏洞?”,你就真正进入了容器化工程实践的大门。
最后分享一个小技巧:把最常用的docker run命令写成 shell 函数,放在~/.bashrc或~/.zshrc里。例如:
# 快速启动一个带 bash 的 Ubuntu 容器,挂载当前目录 dock () { docker run -it --rm -v $(pwd):/workspace -w /workspace ubuntu:22.04 bash }以后在任意目录下敲dock,就直接进入一个干净的 Ubuntu 环境,exit后容器自动删除。这种“命令即服务”的思维,才是 Docker 真正融入工作流的标志。