Docker入门实战:从环境搭建到容器编排的避坑指南
2026/9/24 18:48:35 网站建设 项目流程

实习第一天,带我的同事丢给我一台机器,说“先熟悉一下 Docker,把测试环境搭起来”。当时我连 Docker 和虚拟机的区别都说不清,硬着头皮装环境,光是 Docker Desktop 就折腾了一下午。后来泡在文档、社区帖子和一次次“删了重来”里,总算把每天要用的 Docker 常用操作摸透了。回头看,新人在实习阶段真正用到的操作并不复杂,无非是环境安装、镜像拉取、容器生命周期、数据卷和网络、再加一个 compose 编排。这篇文章就围绕这几条线展开,所有命令我都按“自己在工位上会怎么敲”来写,顺便把容易踩的坑也标出来。

1. 装好 Docker 再谈其他:Windows 和 Linux 环境实战

Docker 本身是一套客户端加服务端的架构,你的日常工作大部分是在和docker命令打交道。但在敲第一条命令之前,得先把环境搞对。实习阶段最常见的两种场景,一个是自己电脑上装 Docker Desktop 做开发调试,一个是公司服务器上装 Docker Engine 跑测试环境,两边我都折腾过不少。

1.1 先解决 Windows 上的 Docker Desktop 启动问题

在 Windows 上装 Docker Desktop,很多人以为下载安装包点下一步就行,结果装完双击图标一直转圈,最后弹一个virtualization support not detected。这个问题的根源不是 Docker 安装包出了问题,而是 Windows 下跑 Linux 容器需要虚拟化支持。

安装之前先做两件事:第一,打开“启用或关闭 Windows 功能”,确认“适用于 Linux 的 Windows 子系统”和“虚拟机平台”这两项已经勾选;第二,打开任务管理器,切到“性能”标签页,看 CPU 那一栏的“虚拟化”是不是“已启用”。如果显示未启用,就需要重启进 BIOS,找到 Intel Virtualization Technology 或 AMD SVM Mode,把它设置为 Enabled。公司电脑 BIOS 如果有密码锁,就不要自己硬搞了,直接找 IT 帮忙,这不是靠软件能绕过的。

Docker Desktop 现在默认用 WSL2 作为后端,相比老牌 Hyper-V 方案,WSL2 启动更快、内存占用更灵活,和 Windows 文件系统交互也更自然。所以装完之后最好顺手设一下默认版本:终端执行wsl --set-default-version 2,再跑一次wsl --update,把 WSL 内核更新到最新。第一次启动 Docker Desktop,会有一个让你选 Linux 容器还是 Windows 容器的提示,日常开发基本上都选 Linux 容器,Windows 容器主要面向 Windows 工作负载,别在这里选错。

如果 Docker Desktop 已经装好,启动也点了,但执行docker version一直报failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen这类错误,多半是 Desktop 自己没起来或者 WSL 卡住了。不要急着卸载重装,先把 Docker Desktop 退掉,然后执行wsl --shutdown,等几秒再重新打开 Docker Desktop。我碰到过好几次“Docker 连不上 API”的问题,绝大多数都是这一招解决的,真正需要重装的情况极少。

1.2 Linux 服务器下用 apt 安装 Docker Engine

到了公司服务器上,图形界面就没有了,这时候装的是 Docker Engine,不是 Desktop。实习第一天我在一台 Ubuntu 22.04 上操作,最省事的安装方式是走 Docker 官方 apt 源。

先清理旧版本,避免系统里残留不兼容的包:

sudo apt-get remove docker docker-engine docker.io containerd runc

然后安装证书相关工具,并且把 Docker 官方 GPG key 导入系统:

sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg

接着添加 apt 源文件:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

源配好后再更新索引,安装 Docker 全家桶:

sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

装完之后用sudo systemctl enable --now docker设置开机自启,sudo systemctl status docker看状态,最后docker version确认客户端和服务端都显示正常版本号。这里要说一个点:为什么服务器上不需要 Docker Desktop?因为 Desktop 是把 Docker Engine、Kubernetes、图形管理界面打包在一起的桌面软件,服务器上我们只需要 Engine 和命令行工具,open 容器、看日志都靠 ssh 加终端,图形界面反而是多余资源。

国内好多教程会建议你装完立刻改镜像源,这个思路是对的,但前提是先把 Docker 本身跑起来再改,否则 daemon 配置出错会直接导致服务起不来。另外,如果你的服务器在网络受限的内网环境,访问不了 Docker 官方源,不要瞎改源地址硬试,先问问运维同事有没有内部 apt 镜像地址或离线安装包,团队内部通常早有标准方案。

2. 镜像和容器:每天高频使用的那些 Docker 命令

环境终于通了,接下来是真正的工作。实习阶段最常见的任务,就是“把某个中间件跑起来”。比如测试环境要一个 Nginx、一个 MySQL、一个 Redis,这时候你用到的就是镜像和容器两样东西。

2.1 先理清镜像与容器的关系

网上喜欢说“镜像就是模板,容器就是实例”,我在实习初期的理解是:镜像是一个只读的文件系统快照,里面装好了程序、依赖、配置和启动脚本;容器是镜像被运行起来后形成的动态进程,它有自己的读写层、网络命名空间和资源限制。

有个挺贴切的类比:镜像像烤饼干的模具,容器像模具压出来的饼干。同一个模具可以压出无数块饼干,同一个镜像可以启动多个容器,容器可以被停止、删除、重新创建,但镜像是不会因为容器删除而消失的。这也是我刚接触 Docker 时反复绕不清的地方:明明docker rmi删不掉一个镜像,提示“被容器使用”,就是因为那个镜像已经起过容器,哪怕容器已经停在那里。解决办法是先docker rm删除容器,再docker rmi删镜像。

另外要理解,docker run不是单纯“创建容器”,它同时完成了三件事:检查本地有没有镜像,没有就自动 pull;用镜像创建一个容器;启动这个容器。所以你第一次docker run nginx,看到它在“拉镜像”,并不是出了什么错。

2.2 高频命令速查与参数详解

实习期间我每天敲得最多的命令,一张表就能列完:

操作命令示例说明
查看本地镜像docker images列出所有已下载镜像
拉取镜像docker pull nginx:stable指定 tag 可避免拉到 unexpected 版本
删除镜像docker rmi nginx需要先删除依赖该镜像的容器
运行容器docker run -d --name web -p 8080:80 nginx后台运行并映射端口
查看运行中容器docker ps只显示 up 状态的容器
查看所有容器docker ps -a包含停止的容器
停止容器docker stop web停止进程,但容器还在
启动已存在的容器docker start web重新启动之前 stop 的容器
删除容器docker rm -f web-f 表示强制删除运行中的容器
实时看日志docker logs -f web调试容器时最常用的命令
进入容器终端docker exec -it web bash如果镜像没装 bash 就换成 sh
查看容器资源占用docker stats类似宿主机 top,看 CPU/内存

docker run的参数看着多,但实习里翻来覆去就那么几个。-d是后台运行,不加的话日志会一直刷屏,而且一关终端容器就停了。--name给容器起个容易记的名字,后面 stop、logs、exec 都能直接用名字,比随机生成的 ID 好记太多。-p 8080:80表示把宿主机的 8080 端口映射到容器内的 80 端口,冒号左边是宿主机,右边是容器,方向千万别搞反。-it常和exec配合,表示分配一个交互式终端,方便在里面执行命令。

踩过的一个坑是:很多官方镜像默认不是 root 用户,进去之后目录权限可能不够。遇到这种情况不要慌,先看镜像文档或环境变量,大部分服务镜像都提供了-e参数来指定初始用户和密码,不是非得进容器里去改文件。

2.3 镜像拉不下来或太慢怎么办

在公司网络环境下,docker pull卡半天是非常常见的事。表现形式不一样,有的卡在Waiting,有的报 timeout,有的干脆一直转圈。核心原因是默认的 Docker Hub 镜像仓库对我们来说网络延迟偏高。解决办法不是去下载安装包,而是配置 registry-mirrors。

Linux 上编辑/etc/docker/daemon.json,Windows 上可以在 Docker Desktop 的 Settings -> Docker Engine 里直接改 JSON,配置文件格式是一样的:

{ "registry-mirrors": ["https://docker.example.com"] }

改完执行systemctl restart docker(Windows 上重启 Docker Desktop),再用docker info查看 Registry Mirrors 字段是否生效。这里要提醒一句:不要随便在网上去复制来路不明的镜像源地址,尤其是要求你关掉安全校验的那种。优先用公司内部镜像仓库,如果个人开发,就选你本机网络环境测下来比较稳的几个公共镜像源,配置好之后拉个nginx验证速度。

还有一类场景是目标机器完全没有外网,这时候镜像源也白搭。可以在一台能联网的机器上先把镜像打包出来,迁移过去:

docker save -o nginx.tar nginx:stable docker load -i nginx.tar

saveload是打包镜像的标准姿势,适合离线机房;如果只是想导出容器里的文件系统,可以用exportimport,但这两个概念不同,实习面试时经常被问到,别搞混。

3. 数据卷、端口与网络:让容器没那么“流浪”

刚用 Docker 那几天,我最大的错觉是“容器里的东西会一直还在”。直到有一次我在容器里改完配置,重启容器发现改动全没了,才真正理解“容器是无状态的”。这份代价教会我三件事:数据要挂数据卷,对外要映射端口,容器之间通信要进自定义网络。

3.1 数据不丢:-v 和具名卷的正确用法

Docker 容器的读写层是临时的,容器删除后,写在这个层上的数据也就没了。所以凡是需要持久化的数据,比如数据库文件、日志文件、应用上传的文件,都必须放到宿主机或者专门的数据卷里。最简单的写法是绑定挂载:

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

这条命令里-v /data/mysql:/var/lib/mysql把宿主机目录/data/mysql挂到容器的/var/lib/mysql,MySQL 的数据库文件就落在宿主机上。容器删了之后,数据还在/data/mysql里。

还有一种写法是具名卷:

docker volume create mysql_data docker run -d --name mysql8 -v mysql_data:/var/lib/mysql mysql:8.0

具名卷由 Docker 管理,物理位置在/var/lib/docker/volumes/mysql_data/_data。它的好处是不依赖宿主机某个特定目录,适合需要真正“卷起来”的场景。新手容易犯的错是把两者混用,启动失败时去宿主机目录找数据,发现是空的,其实数据在 volume 里。我的建议是:数据库这种核心数据,要么用绑定挂载并写清楚宿主机路径,要么统一用具名卷,别一会这一会那。

3.2 端口映射:宿主机和容器之间的关系

默认情况下,你在容器里启动的 MySQL 监听的是容器的 3306 端口,宿主机访问不到。要让外部能连上,必须做端口映射:-p 宿主机端口:容器端口。比如 MySQL 在容器内监听 3306,你在命令里写-p 3306:3306,外部就可以通过宿主机的 3306 访问容器里的 MySQL;如果改成-p 3307:3306,外部就要连宿主机的 3307 才能进到容器内的 3306。

刚入门容易理解成“容器会占用宿主机的端口”,其实更准确的理解是“你把宿主机上的某个端口‘接通’到容器的某个端口”。端口映射只是宿主机和外界之间的事情,容器和容器之间的通信不需要-p,因为它们在同一个 Docker 网络里可以直接用对方容器名或 IP 访问。

验证端口映射是否生效,可以先用docker ps看 PORTS 列,如果显示0.0.0.0:3306->3306/tcp说明映射成功。然后从宿主机执行curl 127.0.0.1:3306telnet 127.0.0.1 3306,如果通了,问题大概率出在业务层面而不是 Docker 层面。

3.3 用自定义网络搞定 Redis 主从服务

实习第三周,我的任务是搭一套 Redis 主从。按照传统思路,主节点和从节点肯定要互相通信,但要是靠容器 IP,一重启容器 IP 就变了,脚本直接报废。正确做法是创建一个自定义 bridge 网络,让容器在同一个网络里用容器名互相访问。

先建网络:

docker network create redis-net

然后分别启动主节点和从节点:

docker run -d --name redis-master --network redis-net -p 6379:6379 redis:7 redis-server --appendonly yes docker run -d --name redis-slave --network redis-net -p 6380:6379 redis:7 redis-server --slaveof redis-master 6379

这条命令里两个容器都指定了--network redis-net,所以从节点容器内部访问redis-master时,Docker 的 DNS 会解析成主节点容器的 IP。主节点不需要给从节点暴露宿主机端口,因为容器在同一网络里通信不经过宿主机。从节点映射6380:6379只是方便我们自己在宿主机上调试用。

启动完成后,从节点容器里执行:

docker exec -it redis-slave redis-cli info replication

看到master_link_status:up就表示主从关系正常。这里有个很重要的经验:如果你把两个容器放进默认的 bridge 网络,互相用 IP 访问有时能通,但用容器名解析基本不行,因为默认 bridge 网络没有内置 DNS 解析。所以不要为了省事跳过自定义网络,这个习惯会在以后部署微服务时帮你大忙。

4. 从单容器到多服务:docker-compose 整理启动参数

命令越来越长,维护越来越痛苦。尤其是要同时跑 MySQL、Redis、后端服务的时候,每次都要敲好几条超长的docker run,漏一个环境变量排查半天。实习第二周,带我的同事给我看了一个 compose 文件,我才意识到:容器的启动配置不应该靠人脑记,应该像代码一样放进仓库里管理。

4.1 一个 compose 文件搞定开发环境

docker-compose其实就是把docker run的参数用 YAML 写下来。比如我想在本地起一个 MySQL 加 Redis,再跑一个后端应用,可以建一个docker-compose.yml

services: mysql: image: mysql:8.0 container_name: dev-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: app ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql networks: - dev-net healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s timeout: 3s retries: 10 redis: image: redis:7 container_name: dev-redis ports: - "6379:6379" networks: - dev-net app: build: ./app container_name: dev-app depends_on: mysql: condition: service_healthy redis: condition: service_started ports: - "8080:8080" environment: DB_HOST: mysql REDIS_HOST: redis networks: - dev-net volumes: mysql_data: networks: dev-net:

这个文件对应了之前docker run里的几大核心参数:image对应镜像,ports对应-pvolumes对应-vnetworks对应--networkenvironment对应-e。如果你本地有 Dockerfile,build: ./app可以让 compose 先帮你构建镜像,再把构建出来的镜像启动成容器。

我特别想说的是depends_on。没经验的人以为它代表“等前一个服务完全可用后再启动下一个”,其实它默认只是控制启动顺序,即“先启动 mysql,再启动 app”。但 MySQL 可能启动到一半还没监听 3306,app 起来连不上还是白搭。所以上面的示例里我给 MySQL 加了healthcheck,然后让appdepends_on等待service_healthy,这样才真正做到了“数据库就绪后再启动应用”。这个细节在生产环境里非常有用。

4.2 compose 常用命令与排错

compose 文件写好后,启动整个环境只需要一条命令:

docker compose up -d

后面跟着的是实习生最常用的一批命令:

docker compose ps # 查看所有服务状态 docker compose logs -f # 实时看所有服务日志 docker compose logs app # 只看 app 服务日志 docker compose exec app bash # 进入某个服务容器 docker compose down # 停止并删除容器,数据卷保留 docker compose down -v # 连数据卷一起删,慎用

如果不小心改了 compose 文件里的端口或镜像,再执行一次docker compose up -d,compose 会自动对比当前运行状态和期望状态,把变化的容器重建。这是它比手写docker run舒服的地方:配置是声明式的,系统会自动收敛到最终状态。

排错的时候,第一看docker compose ps里容器状态是不是Up,第二看docker compose logs <服务名>。如果端口被占用,日志里会出现bind: address already in use,这时候去查宿主机是谁占了端口,而不是改 compose 里的 container_name。新版 Docker 默认支持docker compose这条命令,老项目里可能出现docker-compose(带横杠),那是独立 Python 包,装一下也能用,不过新环境优先用内置插件。

5. 实习期最容易踩的 6 个坑

操作命令背得再熟,不等于部署时不出问题。我整理了一下实习期间自己和其他同事踩过的坑,大部分都是环境或配置层面的,写成速查表能省不少时间。

5.1 先记住一个排查原则

Docker 出问题先别慌,把故障分成三层来分析。第一层是环境层,也就是 Docker Engine、WSL2、虚拟化、系统服务状态;第二层是容器层,包括启动命令、镜像配置、环境变量、容器日志;第三层是网络层,包括端口映射、防火墙、自定义网络、DNS 解析。大多数报错都可以归到这三层里,先定位问题在哪一层,再动手改,效率高得多。

5.2 六个高频问题速查表

现象可能原因处理方式
Docker Desktop 启动失败,提示 virtualization support not detectedBIOS 没开虚拟化,或 WSL2 未启用开机进 BIOS 开启 SVM/VT-x,开启 Windows 功能里的“虚拟机平台”和“适用于 Linux 的 Windows 子系统”
docker 命令报 failed to connect to the docker api at npipe...Docker Desktop 没完全启动,或 WSL 卡住先重启 Docker Desktop;不行就执行wsl --shutdown,再重新打开
Linux 下 docker 命令提示 permission denied当前用户不在 docker 组执行sudo usermod -aG docker $USER,重新登录后生效
容器启动后立刻退出,exit code 为 1启动命令错误,或缺少必要环境变量先用docker logs <容器名>看报错,再检查环境变量
宿主机访问不到容器端口没有-p映射,或防火墙拦截docker ps查看 PORTS 列,确认映射;再从宿主机检查防火墙
容器之间 ping 不通容器不在同一网络,或使用了默认 bridge创建自定义网络,启动容器时都加--network <网络名>

表格只是线索,排错时一定要看具体日志。举一个我自己的实例:实习生第一次部署 MySQL,端口映射和环境变量都写了,但容器一直退出,状态码 1。当时我直接重新 pull 镜像,反复试了几次都没用。后来冷静下来执行docker logs mysql8,看到的报错是宿主机挂载目录/data/mysql权限不足,容器内 MySQL 进程没有写入权限。原因是绑定挂载的目录是 root 创建的,容器里 mysql 用户 uid 是 999,自然不能写。解决方案是chown -R 999:999 /data/mysql,或者干脆换成具名卷。这个案例说明,容器起不来,第一步永远是看日志,不是重装。

5.3 镜像拉取慢的补充排查

镜像拉取慢不止是网络延迟的问题。有时是 DNS 解析异常,明明配置了镜像源也没用。遇到这种情况,先用docker info确认配置是否生效,再在宿主机上ping一下镜像仓库域名看通不通。如果公司的网络环境特殊,可能还需要问运维同事要公司内部的镜像仓库地址,这个比公共镜像源更稳定。

另外,内存不足也会导致启动异常。用 Docker Desktop 跑 MySQL,默认给虚拟机的内存可能只有 2GB,MySQL 加上系统本身很容易 OOM,现象是容器退出状态码 137。这时候去 Docker Desktop Settings 里把内存调到 4GB 以上,问题通常会缓解。docker stats是观察资源占用的好工具,养成习惯,出问题前就能发现苗头。

6. 写在最后:两个习惯让我少踩了很多坑

实习这段时间,我最大的体会是:Docker 操作不是靠背命令,而是靠建模和执行习惯。每次敲docker run之前,我都会先在脑子里过一遍,这个容器的数据落在哪里,端口映射是给谁访问的,容器之间靠什么通信,要不要自启动和健康检查。这个“先想后敲”的习惯帮我避免了很多次“容器跑了但数据没了”的返工。

另一个习惯是保留现场。遇到容器启动失败,我不会急着把容器删了重来,而是先执行docker logsdocker inspect,把报错记下来再操作。很多问题的答案就在日志的最后几行里,重来了反而失去排查线索。如果你也想在实习里快速成长,我建议从今天开始,把碰到的每一次报错都当成一个考点,而不是一个麻烦,记录下来的排查过程就是最宝贵的经验。

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

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

立即咨询