Docker速通指南:实习生必备的容器化环境部署与避坑手册
我到现在还记得实习第一天mentor把一台装了Windows的电脑推到我面前说“帮我把Docker跑起来然后用它装个MySQL和Redis等会联调要用。”我看着桌面上的Docker Desktop图标脑子里全是问号。Docker是什么和虚拟机一样吗为什么要用它装数据库折腾了一周踩过权限、网络、镜像下载慢这些坑之后我总算能跟别人讲明白这套东西了。Docker就是一把环境打包机把你的应用和它依赖的运行环境塞进一个个轻量级容器里换台机器也能原样跑起来。这篇文章是专门写给实习生的速通笔记目标是用半天到一天的时间搞定安装、配好镜像加速、把MySQL和Redis跑起来再用Compose拉起一套能交付联调的环境顺带把新手必踩的坑都给你标出来。1. 为什么要在实习第一天就搞懂Docker它到底帮你省了哪些事第二天我还在纠结直接装个MySQL不香吗等我看到同事电脑上那堆环境变量、服务配置、PHP扩展和启动脚本时才明白一个事实每台机器的环境都不完全一样。JDK版本差一个小版本Spring Boot启动就可能直接报错MySQL的字符集配置不同线上导出的数据在本地就是乱码。这些“环境差异”造成的问题在团队协作里几乎是每天都能遇到。Docker的核心理念就是解决这个环境即代码环境可移植。1.1 环境的“一键搬迁”从“在我机器上是好的”开始说起“在我机器上是好的”这句话估计每个研发都听过。以前排查这种问题需要一步步对比两台机器的系统版本、依赖库、配置文件工作量非常大。有了Docker之后你可以把应用连同它依赖的操作系统库、运行时、配置文件一起打包成一个镜像然后把这个镜像丢到任何一台装有Docker的机器上运行。从效果看就像把冰箱、洗衣机、床垫全部塞进一个集装箱搬家公司只需要搬运集装箱到了新家打开就能用。虚拟机也是类似的思路但它要完整模拟一套操作系统占用好几个G启动也要分钟级容器则直接共享宿主机内核只用隔离进程和文件系统镜像可能只有几十M启动速度在秒级。1.2 镜像、容器、仓库三个容易混的词一次说清新手最容易被这三个词绕晕我再怎么强调都不过分。镜像Image是一个只读模板里面包含了一套完整的运行环境比如Ubuntu系统层、MySQL程序、配置文件等可以用Dockerfile描述构建过程。容器Container是镜像运行时的实例一个镜像可以创建出多个独立运行的容器彼此互不影响。仓库Registry是存放镜像的“图书馆”最常见的Docker Hub你可以从上面拉取别人构建好的镜像也可以把自己构建的镜像推上去供团队使用。打个比方镜像是菜谱容器是按菜谱做出来的一盘菜仓库是放菜谱的书架。1.3 Docker的边界它不能帮你解决什么不过我得先把丑话说在前头Docker不是万能的。容器共享宿主机内核所以它做不到像虚拟机那样的内核隔离如果你的应用需要运行一个完全不同内核版本的操作系统容器方案基本不适用。它也不能解决所有“环境不一致”的问题——比如宿主机本身的网络拓扑、存储性能、内核参数不同容器里的表现仍会有差异。实习生经常犯的错是把Docker当成一个“隐藏复杂性的黑盒”其实它只是帮你把环境封装好基础的Linux命令、网络知识、进程管理你还是得会。所以别急着只学Docker命令底层原理该补的还是要补。2. 实习装机实录把Docker从安装到跑起nginx的完整链路既然是速通我默认你是第一次在真实机器上装Docker。这一章的路线会很直白Windows怎么装、Linux怎么装、装完先做哪一步、怎么验证成功。每一步都有坑我按顺序给你标出来。2.1 Windows装Docker Desktop前必查的两个开关如果你用的是Windows多半会装Docker Desktop。现在Docker Desktop默认用WSL2作为后端虽然安装包是图形界面下一步下一步但前置条件经常被忽略。装完后双击图标如果看到“Docker Desktop failed to start because virtualisation support wasnt detected”这种报错基本是两种情况一是BIOS里的CPU虚拟化没开二是Windows的“虚拟机平台”或“适用于Linux的Windows子系统”功能没启用。排查链路很简单先去任务管理器-性能-CPU看“虚拟化”那一栏是否显示“已启用”。如果没启用重启进BIOS找到Intel VT-x或AMD-V选项打开。然后在Windows功能里勾选“适用于Linux的Windows子系统”和“虚拟机平台”重启后再试。如果还不行建议直接打开PowerShell跑一下wsl --status确认WSL2是否可用。我见过不少实习生卡在这一步其实换个WSL2版本或者重装WSL内核就能解决。2.2 Linux命令行安装Docker仓库、权限、开机自启绝大多数公司和服务器都是Linux所以命令行安装是必须会的。以Ubuntu为例最好从官方仓库装不要直接apt install docker.io那个版本经常偏老。标准做法是先装依赖、加GPG key、加仓库然后安装docker-ce docker-ce-cli containerd.io docker-compose-plugin。CentOS类似用yum install yum-utils配置repo再装同一组包。装完以后第一件事不是马上用而是把自己加入docker用户组。直接执行sudo usermod -aG docker $USER然后注销重新登录。否则你会立刻碰到那条经典的报错permission denied while trying to connect to the docker api。这是因为Docker客户端默认通过/var/run/docker.sock这个socket和守护进程通信当前用户不在docker组里就没权限访问。别忘了sudo systemctl enable docker让Docker开机自启不然服务器重启之后你的容器全部躺平。2.3 开镜像加速一次配置少等半小时镜像下载慢绝对是新手劝退主力。默认情况下Docker从Docker Hub拉取镜像国内网络经常会卡到几十KB/s一个几百M的镜像要等半天。解决办法是配置镜像加速器。Linux上编辑/etc/docker/daemon.json填入一个或多个加速器地址{ registry-mirrors: [https://docker.m.daocloud.io] }然后执行sudo systemctl restart docker。Windows用户直接在Docker Desktop设置里的Docker Engine配置项中修改。注意加速器属于公共资源有时候某个地址会变慢或失效如果遇到拉取超时换一个加速地址再重启。我实习时第一次拉mysql镜像忘记配加速硬生生等了快二十分钟后来配好重拉一分钟左右就下来了差别体感非常明显。2.4 跑起nginx容器验证到底成没成环境装好加速配好现在跑一个最小的容器验证整条链路。打开终端执行docker run -d --name web -p 8080:80 nginx-d表示后台运行--name web给容器起名叫web-p 8080:80把宿主机8080端口映射到容器内80端口。镜像没有的话会自动拉取。跑完执行docker ps看到nginx容器处于Up状态再用浏览器访问http://localhost:8080能看到Welcome to nginx页面就说明成了。接着可以用docker exec -it web bash进入容器内部自己看看/usr/share/nginx/html目录。看完之后exit退出docker rm -f web把这个测试容器删掉。到这里你的Docker环境才算真正跑通了。3. 每天反复在用的核心命令镜像、容器、数据卷一次讲透第二周开始我基本每天都要和镜像、容器、数据卷打交道。这几个概念对应的命令不多但每个参数都有它的用途。这一章我不列全部命令只把“实习期最常用”的挑出来配合场景讲清楚。3.1 镜像管理拉取、查看、搬运、清理镜像操作其实就围绕四件事拉、查、删、搬。docker pull mysql:8.0 docker images docker rmi mysql:8.0 docker save mysql:8.0 -o mysql.tar docker load -i mysql.tardocker save和docker load用来离线迁移镜像我实习时服务器不能直连外网就是在这个笔记本上save成tar包再scp到服务器load进去。另外docker rmi删的是镜像如果镜像已经被某个容器使用需要先删容器或者加-f强制删但我不建议轻易加-f容易把依赖关系搞乱。清理悬空镜像的实用命令是docker image prune -f这个可以放心用它只删掉那些“没有标签且没有被容器引用”的镜像层。3.2 docker run参数拆解-p -v -e --name -d 的含义与顺序新手写docker run最怕一长串参数记不住。我给你拆成一目了然的形式参数作用例子-d后台运行容器-d nginx--name给容器命名--name web-p端口映射宿主机端口:容器端口-p 8080:80-v数据卷挂载宿主机目录:容器目录-v /data:/var/lib/mysql-e设置容器内环境变量-e MYSQL_ROOT_PASSWORD123456--restart设置重启策略--restart unless-stopped理解-p和-v就够了。端口映射让宿主机能访问容器里的服务数据挂载让容器可以读写宿主机文件。实际上-v有两种写法一种是刚才表格里的“目录挂载”叫bind mount直接把宿主机的路径绑进去另一种是命名卷比如-v mysql_data:/var/lib/mysql这个mysql_data是Docker自己管理的一块存储不管容器怎么删数据都还在。对于数据库这类必须持久化的应用我强烈建议用命名卷而不是目录挂载因为Docker会自动处理权限目录挂载到MySQL容器里经常会遇到“Permission denied”。3.3 调试三板斧logs、exec、inspect容器跑起来之后怎么判断它是不是正常工作只看docker ps是不够的还得会看日志、进容器、看细节。日志用docker logs -f 容器名-f是持续跟踪输出就像tail -f一样。进容器用docker exec -it 容器名 bash进去之后可以排查容器内部的问题比如环境变量、文件路径。如果容器启动失败第一件事应该是docker logs 容器名看看最后输出的报错信息大多数情况下日志里已经把原因写得很清楚了。docker inspect则是查看容器的详细配置包括IP地址、挂载目录、环境变量、网络模式等等。我实习时排查端口起不来就是用docker inspect看端口映射是否生效。还有一个实用技巧docker inspect --format{{.NetworkSettings.IPAddress}} 容器名可以直接输出容器IP写脚本时很好用。3.4 数据卷与容器生命周期删除容器不丢数据的关键这是很多新手掉过的坑数据库容器跑得正欢想升级镜像直接docker rm -f mysql然后重新跑了一个结果发现数据全没了。为什么会丢因为当初你用容器内置的路径存数据并没有挂载任何卷。容器一旦删除它内部写过的文件全部消失。解决办法就是前面提到的命名卷。我建议创建MySQL容器时至少加这一句-v mysql_data:/var/lib/mysql。这样MySQL的数据写到命名卷里之后无论你删容器、换镜像、升级版本只要重新启动容器时挂载同一个卷数据都还在。验证方法也不难在容器里往库插一条记录然后docker rm -f删除容器再重新运行一个挂载同一个卷的MySQL容器数据就在。这条经验值五颗星必须牢记。4. 直接上手业务用Docker部署MySQL 8.0和Redis主从并连通项目实习生的典型任务就是在本地用Docker拉起一套和线上环境一致的数据库和缓存供项目联调使用。这一章我以最常见的MySQL 8.0和Redis为例从零开始跑通并且解决“项目容器访问docker容器内的mysql”怎么连的问题。4.1 最简MySQL 8.0启动命令与初始化启动命令可以直接抄docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEtestdb \ -v mysql_data:/var/lib/mysql \ mysql:8.0这里用MYSQL_ROOT_PASSWORD设置了root密码用MYSQL_DATABASE创建了一个初始数据库。第一次启动会做初始化可能需要几十秒期间你执行mysql客户端连不上很正常不要着急。我看过很多“docker安装mysql失败”的帖子其实多半是两种情况一是初始化还没完成就急着连接二是有其他进程占了3306端口。排查端口占用可以执行netstat -tlnp | grep 3306宿主机端口被占用的话就把映射到别的端口比如-p 3307:3306。容器起来后在宿主机用mysql -h127.0.0.1 -P3306 -uroot -p连一下能连上就算成功。如果想从项目代码里连连接地址填127.0.0.1:3306就够了因为端口映射会把容器3306暴露给宿主机。4.2 三种网络方案让应用容器和MySQL互相连通实际项目不会只在宿主机跑应用也经常跑在Docker容器里。这时候就会出现一个问题应用容器里访问数据库用127.0.0.1:3306是连不上的因为那个java容器的localhost不是宿主机除非你加了特殊参数。我简单梳理三种方案使用host网络模式在docker run时加--network host容器和宿主机共享网络栈应用可以直接访问127.0.0.1:3306但会占用宿主机端口隔离性差一些。Windows和Mac的Docker Desktop提供了host.docker.internal容器内连这个域名可以映射到宿主机适合开发时用。推荐方案把所有相关容器加入到同一个自定义网络让容器通过名字互访。自定义网络的操作是这样docker network create devnet docker network connect devnet mysql8 docker run -d --name backend --network devnet some-app-image之后backend容器里直接连接jdbc:mysql://mysql8:3306/testdb就行了Docker内部DNS会自动解析容器名。这个方法最接近真实生产环境因为你不需要关心IP变化容器重启名字不变就能继续访问。4.3 Redis主从两条命令搭好但要记住这个坑Redis相对简单单机版一行命令docker run -d --name redis -p 6379:6379 -v redis_data:/data redis:7 redis-server --appendonly yes--appendonly yes开启AOF持久化数据放在/data挂载到命名卷。一般来说单机够用了但面试和线上环境经常要求做主从。主从搭建其实也不复杂先启动一个主节点docker run -d --name redis-master -p 6379:6379 redis:7 redis-server --appendonly yes再启动从节点docker run -d --name redis-slave -p 6380:6379 --network devnet redis:7 redis-server --slaveof redis-master 6379注意新版本Redis已经用--replicaof替代了--slaveof不过两个指令在较新版本都还兼容。另一个坑是主从节点必须在同一个自定义网络里否则从节点访问redis-master这个主机名根本解析不了。验证主从链接用docker exec redis-slave redis-cli -p 6379 info replication看role:slave和master_link_status:up就说明打通了。我实习时第一次搭主从死活连不上最后发现从节点跑了默认bridge网络主节点在自定义网络里加--network devnet解决这个问题值得记住。4.4 从容器外部验证端口、数据写入和时区启动完MySQL和Redis不要急着走至少做两个验证。先写入一条数据docker exec -it mysql8 mysql -uroot -p -e INSERT INTO testdb.demo(name) VALUES(hello);然后又在宿主机用客户端查出来确认数据真的落盘。再试一下容器重启后数据还在docker restart mysql8再查一次。如果你发现容器内时间差8小时比如date显示UTC时间这就是时区问题。MySQL和日志排查看起来会很混乱建议启动时加上环境变量-e TZAsia/Shanghai或者挂载/etc/localtime:/etc/localtime:ro。Redis一般不需要单独设时区但如果你在项目里读到的Redis过期时间不对也要检查容器时区。5. 容器多了就要上Compose一次定义一整条开发环境当你需要同时跑MySQL、Redis、你的后端项目甚至还有前端Nginx和一个消息队列再靠一条条docker run命令去启动会非常痛苦。每个容器都要写映射、写挂载、写网络启动顺序还得自己控制稍不留神就漏配置。这一章我用一个最小可用的docker-compose.yml把你从手工敲命令中解放出来。5.1 什么时候该从docker run转向docker compose我个人的判断标准是超过两个需要互相通信的容器就值得上Compose。Compose的核心价值是把多个容器的配置、环境变量、网络、依赖关系写在一个YAML文件里一条命令全部拉起或者全部停掉。它不改变容器本身的原理只是替你把启动参数整理成声明式配置方便团队共享。你现在手里的项目如果是Java可能还有构建步骤Compose也可以和镜像构建联动。注意命令区别新版本Docker推荐使用docker compose带空格它是Docker Compose v2插件老版本用的是docker-compose带横线。如果你按教程装的是旧版两个命令都能用但我在Ubuntu上更建议安装docker-compose-plugin包直接用docker compose。5.2 一个最小可用的compose文件长什么样假定你要拉起一个“MySQL Redis Spring Boot开发环境”文件长这样services: mysql: image: mysql:8.0 container_name: dev-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: testdb ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -proot123] interval: 10s timeout: 5s retries: 5 redis: image: redis:7 container_name: dev-redis ports: - 6379:6379 volumes: - redis_data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 5s retries: 5 backend: build: context: . dockerfile: Dockerfile container_name: dev-backend depends_on: mysql: condition: service_healthy redis: condition: service_healthy ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: dev DB_HOST: mysql DB_PORT: 3306 REDIS_HOST: redis volumes: mysql_data: redis_data:不用纠结这个配置是否完整重点看它表达了什么三个服务、各自的镜像、环境变量、端口映射、数据卷以及后端依赖MySQL和Redis先健康起来。写好后在项目根目录执行docker compose up -d它会自动联网拉镜像、构建代码、创建网络最后把三个容器都跑起来。docker compose ps可以查看状态docker compose logs -f backend能持续看后端日志要停全部就docker compose down比以前一个个敲命令舒服太多了。5.3 docker compose up -d报错我总结出的三个排查方向新手执行docker compose up -d时可能会遇到报错最常见的我归纳成三类。第一类是语法问题YAML文件缩进不对报错信息里会带上line和column位置用空格而不是Tab缩进是基本要求。第二类是端口冲突比如本机3306已经被MySQL占了Compose会报端口不可用改掉映射端口就行。第三类是镜像拉取失败或配置项不认识比如depends_on.condition在旧版本Compose里不支持那就需要升级插件或修改写法。排查时可以加docker compose config命令它会校验并输出最终配置如果这个能过说明静态配置没问题再去考虑端口和网络。6. 把项目变成镜像入门Dockerfile与微服务部署实战前面的步骤都是在使用现成镜像但真实项目最终都要把自研代码打成镜像。这一章我用一个常见的Java后端项目举例带你从零写Dockerfile完成镜像构建、瘦身和部署。其他语言Python、Node、Go思路完全一样只是基础镜像和构建命令不同。6.1 从源码到镜像一个Java项目的Dockerfile完整演进假设项目用Maven构建产出是一个Spring Boot jar包。新手最容易写的Dockerfile是这样FROM maven:3.8-jdk-17 WORKDIR /app COPY . . RUN mvn clean package -DskipTests EXPOSE 8080 CMD [java, -jar, target/demo-0.0.1-SNAPSHOT.jar]这样虽然能构建但镜像里会包含整套Maven和源码体积巨大而且运行时的容器里还有编译工具安全风险高。更好的做法是把构建和运行分开也就是多阶段构建。6.2 多阶段构建给镜像瘦身1.2GB到200MB的实测记录多阶段构建用一个Dockerfile写两个FROM第一个阶段负责编译第二个阶段只拷贝成品和运行环境FROM maven:3.8-jdk-17 AS build WORKDIR /app COPY . . RUN mvn clean package -DskipTests FROM eclipse-temurin:17-jre WORKDIR /app COPY --frombuild /app/target/demo-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建命令还是docker build -t demo:1.0 .。第二个阶段基于精简的JRE运行时镜像没有Maven没有源码只有jar包。我实测同一个项目单阶段镜像大约1.2GB多阶段后只有200MB左右。剪掉的不只是体积还减少了攻击面部署传输也快很多。构建项目时建议用docker build --no-cache排除一层缓存带来的错觉进入容器里验证时再用。如果你用IDEA开发也可以在IDEA里配置Docker插件右键Dockerfile直接构建镜像或者在pom文件里接上dockerfile-maven-plugin插件让Maven直接负责打包。不过实习阶段先别依赖IDE把Dockerfile的手写逻辑搞懂后面怎么自动化都容易。6.3 部署到服务器重启策略、健康检查与私有仓库的取舍镜像构建好之后部署就很简单。本地验证通过可以推送到一个私有镜像仓库比如自己搭的Registry或云厂商的镜像服务然后服务器上docker pull再运行。如果只是内网临时部署docker save/load传tar包也够用。生产部署的必要参数--restart unless-stopped这样Docker服务重启后容器会自动拉起还有健康检查可以在运行时加--health-cmd也可以在镜像的Dockerfile里定义HEALTHCHECK让Docker定期探测存活。我见过实习生部署到服务器后进程一崩就彻底歇菜登录上去才发现容器是Exited状态就是因为没配重启策略。这些细节在生产环境不是说可要可不要而是必须提前想清楚。7. 实习生最容易踩的五个坑权限、网络、磁盘、时区、镜像速度最后这一章我专门把速通过程中最常遇到的五类问题拉出来每一条都有清晰的排查链路。你几乎一定会遇到其中一个建议直接收藏。7.1 permission denied深入排查用户组与docker.sock报错原文经常是permission denied while trying to connect to the docker api。我实习第一天装完Docker直接跑docker ps就撞上了这个。原因前面提过当前用户不在docker组里。但如果你已经执行了usermod -aG docker还报这个错别急着重新登录先检查ls -l /var/run/docker.sock。正常情况下socket文件应该显示docker组拥有权限是rw。如果你不小心chown过这个文件或者Docker守护进程不是用root身份启动权限就会错。修复方法通常是重启Docker服务或者重新加入docker组后执行newgrp docker刷新当前会话。注意加入docker组等于拿到了宿主机root级别的权限这在公司环境里要谨慎但实习开发机一般问题不大。7.2 容器网络不通从bridge到host到自定义网络怎么选第二个高频场景容器能启动但容器内访问不了外网或者容器之间互相访问不到。先说外网不通多半是DNS问题。默认容器使用系统镜像里设置的DNS你可以在启动时手动指定--dns114.114.114.114或者daemon.json里统一配置DNS。再说容器间互访很多新手把所有容器丢在默认bridge网络里然后用localhost去访问对方结果当然失败。排查链路是这样docker network ls看一下有哪些网络docker inspect 容器名 | grep NetworkMode确认所属网络再docker exec -it 容器名 ping 另一个容器名测连通性。如果不在同一网络就把它们都加入一个自定义网络容器名就是互相访问的地址。自定义网络相比默认bridge有DNS解析优势这也是我在第4章反复让你建devnet的原因。7.3 磁盘被镜像占满清理姿势容器和镜像越拉越多磁盘很容易被吃满。先执行docker system df你会看到镜像、容器、本地卷、构建缓存各自占了多少。最常用的清理命令是docker system prune -a它会删除所有未被使用的镜像、悬空容器和网络。注意-a会删掉那些没有运行中的容器引用的镜像如果你只是想临时把空间腾出来可以接受。如果磁盘还满看看容器日志有没有无限膨胀。Docker会默认记录容器日志一个疯狂打日志的容器能把几十G磁盘打满。解决方法是运行容器时加日志限制--log-driver json-file --log-opt max-size10m --log-opt max-file3也可以在daemon.json里全局配置。实习生的机器经常因为数据卷和日志爆盘这个清理姿势很救命。7.4 容器时区少8小时一条命令根治容器默认使用UTC时区所以日志时间总是和北京时间差8小时。你如果在公司联调看到日志时间对不上第一反应应该是检查容器时区。根治方法是启动容器时加-e TZAsia/Shanghai对于Dockerfile也可以加ENV TZAsia/Shanghai。部分基础镜像可能没有zoneinfo文件更保险的做法是挂载/etc/localtime。我在MySQL容器踩过这个坑排查线上问题看到日志时间全差了8小时白白折腾了小半天后来在compose文件里统一给每个服务加上TZAsia/Shanghai再也没出过这类问题。7.5 镜像下载慢先别急着换源先看这一处如果你已经配了镜像加速docker pull还是像蜗牛爬别急着频繁换加速源先检查一下是不是镜像墙内分发本身不稳定。可以执行docker info看Registry Mirrors那一栏是否已经显示了你配置的加速器。如果显示为空说明配置没生效daemon.json可能写错了或者Docker没有重新启动。配置生效但依然慢可以试试拉取一个热门小镜像比如alpine做对比确认是网络问题还是特定镜像问题。如果某个镜像只有一个超大层且加速器没有缓存那也只能等没有捷径。我实习时最大教训是把“镜像慢”归咎于网络之前先花十秒确认配置真的加载了能省掉很多无效等待。写到这里基本上把实习生从装环境到日常使用、再到项目部署的Docker速通路径走完了。最后再分享一个我个人的习惯刚开始接触Docker时别急着追求花式命令把docker pull、docker run -d -p -v -e、docker logs、docker exec、docker compose up -d这五个组合吃透已经能覆盖绝大多数开发和联调场景。等这些命令形成肌肉记忆再回头去研究网络模式、镜像分层、资源限制这些进阶内容你会发现自己已经能独立处理很多环境问题了。