Docker新手必看:8个核心操作、6大高频坑,一次讲透容器本质
2026/9/9 8:15:39 网站建设 项目流程

你说你是Docker新手,拉个镜像慢到怀疑人生,docker run 参数抄了又抄还是记不住,好不容易把容器跑起来,第二天发现数据全没了,瞬间想砸电脑。别急,这些问题我全踩过,今天这篇就把Docker容器最核心的操作逻辑一次讲透,没有废话,全是实操。

这个标题看着可能有点密集——8个核心操作、6大高频坑、3个核心逻辑,但核心就一件事:让你从"只会跟着教程敲命令"变成"遇到问题能自己判断怎么处理"。我接触Docker这么多年,带过不少新人,发现最容易出问题的往往不是命令本身,而是对容器本质的理解偏差——不知道它是个什么东西,出问题的时候自然不知道往哪个方向排查。所以我会先讲透底层逻辑,再带你过一遍必须会的操作,最后把高频坑逐个拆开,保证你看完能少走一大半弯路。

1. 三个核心逻辑,先搞懂容器到底是个啥

1.1 容器不是虚拟机,它就是一个进程

很多新手第一次接触Docker,脑子里默认把它当成了轻量级虚拟机。这个误解非常要命,因为一旦用"虚拟机"的思维去理解容器,后面遇到的所有问题都会觉得莫名其妙。

举一个最简单的例子:你用docker run -d nginx启动了一个容器,然后执行docker ps看不到它。如果你把容器理解成虚拟机,你的第一反应是"机器没开起来";但如果你理解成进程,你的第一反应是"进程挂了",然后再去docker logs查看问题出在哪。这个思路的转变,决定了你能不能快速定位问题。

实际上的原理是这样的:Docker容器共享宿主机内核,通过Linux内核的namespace做资源隔离,通过cgroup做资源限制。容器里的进程在宿主机上就是一个个普通进程,只是被"圈"在了一个隔离环境里罢了。

注意:这个认知直接决定你的排查思路,容器应用挂了第一件事就是看日志,不是去"重启系统"。

1.2 镜像是一层一层的,文件系统变更靠"写时复制"

Docker镜像为什么能复用?为什么拉一个几百MB的镜像有时只需要几十秒?核心在于镜像的分层存储机制。

Docker镜像是由多个只读层堆叠而成的,每一层对应Dockerfile里的一条指令。你拉取一个镜像时,Docker会检查本地已有的层,只下载缺失的层,这就是为什么如果你的本机已经有其他镜像共享了一些基础层,拉新镜像会快很多。

当一个容器运行后,Docker会在镜像层之上挂载一个可写层。容器中对文件系统的任何修改,包括新增、删除、修改文件,都发生在这个可写层。用"写时复制"的机制,即使多个容器共享同一个镜像或底层只读层,每个容器内部的修改也是相互隔离的。

这就是为什么容器删除后数据会丢——因为数据写在可写层里,容器删了,可写层也就没了。理解了这个原理,你就会明白为什么官方一直强调:数据必须挂载到数据卷或宿主机目录里。

1.3 容器是"一次性"的,可写层不是保险箱

顺着上面的逻辑再往下走:容器的生命周期应该是短暂的、可替换的。你要做的是随时可以删掉一个容器,再用同一个镜像重新创建一个,而业务数据丝毫不受影响。

我之前见过一个小伙子在容器里部署了一个MySQL,用了一段时间之后数据越来越多,某天服务器重启后容器没自动启动,他一着急,直接去容器内部检查数据,结果发现数据还在,就没放在心上。过了几天他手动清理镜像的时候,顺手把那个容器给删了,等他发现数据库连不上的时候,数据已经彻底没了。

这个场景太典型了。你得把容器理解成一次性的沙滩城堡,而数据卷是你放在岸边的保险柜。城堡被海浪冲走了没关系,保险柜里的东西不能丢。

2. 八个核心操作,从零开始跑通容器

2.1 拉取镜像:先搞明白你下载的是啥

镜像是什么?你可以理解为应用运行所需的"全部材料打包"。nginx镜像里包含了nginx程序、配置文件、依赖库等;MySQL镜像里包含了MySQL程序、初始化脚本等。

拉取命令很简单:

docker pull nginx

默认会从Docker Hub拉取最新版本(latest),也可以指定版本和仓库地址:

docker pull mysql:8.0 docker pull registry.cn-hangzhou.aliyuncs.com/library/mysql:8.0

这里多提一句,国内拉取Docker Hub镜像经常很慢,甚至超时失败。解决方案有两种:一是配置镜像加速器(在Docker Desktop的设置里可以配置registry-mirrors),二是在拉取命令里直接指定国内镜像源地址。配置好之后实测下载速度能提升好几倍。

2.2 运行容器:docker run 是全部操作的入口

docker run是最常用的命令,但新手往往被它的参数搞晕。我拆解一下最常用的组合:

docker run -d -p 8080:80 --name web-server nginx
  • -d:后台运行,不加这个参数,你的终端就会被nginx的前台日志刷屏,Ctrl+C一按容器就停了。
  • -p 8080:80:端口映射,格式是"宿主机端口:容器内端口"。这里把宿主机的8080端口映射到容器的80端口,这样你访问http://localhost:8080就能访问到容器内部的nginx。
  • --name:给容器起个名字,不加的话Docker会随机生成一个类似"angry_gates"的名字。

启动MySQL时一般还要加环境变量和挂载数据卷,这就是比较完整的形态:

docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=yourpassword \ -e MYSQL_DATABASE=mydb \ -v mysql-data:/var/lib/mysql \ mysql:8.0

-e是设置环境变量,MySQL镜像需要通过MYSQL_ROOT_PASSWORD指定root密码,不然容器会直接启动失败。-v是挂载数据卷,我们后面专门说。新手最怕看到又长又乱的命令,我的建议是把常用参数写成一个小抄贴在手边,用几次就熟练了。

2.3 查看容器状态:docker ps 的两种用法

刚跑完容器,第一件事就是确认它有没有正常运行:

docker ps

这个命令只显示正在运行的容器。如果容器启动后因为各种原因退出了,docker ps列表里看不到它,你可能会以为容器不存在。这时候要用:

docker ps -a

-a参数显示所有容器(包括已退出的)。每个容器会有一个唯一的CONTAINER ID,后续很多操作都可以直接使用这个ID的前几位来定位容器,比如docker logs abc123

2.4 查看日志:排查问题的第一把钥匙

容器跑起来了但访问不了,或者没几秒就退出了,第一个排查手段永远是看日志:

docker logs web-server

如果日志太多,可以只看最后100行并且持续跟踪:

docker logs --tail 100 -f web-server

--tail是只看末尾多少行,-f是持续输出新产生的日志,类似tail -f的效果。排查问题的时候这招最实用。很多新手容器一启动就退出,不查日志就盲目删了重跑,这完全是碰运气。八成的情况,日志里已经写清楚了失败原因——比如端口被占用、环境变量缺失、配置文件写错等等。

2.5 进入容器:在容器内部执行命令

需要进入容器内部调试的时候,用这个:

docker exec -it web-server bash

-it是两个参数:-i保持标准输入打开,-t分配一个伪终端。不这样写,交互式操作没法玩。如果容器里没有bash(比如一些精简版alpine镜像),可以试试sh

docker exec -it web-server sh

如果你只是想执行单条命令,不用进入容器:

docker exec web-server ls /usr/share/nginx/html

这个在排查问题时非常常用,不用反复进出容器。

2.6 停止、启动、删除容器:生命周期管理

容器用完了想停下来,或者修改了配置想重启,常用命令如下:

docker stop web-server # 停止容器,会发出SIGTERM信号给它留出优雅退出的时间 docker start web-server # 启动一个已停止的容器 docker restart web-server # 重启容器 docker rm web-server # 删除容器(需要先停止)

如果要删除所有已停止的容器,记住这个:

docker container prune

它会一次性清除所有处于停止状态的容器,释放命名和存储资源。如果明确要强制删除正在运行的容器,加-f参数。

2.7 端口映射与数据卷:容器外部访问入口和数据存放点

端口映射在2.2里已经提过,这里强调一个点:端口映射是"单向"的,你可以在启动容器时通过-p参数指定映射,但容器运行中不能追加新的映射。如果想改端口,只能把容器删了重新run

数据卷是Docker里最容易被忽视、后果最严重的一环。数据卷就是挂载到容器里的一个独立存储空间,它可以在宿主机上,也可以由Docker管理,目的是让数据独立于容器生命周期。

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

上面这个mysql-data由Docker管理,数据存在宿主机的/var/lib/docker/volumes/目录下。另一种方式是直接挂载宿主机目录:

-v /opt/mysql/data:/var/lib/mysql

这种方式更直观,你可以直接在宿主机上看到和备份数据。具体用哪种,看你的运维习惯,我建议生产环境直接用宿主机目录的方式,方便备份和迁移。

2.8 清理镜像和缓存:给磁盘瘦身

长期使用Docker,磁盘空间会被镜像、容器、数据卷、构建缓存占满。我清理的思路是,先删容器、再删镜像,最后清没用的数据卷:

docker rm $(docker ps -aq) # 删除所有容器 docker rmi <镜像ID或名称> # 删除指定镜像 docker image prune -a # 删除所有没有被容器使用的镜像 docker volume prune # 删除未被任何容器挂载的数据卷 docker system df # 查看磁盘占用情况

docker system df会列出镜像、容器、数据卷、构建缓存各自占了多少空间,非常直观。

3. 六大高频坑,每一个都是新手踩出来的

3.1 镜像拉取失败或超时

这个问题在国内几乎每个人都碰到过。官方镜像源在国外,网络连接不稳定或速度极慢,导致拉取超时、中断,甚至出现manifest unknown之类的错误。

解决办法第一选择是配置镜像加速器。打开 Docker Desktop 的 Settings → Docker Engine,在配置里加上registry-mirrors地址,然后重启Docker。Linux环境下修改(或新建)/etc/docker/daemon.json

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://hub-mirror.c.163.com" ] }

然后重启Docker服务:

sudo systemctl daemon-reload sudo systemctl restart docker

配置好之后拉镜像速度是肉眼可见的提升。如果还是慢,就直接从国内镜像仓库拉取,比如阿里云容器镜像服务,docker pull registry.cn-hangzhou.aliyuncs.com/...

3.2 容器一启动就退出(秒退)

这是新手最常遇到的问题,没有之一。docker ps -a看到容器显示EXITED,第一反应往往是"再跑一次",结果依然秒退。

绝大多数的原因是:镜像里定义的执行命令运行失败。常见场景有:

  • 指定了错误的环境变量(比如MySQL的密码没设)
  • 依赖的端口已被占用
  • 配置文件写错,应用启动时直接报错退出

终极解法就三个字:看日志。

docker logs <容器ID或名称>

日志会明确告诉你失败的原因。比如MySQL缺少MYSQL_ROOT_PASSWORD,yesql配置的端口被占用了等等。看完日志再动手,效果立竿见影。另外,有些容器是"前后台不分"的,如果镜像里的进程本身是后台守护进程(比如某些脚本里写了daemon off),容器会认为主进程已经退出了,所以也秒退。这个问题常见于直接用系统包管理器装nginx之类的软件然后手动启动的镜像。

3.3 端口被占用了怎么办

启动容器报错Bind for 0.0.0.0:8080 failed: port is already allocated,这是端口冲突。可能的情况是宿主机上有其他进程占了这个端口,也可能是另一个容器已经映射了这个端口。

排查方法:

lsof -i :8080 # 或者 netstat -tunlp | grep 8080

看看是谁占用了8080端口,然后改成其他端口重新启动容器。注意,改端口可不是改条命令那么简单,你得先删掉原来的容器:

docker rm -f web-server docker run -d -p 8081:80 --name web-server nginx

3.4 容器删除后数据全没了

这个问题在前面已经反复强调过了,根因就是没挂载数据卷。运行MySQL、Redis、MongoDB这类有状态服务时,数据目录一定要挂载到宿主机:

  • MySQL数据目录:/var/lib/mysql
  • Redis持久化文件:/data
  • MongoDB数据目录:/data/db
  • PostgreSQL数据目录:/var/lib/postgresql/data

如果你用命名数据卷,即使容器删了,只要卷还在,数据就还在。你完全可以放心重建容器,重新挂载同一个卷,数据立马回来。我用一句话总结:操作数据库容器之前,先检查启动命令里有没有-v,没有就先别干。

3.5 容器时区不是北京时间

容器默认时区通常是UTC,所以日志时间比北京时间慢8小时。排查日志的时候,时间对不上很误导人。

最简单的解决方式是启动时挂载宿主机的时区配置:

docker run -d \ --name web-server \ -e TZ=Asia/Shanghai \ nginx

很多官方镜像支持TZ环境变量,启动时指定一下就行。如果镜像不支持或者你已经启动了,就进容器改:

docker exec -it web-server bash ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

改完重启容器生效。不过每次重建容器都要重新设置,所以我更推荐在docker run时直接加-e TZ=Asia/Shanghai,或者挂载时区文件:

-v /etc/timezone:/etc/timezone:ro

3.6 容器里用root跑服务,安全隐患很大

默认情况下,容器里的进程是以root身份运行的,这非常危险。一旦容器被攻破,攻击者获得的就是宿主机上的root权限。即使有namespace隔离,风险依然很大。

解决办法是启动容器时指定运行用户:

docker run -d --user 1000:1000 nginx

或者用-u参数指定用户名。更规范的做法是:如果你自己写Dockerfile,在构建镜像时就创建一个普通用户,并用USER指令切换过去。

需要注意一个前提,容器里的用户必须对挂载的目录有写权限。如果数据卷目录是root创建的,普通用户进不去,应用就会启动失败,这个在调试时最容易迷惑人。

4. 进阶但不难的补充:网络、Compose与日常排障

4.1 容器网络:别默认把端口暴露到公网

-p 8080:80会让端口直接暴露在宿主机的所有网卡上,包括公网。如果你只是本机调试,不想让外部访问,可以绑定回环地址:

-p 127.0.0.1:8080:80

多个容器之间通信,推荐创建自定义网络:

docker network create my-net docker run -d --network my-net --name mysql8 mysql:8.0 docker run -d --network my-net --name web-server nginx

使用自定义网络后,容器之间可以用容器名直接访问,比如web容器里连接MySQL,直接写mysql8:3306就行,IP地址不用记。

4.2 docker compose:多个容器一条命令跑起来

如果项目依赖了MySQL、Redis、应用本身等多个容器,再用docker run一个个启动就太痛苦了。docker compose可以把所有服务定义在docker-compose.yml文件里,一条命令全部拉起:

version: '3' services: mysql: image: mysql:8.0 container_name: mysql8 environment: MYSQL_ROOT_PASSWORD: rootpassword ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql app: build: . container_name: web-app depends_on: - mysql ports: - "8080:80" volumes: mysql-data:

然后运行:

docker compose up -d

这个文件把镜像、端口、数据卷、环境变量都声明清楚了,下次服务器迁移或者换机器部署,拷贝文件直接跑,比手动翻历史命令找docker run靠谱一万倍。我强烈建议,凡是超过一个容器的场景,直接用compose。

4.3 常见排查命令速查

把最有用的排查命令整理成一张表,建议收藏:

场景命令说明
查看运行中容器docker ps列出所有运行中的容器
查看所有容器docker ps -a包含已退出容器
查看容器日志docker logs -f <容器>跟踪日志输出
进入容器docker exec -it <容器> bash容器内执行交互命令
查看镜像docker images列出本地镜像
查看端口占用lsof -i :8080检查端口占用情况
查看资源占用docker stats实时看容器CPU、内存
查看容器信息docker inspect <容器>包括IP、挂载卷、环境变量等

docker inspect是排查问题的神器,比如确认容器实际的挂载路径对不对、IP是多少、环境变量有没有生效,全在这里面。

5. 实操总结与个人体会

如果说这么多内容里只能留下一个观念给你,我最想强调的是:把容器当成一只"一次性手套",而不是一台"小电脑"。它随手可取、随时可换,你需要保护的是"手套"之外的数据和配置。任何需要长期保留的东西,都必须挂载到宿主机目录或数据卷里;任何需要改的配置,都应该写进docker run参数或者compose文件里,而不是手动进入容器"改完就这样用"。

我自己早期因为没挂载数据卷把MySQL数据弄丢过一次,从那以后定了个死规矩:跑有状态服务容器,先想清楚数据落在哪里;跑测试环境容器,一律标注清理日期,过时就容器和镜像一起清掉。

如果你看完想动手试试,我建议的路线是:先拉一个nginx,跑起来,修改默认页面,然后把容器删了再重建一次,体会一下"增减容器对配置和数据无感"的爽快。接下来可以试着跑一个Redis,设置密码、挂载数据卷,再用compose把Redis和一个小应用一起拉起来。走完这三四个实验,你基本就具备日常使用Docker的底气了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询