news 2026/10/20 17:21:54

Docker入门实战:从容器基础到部署全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker入门实战:从容器基础到部署全解析

直接开始写。

1. 为什么要学 Docker:先搞清楚它解决什么问题

我最早接触 Docker 是在一次代码上线事故之后。某个项目在本地跑得风生水起,代码一提交,测试同事那边直接起不来。查了半天,人家环境里缺了个系统库,版本也不对。这种问题相信每个人都经历过,背后就是那个老生常谈的“环境不一致”问题。

Docker 的本质,其实就是把你的应用和它运行所需要的环境打包到一起。这个环境包括操作系统依赖、运行时、系统库、配置文件、环境变量,全部装进一个标准化的“盒子”里。这个盒子在任何装了 Docker 的机器上,跑到哪里都是一毛一样的行为。这就是它的核心价值:一次构建,到处运行。

1.1 环境不一致的痛点

先聊聊环境问题背后的真实场景。我们开发的时候用的是自己的电脑,可能是 Mac,可能装了一堆配置;代码提交之后,测试环境是 Linux,而且测试服务器上可能已经跑了好几个项目,依赖互相污染。前端还好一点,后端涉及的系统库、Python 或 Node 版本、环境变量,哪一个不对都跑不起来。

我记得做一个数据清洗任务的时候,本地用的是新版 JSON 解析库,测试机上旧版本的库不兼容,结果代码跑一半直接崩了。排查了大半天,就是环境版本问题。如果没有 Docker,这类问题几乎无解,总不能每次部署都手动去配环境。而 Docker 的思路是,你把项目用到的所有依赖装进镜像,别人拿到镜像直接运行,根本不用关心底层是什么系统、缺什么库。

1.2 Docker 的核心价值

除了环境一致性,Docker 还有两个非常实际的好处。

第一是资源隔离。一个容器就像一台轻量级的虚拟机,但它不像虚拟机那样需要模拟整个硬件,而是直接和宿主机共享内核,所以启动速度极快,资源占用也低。同一台服务器上,你可以同时跑几个完全不同的应用,它们互不干扰。某一个容器挂掉了,不影响其他容器。

第二是快速部署和弹性伸缩。以前上线一个新项目,要装环境、配依赖、启动服务,少说几个小时。现在镜像打好了,几秒钟就能拉起一个一样的容器实例。流量大的时候,同一台机器上多跑几个容器就是扩容,用完就删。这种弹性的便利性,在没有容器技术之前是完全不可想象的。

提示:很多初学者会把 Docker 当作虚拟机的一个替代品,但实际上它们的定位不同。虚拟机虚拟的是硬件层,而 Docker 虚拟的是操作系统用户态。不懂底层原理可以先跑起来,后面自然会理解。

这个系列我会一步步带着你从零开始,把概念、原理、命令、实战全部过一遍。这一篇先解决认知和基础环境的问题。

2. Docker 的三个核心概念:镜像、容器、仓库

上手之前,有三个概念必须先理清楚。镜像、容器、仓库,这三者的关系可以拿编程里的“类、实例、代码库”来类比。

2.1 镜像:打包好的模板

镜像是 Docker 的核心,它是一个只读的模板,里面包含了你运行一个应用需要的全部文件。你可以理解成一台电脑的“系统安装盘”,里面已经装好了操作系统、驱动、软件,拿过来直接安装就能用。

镜像是分层存储的,这一点很重要。构建一个新的镜像时,每一行指令都会生成一个只读图层。多个镜像可以共享相同的底层图层,比如所有基于某个基础镜像构建的应用,都会复用同一个基础层。这样做的好处是节省磁盘空间,拉取镜像的时候,如果底层已经存在,只需要下载增量部分。

拿实际项目举例,你要部署一个 Python Web 应用,镜像里会包含一个操作系统发行版(比如精简版 Linux)、Python 运行时、你的代码、以及用到的第三方库。别人拿到这个镜像,不需要安装 Python,也不需要装依赖,直接就能运行。

2.2 容器:运行中的实例

容器是镜像运行时的实例。镜像是只读文件,容器则是在镜像之上加了一层可写层。你启动一个镜像,Docker 就会在镜像的只读层上创建一个可写层,这就是容器。

打个比方,镜像是做蛋糕的模具,容器是用这个模具做出来的一个个蛋糕。同一个模具可以做无数个蛋糕,同一个镜像也可以启动多个容器。每个容器之间是相互隔离的,有自己的文件系统、网络、进程空间。容器的启动速度非常快,因为本质上是宿主机上的一个进程,只是利用内核的命名空间和 Cgroups 机制做了资源隔离。

容器里的数据是易失的。一旦容器删除,容器里新增的所有数据都没了。所以真正的数据要挂载到宿主机或者使用数据卷来持久化,这个后面会单独讲。

2.3 仓库:存放镜像的地方

仓库是集中存储和分发镜像的服务器。最常用的公共仓库是 Docker Hub,你拉取的大部分官方镜像都来自那里,比如官方的基础镜像、常用的数据库和服务镜像。

仓库的命名规则需要注意一下,完整格式一般是“命名空间/仓库名:标签”。比如nginx:1.25,nginx是仓库名,1.25是标签,代表具体的版本。如果不写标签,默认拉取latest最新版本。在企业里,你可能会搭建私有的镜像仓库,用来存放自己构建的镜像,供团队内部使用。

注意事项:生产环境中强烈不建议使用latest标签。原因很简单,今天拉下来的latest和三个月后拉下来的latest可能是完全不同的版本,一旦上游更新了导致不兼容,你根本说不清楚问题出在哪里。给镜像打上明确的版本号,是规范化运维的第一步。

这三个概念是 Docker 的基石。看着简单,但是后面所有的操作都离不开它们。理解不深没关系,先把这里记下来,实际操作中你会慢慢建立起直观的感觉。

3. 从安装到第一个容器:环境准备

在你本地把 Docker 跑起来,这是系列一最核心的目标。我用的是最常见的方式,直接安装 Docker 桌面版,因为对新手最友好,自带图形界面和管理面板,能帮你直观地看到容器状态。

3.1 安装 Docker 桌面版

Docker 的安装在不同操作系统上略有差异。我以 Linux 和桌面系统分别来说。

如果你用的是 Windows 或 macOS,直接下载安装包,一路默认设置安装即可。安装完成后,可能需要重启或者退出登录,让 Docker 的系统代理和权限生效。Windows 下要确保开启了硬件虚拟化,macOS 下要求在较新的系统版本上运行。

如果你用的是 Linux(以 Ubuntu 为例),可以用官方脚本一键安装,也可以手动添加软件源来安装。我倾向于手动安装,因为可控性更强:

# 更新软件源 sudo apt update sudo apt install -y apt-transport-https ca-certificates curl software-properties-common # 添加 Docker 官方 GPG 密钥和软件源 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io # 启动并设置开机自启 sudo systemctl enable docker sudo systemctl start docker

安装完成后,用docker --version验证一下。如果能看到版本号,说明装好了。

3.2 配置镜像加速器

这一步在很多网络环境下其实是刚需,可以不加,但加了之后能明显提升拉取镜像的速度。Docker 默认从 Docker Hub 拉镜像,在某些地区速度不稳定。配置一个镜像加速服务,Docker 会从加速地址拉取,速度快很多。

在 Docker 桌面版里,打开设置,找到 Docker Engine 配置项,在 JSON 配置里加上注册服务地址:

{ "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn" ] }

不同服务商的加速地址不一样,你自己找可用的即可。配置完保存重启 Docker,用docker info能看到 Registry Mirrors 那一栏列出了你配置的地址。

提示:镜像加速器只对拉取公开镜像有效,如果涉及私有镜像,还是要走正常的认证流程。

3.3 运行第一个容器

环境就绪之后,我们做一个所有 Docker 初学者都会做的实验:运行一个 hello-world 容器。

docker run hello-world

这条命令的执行过程是这样的:

首先 Docker 会在本地查找有没有hello-world这个镜像,如果没有,它会自动从配置的仓库拉取。拉取完成之后,Docker 根据镜像创建并启动一个容器,容器执行完镜像里预设的命令,输出一段欢迎信息,然后退出。

看到类似Hello from Docker!的输出,就说明你的 Docker 环境完全正常了。

接着我们再跑一个稍微有点实际意义的容器,比如一个 nginx:

docker run -d -p 8080:80 --name my-nginx nginx:1.25

解释一下这几个参数:

  • -d:后台运行容器
  • -p 8080:80:宿主机 8080 端口映射到容器内 80 端口
  • --name my-nginx:给容器起一个名字,方便管理
  • nginx:1.25:镜像名加明确的版本号

执行完以后,打开浏览器访问http://localhost:8080,如果你能看到 nginx 的欢迎页面,第一个容器就正式跑起来了。

4. 镜像管理:拉取、查看与删除

容器跑起来了,接下来进入日常使用频率最高的部分:镜像管理。很多人学 Docker 学了半天,命令背了一堆,其实真正每天用到的就那么几条。这里我把最实用、最高频的镜像操作全部过一遍。

4.1 拉取镜像时该注意什么

拉取镜像是docker pull,但我在实际使用中总结出了几个小技巧,新手很容易踩坑。

第一个是不写标签会默认拉latest。很多教程里直接写docker pull nginx,看起来很方便,但如果你指望“最新版”适合生产使用,那就大错特错了。官方发布latest标签往往是跟随最新的主版本,这个版本可能是稳定版,也可能是刚发布的新版本,停留在不同的语义化版本上。版本之间的配置差别,很可能让你的部署脚本失效。所以要养成写标签的习惯。

第二个是注意镜像的大小。同样是 nginx,默认镜像体积在 190MB 左右,但如果拉取精简版nginx:alpine,体积能缩小到 40MB 左右。alpine是一个极简 Linux 发行版,体积小,安全性相对更好,因为攻击面小。很多镜像都提供了 alpine 变体,适合那些不需要复杂调试工具的场景。

第三个是官方镜像和社区镜像的辨识。在仓库中,不带命名空间的通常是官方镜像,例如nginx、redis、ubuntu。带命名空间的通常属于个人或组织发布,比如bitnami/nginx这种。社区镜像的质量参差不齐,使用前最好确认一下它的维护活跃度和下载量。

4.2 查看和删除镜像

查看本地镜像很简单:

docker images

这个命令会列出镜像仓库名、标签、镜像 ID、创建时间和大小。注意镜像 ID 是一个 64 位的十六进制字符串,通常只需要用前 4 位以上的字符就能唯一标识,比如你看到docker images显示abc123456789,后面操作时只需要写abc1就够了,Docker 能识别。

删除镜像用docker rmi:

docker rmi image_id

如果镜像正被一个容器使用,删除会报错。你需要先删除依赖它的容器,或者加-f强制删除。我不建议频繁使用-f,因为这样可能留下一些孤儿资源,虽然功能上没问题,但需要你去手动清理,操作多了会觉得麻烦。

批量清理没用的悬空镜像,可以用:

docker image prune

这个命令会删除所有“没有被任何容器引用”且“没有打标签”的镜像层。注意,它不会删除正在使用的镜像,所以可以放心执行。

4.3 镜像的导入导出

有些场景下,你需要把镜像从一台机器迁移到另一台机器,尤其是离线环境。这种情况下可以用:

docker save -o my-image.tar my-image:tag docker load -i my-image.tar

save导出的是镜像的完整 tar 包,load把 tar 包加载进来。另外一个容易混淆的是export和import,它们操作的是容器的文件系统,不是镜像本身。容器 export 出来的文件系统,import 进去就不再是一个标准的镜像,丢失了层信息和构建历史,所以容器导入导出的方式很少用于镜像迁移。

经验分享:生产环境做镜像迁移,永远用save/load组合,不要用export/import。后者会丢失镜像的元数据和历史层,导致后续无法基于原镜像做增量更新。

5. 容器管理:启动、停止、进入与日志

镜像管理熟练之后,真正的重头戏是容器操作。我把这套流程从创建到销毁的完整生命周期讲清楚。

5.1 容器的创建与启动

创建容器有一条必学的命令:docker run。它把“创建容器”和“启动容器”两个步骤合在一起完成了。

最基础的写法:

docker run --name my-ubuntu -it ubuntu:22.04 /bin/bash

-it是-i和-t的组合,-i表示保持标准输入打开,-t分配一个伪终端。这条命令会创建一个 Ubuntu 容器,然后以交互模式进入容器的 bash shell。在容器里敲命令跟在一台虚拟机里操作的感觉很像。

如果你需要在容器创建时不进入交互模式,而是让它在后台静静运行,那就是之前用过的-d。加不加-d,取决于这个容器是不是一个常驻服务。数据库、Web 服务这类容器通常用-d后台运行;调试类、一次性任务类的容器通常用-it交互运行。

容器停止之后,如果不删除,它依然存在于磁盘上。你随时可以用:

docker start my-ubuntu

重新启动这个“已存在但停止”的容器,而不是重新创建一个新的。这样做的好处是你上次往容器写的数据还在。

5.2 进入运行中的容器

很多时候容器在后台跑着,你需要进去查看环境、调试排错。这里有几种方式。

方式一,使用docker exec:

docker exec -it my-ubuntu /bin/bash

这条命令在目标容器里启动一个新进程,并把当前终端接入这个进程的标准输入输出。这是生产环境里最常用的调试方式。

方式二,使用docker attach:

docker attach my-ubuntu

attach直接连接到容器的主进程。区别在于,exec是“额外开了一个进程”进入容器,attach是“把自己附着在容器的 PID 1 上”。如果你用attach,一旦你从终端退出,就有可能同时把容器的主进程杀掉,这在生产环境是很危险的。所以我的习惯是:只进里面执行命令,永远不会用attach。

警告:不要在运行中的生产容器里用attach去“看一眼”,你的 Ctrl+C 或 Ctrl+D 操作很可能导致主进程收到中断信号,直接终止整个容器服务。

5.3 日志:Debug 的第一手资料

拿到一个容器,我先做的事永远是看日志。

docker logs my-nginx

这个命令会把容器主进程的 stdout 和 stderr 输出全部打印出来。加上-f可以持续跟踪最新输出,类似tail -f:

docker logs -f --tail 100 my-nginx

--tail 100表示只显示最近 100 行,这个参数在日志非常多的场景下特别有用。如果你发现一个容器启动后马上退出了,那就去看日志,基本能定位九成的问题。

5.4 容器的删除与清理

删除单个容器:

docker rm my-ubuntu

删除所有已停止的容器:

docker container prune

我建议每隔一段时间跑一次这个清理命令。开发调试过程中会遗留大量的停止容器,占着磁盘空间,还容易让人分不清哪些是真正在用的。

强制删除正在运行的容器,用docker rm -f。它会向容器主进程发送 SIGKILL 信号,直接终止它。能少用就少用,尽量先docker stop让它优雅退出,再做删除。优雅退出会给应用一个处理善后工作的机会,比如保存状态、关闭数据库连接、释放资源。

6. 端口映射与数据持久化:让容器真正可用

容器本身跑起来只是一个隔离环境,要被外部访问、要持久保存数据,就必须处理端口和存储的问题。

6.1 端口映射的两种姿势

容器内部跑着一个服务,默认情况下外部网络是访问不到的。刚才我们用-p 8080:80做了端口映射,把宿主机的 8080 端口转发到容器的 80 端口。

这个映射方式有一个明显的问题:宿主机 8080 的端口被占用了,就没法再起第二个容器映射同一个端口。如果你要部署多个 nginx 实例,就得手动指定不同的宿主机端口。我在本机调试时经常把端口改成随机的,避免冲突。

另一种方式是-P,大写字母 P,它会自动为容器内部所有暴露出来的端口分配一个随机的高位端口。执行docker ps就能看到端口映射情况。这种方式适合快速测试,但不适合长期稳定的服务。

端口映射的生效时机是在创建容器时,端口映射一旦设定,之后修改不了。所以你如果需要改映射,只能删除容器重新创建。这一点不少新手会踩坑,在容器里面把服务端口改了,以为宿主机上的映射端口也会跟着变,其实不会。

6.2 数据持久化:数据卷与绑定挂载

默认情况下,容器内文件系统在容器删除后会被清空。数据的持久化,靠的是数据卷和绑定挂载。

数据卷是 Docker 管理的特殊目录,它的生命周期不受容器生命周期约束:

docker run -d --name mysql-server -v mysql-data:/var/lib/mysql mysql:8.0

这里-v mysql-data:/var/lib/mysql把名为mysql-data的卷挂载到容器的 MySQL 数据目录。你删除容器,卷还在,数据不会丢。下次新建容器,挂载同名卷,数据就自动回来了。

查看卷列表:

docker volume ls

绑定挂载更直白,直接把宿主机的某个目录挂到容器里:

docker run -d --name web-nginx -v /home/user/html:/usr/share/nginx/html nginx:1.25

修改宿主机/home/user/html里的文件,容器内的 nginx 网站内容会立即更新。这在开发调试时特别方便,改代码不用重新构建镜像。

关于数据管理,有一条铁律:数据库的数据目录和数据卷必须挂载到宿主机。放任数据库数据存在容器可写层内,容器一删,整个库的数据就消失了。

6.3 容器间的通信

容器之间需要相互访问时,用 IP 去访问是不靠谱的,因为容器的 IP 在重启后会变化。正确的方式是创建自定义网络:

docker network create my-net docker run -d --name mysql-server --network my-net mysql:8.0 docker run -d --name backend --network my-net my-app-image:latest

在同一个自定义网络里的容器,可以直接通过容器名进行 DNS 解析。比如在backend容器里,数据库的连接地址直接写mysql-server:3306就行,而不用去关心 MySQL 容器的实际 IP。这是多容器应用部署的基础。上一篇暂时用不到,但你要有这个认知:容器编排的核心,就是通过 Docker 自定义网络实现服务发现。

7. 常用命令速查与排查技巧

到最后,我把这几天的实操经验浓缩成一张常用的命令表,以及几个最常见的报错场景。

7.1 高频命令速查表

用途命令示例说明
拉取镜像docker pull nginx:1.25指定 tag 拉取
查看镜像列表docker images展示本地所有镜像
删除镜像docker rmi image_id删除未被容器引用的镜像
创建并启动容器docker run -d -p 8080:80 nginx:1.25后台运行并做端口映射
列出运行中容器docker ps-a 可查看已停止的容器
停止容器docker stop my-nginx优雅停止,发送 SIGTERM
强制停止容器docker kill my-nginx直接发 SIGKILL
删除容器docker rm my-nginx删除已停止的容器
进入容器docker exec -it my-nginx /bin/bash在容器内启动 shell
查看日志docker logs -f --tail 100 my-nginx跟踪最新日志
查看资源占用docker stats实时显示 CPU/内存/网络
查看容器配置与网络docker inspect my-nginx输出 JSON 格式的详情
清理全部未使用资源docker system prune -a慎用,会清掉很多镜像

docker stats是我特别推荐你上手立刻试一下的命令。它能实时看到每个容器的 CPU 占用、内存占用、网络 IO,排查资源瓶颈的第一利器。

7.2 报错排查思路

这一块用实战经验说话。下面是初学者最多遇到的三个问题的排查思路。

第一个是Failed to connect to the Docker daemon。出现这个错误,说明你的客户端连不上 Docker 引擎。桌面版需要确认是否正常启动,Linux 下需要确认 docker 服务是否在运行,执行systemctl status docker查看。如果没问题,再看当前用户有没有加入 docker 用户组。没加的话,每次都要用 sudo,否则就会报这个错。

第二个是port is already allocated。端口被占用,说明宿主机上这个端口已经被某个容器占了。用docker ps看看谁占着,要么停止那个容器,要么换一个宿主机端口。

第三个是No space left on device。这是容器日志或镜像堆积导致的磁盘溢满。先执行docker system df查看磁盘使用情况,然后清理不再需要的容器和镜像。如果是特定容器日志太大,可以通过给 Docker 配置日志轮转去解决,而不是手动删除日志文件,因为容器还在写着这个文件。

7.3 容器日志体积过大

很多人遇到磁盘满了,第一反应是清理镜像,忽略了日志。一个长期不重启、日志量很大的容器,几天就能产生几个 GB 的日志文件。解决办法是在启动容器时限制日志文件大小:

docker run -d --log-opt max-size=10m --log-opt max-file=3 nginx:1.25

这样每个日志文件最多 10MB,最多保留 3 份,循环覆盖。生产环境建议所有容器都加上这个配置,成本低,收益显著。

7.4 一个常识性建议:分清容器和镜像的命名规范

容器和仓库/镜像的命名规则有一定区别。容器名可以随便取,但只能用字母、数字、下划线、点号和连字符;镜像的完整标识是“仓库名:标签”。刚开始学的时候容易把容器名和镜像名混在一起,比如运行docker rm nginx:1.25,然后就会得到invalid reference的错误。记住:容器操作用的是容器 ID 或容器名,镜像操作用的是镜像 ID 或镜像名,两者不要混用。

8. 构建自己的第一个镜像:用 Dockerfile 从零开始

系列的最后一篇,我们来做一个“真正属于自己的”镜像。前面你一直在拉别人的镜像,现在开始亲手写一个 Dockerfile。

8.1 结构解析

Dockerfile 是一个包含了一系列指令的文本文件,Docker 会按照文件里的指令一步一步构建出镜像。

举一个最简单的 Python 应用示例:

# 指定基础镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 把本地的 requirements.txt 复制进镜像 COPY requirements.txt . # 安装依赖 RUN pip install --no-cache-dir -r requirements.txt # 把当前目录的代码复制进镜像 COPY . . # 声明容器运行时监听的端口 EXPOSE 8000 # 容器启动时执行的命令 CMD ["python", "app.py"]

这段代码涵盖了几个最核心的指令:

  • FROM:一切的基础,必须放在第一行。你的镜像基于哪个镜像构建。
  • WORKDIR:后续指令的工作目录。不设置的话,默认在根目录,文件存放会乱。
  • COPY:把宿主机上的文件复制到镜像里。上面把requirements.txt复制过去,是为了在安装依赖时能用到它。
  • RUN:在镜像构建过程中执行的命令。不推荐在 README 里写的命令行里装依赖,而是用RUN固化进镜像。
  • EXPOSE:声明这个镜像里服务要用的端口。注意它只是声明,并不能真正让宿主机访问到,还是要配合-p来实现端口映射。
  • CMD:容器启动时执行的默认命令。一个 Dockerfile 只能有一个CMD生效,多个的话只有最后一个生效。

8.2 构建镜像

准备好 Dockerfile 和项目代码后,在项目根目录执行:

docker build -t my-python-app:v0.1 .

最后这个.是构建上下文,Docker 会把当前目录的文件打包发给构建进程。所以构建期间,目录里大文件、敏感文件要特别注意,尽量放在.dockerignore文件里,就像 Git 的.gitignore一样:

__pycache__ *.pyc .env .git

构建过程会逐行执行 Dockerfile 的指令,每一步生成一层镜像图层。如果某一步出错,Docker 会给出错误提示。

构建完成,运行它:

docker run -d -p 8000:8000 --name my-python-app my-python-app:v0.1

8.3 关于镜像层缓存的实践心得

Docker 构建过程中有层缓存机制。如果某个指令没有发生变化,Docker 会直接使用缓存层,不重新执行。利用好这一特性,能大幅缩短构建时间。

拿上面的 Dockerfile 来说,我把COPY requirements.txt放到COPY . .前面,就是典型的缓存优化。因为requirements.txt很少变动,安装依赖的层可以长期命中缓存,只有当你改了依赖时才会重新安装依赖。如果把代码先复制进去,那么每次代码变动,安装依赖的步骤都会因为代码发生变化而失效,整个构建过程就非常慢。

注意事项:写 Dockerfile 时,指令顺序会影响构建效率,乃至影响镜像的复用率。把“频率低变动”的步骤放前面,“频繁变化”的步骤放后面,是 Dockerfile 优化的核心原则。

8.4 多阶段构建的概念

如果你构建一个编译型应用的镜像,比如 Go 或者 Java,镜像里会多出很多编译工具和临时文件,体积非常大。多阶段构建可以用一个临时镜像完成编译,最后只把编译产物复制到最终镜像:

# 阶段一:编译 FROM golang:1.22 AS builder WORKDIR /app COPY . . RUN go build -o my-app # 阶段二:只拷贝产物 FROM alpine:latest WORKDIR /app COPY --from=builder /app/my-app . CMD ["./my-app"]

最终生成的镜像只包含一个精简的操作系统和一个二进制文件,体积从几百 MB 降到几十 MB。生产环境部署这种镜像,不仅拉取快,安全风险也低。

9. 常见问题与避坑实录

这一路写下来,我发现学 Docker 的阻碍往往不是命令本身,而是那些“没被说明书写清楚”的坑。这里集中整理一下我在实践中遇到频率最高的问题和处理方案。

现象原因解决办法
Got permission denied while trying to connect to the Docker daemon当前用户不在 docker 用户组sudo usermod -aG docker $USER,重新登录
容器启动后立即退出前端进程没保持,容器没有任务可执行让进程在前台运行(例如写CMD ["nginx", "-g", "daemon off;"]或tail -f /dev/null)
port is already allocated宿主机端口已被占用换端口或停掉占用端口的容器
容器内时间不对时区默认 UTC挂载宿主机时区文件或设置TZ环境变量
容器内文件权限不对容器用户非宿主机用户创建容器时指定--user,或在 Dockerfile 设置用户

容器启动后立即退出,这个是最常见的“假新手问题”。它往往不是 Bug,而是你没理解容器进程模型。Docker 容器只有在前台有一个活跃进程时才会保持运行。如果你启动一个普通的 Debian 容器而不运行任何常驻进程,容器会立刻退出。解决办法就是让主进程在前台运行,日志输出到标准输出。理解这一点后,绝大多数“容器起不来”的问题都能自我排查了。

时区问题我也踩过不少次。默认情况下,容器内时区是 UTC,你打日志或者定时任务,会发现时间比本地快了 8 个小时。比较干净的做法是在运行容器时加一个环境变量:

docker run -e TZ=Asia/Shanghai my-image

如果基础镜像里没有时区数据,可能还需要在 Dockerfile 里安装tzdata。遇到时间不对,第一时间先查这个。

权限问题是生产环境绕不开的坑。容器内的进程默认以 root 身份运行,安全性不好。加上容器内创建的文件,在宿主机上会显示为 root 所有,这会让宿主机上的普通用户没办法直接删除或编辑。建议在 Dockerfile 里用USER指令切换到非 root 用户运行应用,而不是直接裸奔。

写在最后的一些心得

这一篇的内容,基本上把我从“零基础跑起第一个容器”到“写自己的 Dockerfile 构建镜像”这个过程完整过了一遍。作为系列的第一篇,我没有刻意去追求覆盖 Docker 的全部功能,像编排、单机多容器应用、仓库搭建这些内容,都留到后续的篇幅里展开。

我在实际学习的过程中最深的体会是,Docker 的命令并不难记,关键是要建立一个“镜像-容器-仓库”三角关系的心智模型。只要脑子里有了这个模型,遇到任何报错,你都能判断出问题出在哪一侧,也知道该去哪里查。很多初学者学 Docker 学着学着放弃了,就是因为一开始就陷入复杂概念里,比如网络模式、存储驱动、命名空间,这些在初期根本不重要。把一个 Web 服务容器跑起来,看到页面可以访问,那种正向反馈才是坚持下去的动力。

另外想分享一个小技巧:不要只看别人的教程,一定要自己动手操作。把教程里的命令一个个敲一遍,哪怕敲错了,报错了,也比纯看十遍有用。遇到报错不要慌,看日志,记下关键词去搜索,这是每个 Dcoker 使用者都必须掌握的自我解救能力。把这篇里的例子都实践一遍,你就已经具备了在生产环境里使用 Docker 的基本能力。下一期我会讲容器间的协作和数据持久化的进阶用法,到时候见。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 17:21:54

三电平NPC逆变器SVPWM仿真:扇区划分、小矢量与中点电位平衡

1. 三电平NPC逆变器为什么值得上矢量控制? 做电机驱动做到三相异步电机或永磁同步电机的中大功率段,绕不开三电平NPC逆变器这个话题。和两电平结构相比,三电平NPC拓扑在同样直流母线电压下,把IGBT的集射极耐压需求从整条母线电压降…

作者头像 李华
网站建设 2026/10/20 17:21:19

大模型新版本前瞻:架构演进、推理成本与Agent应用的关键转折

2026年4月本来还在行程表上被标记为一个普通的产品迭代节点,但随着各方信息的逐渐清晰,这个时间点在大模型圈子里已经被赋予了完全不同的含义。原因很简单,那个在2025年把推理成本打下来、把开源模型能力天花板抬上去的系列,即将迎…

作者头像 李华
网站建设 2026/10/20 17:20:51

离线部署Cloudera Manager:本地源搭建与CM Server安装实战

简介:这份资源是面向大数据运维与Hadoop集群管理学习者的Cloudera Manager 5.14.2离线安装包,专为CentOS 7环境准备,解决在无外网或受限网络下部署CM管理平台的难题。压缩包共含18362个文件,约793.9MB,以py、pyo、pyc等…

作者头像 李华
网站建设 2026/10/20 17:20:25

C++一维数组从入门到避坑:声明、初始化、遍历与排序

连载走到第10篇,前面的变量、判断、循环都属于“单打独斗”——变量管一个数,循环管反复操作,可一旦遇到“一批”数据,这两个工具立刻就不够用了。班里60个人的成绩、一个月30天的气温、购物车里几十件商品的价格,你总…

作者头像 李华
网站建设 2026/10/20 17:20:24

JT809联调工具V1.2.5实战:从登录到车辆动态信息交换的避坑指南

简介:本资源为JT809联调工具V1.2.5,面向福建省部标809联调场景下的下级平台开发与测试人员,用于验证上行数据报文解析的正确性。工具可直观查看省平台接收到的数据解析情况,便于联调人员比对解析结果与下级平台上报数据是否一致&a…

作者头像 李华
网站建设 2026/10/20 17:20:24

基于STM32G474的400W微型逆变器设计与并网控制实现

想折腾一块能真正并网发电的小功率逆变器,又不想在驱动、保护、控制算法上走太多弯路的话,把目光放在“STM32G474 400W微型逆变器”这套组合上,是个非常务实的切入点。这套方案覆盖了从光伏组件直流输入到电网工频交流输出的完整链路&#x…

作者头像 李华