从Linux基础到Docker实战:底层原理与避坑指南 📅 发布时间:2026/9/19 21:40:12 👁 浏览次数: 1. 从裸机到容器我为什么劝你先搞懂Linux再碰Docker很多人上手Docker的路径是这样的看到别人一条命令就把MySQL跑起来了觉得很酷于是直接apt install docker.io然后开始搜docker安装mysql教程照着复制粘贴。结果遇到权限报错、端口冲突、数据卷挂载路径写错、容器启动就退出——每一个问题都卡住半天因为根本不知道底层发生了什么。我自己最开始也是这样。第一次在虚拟机上装Docker装完之后docker run hello-world拉不下来镜像盯着终端发呆。后来才意识到Docker不是凭空存在的它深度依赖Linux内核的几个核心机制Namespace命名空间做资源隔离、Cgroups控制组做资源限制、UnionFS联合文件系统做镜像分层。你不理解这些用Docker就永远只能停留在抄命令的阶段出了问题完全无法排查。所以这篇笔记的定位很明确给刚接触Linux和Docker的人一条从底层到实操的完整路径。不管你是用Ubuntu、CentOS还是国产Linux发行版不管你是在物理机、虚拟机还是WSL上操作这里的内容都能直接参考。我会先讲清楚Linux里那些你绕不开的基础操作再进入Docker的安装、配置和实战最后分享一些我在实际使用中踩过的坑和总结出来的经验。整篇内容基于我自己的学习路径整理不是手册式的命令罗列而是按照为什么需要这个→怎么操作→会遇到什么问题→怎么解决的逻辑来组织。你可以从头看也可以直接跳到需要的章节。2. Linux基础操作那些你必须先跨过去的坎2.1 系统安装与虚拟机环境搭建在学Linux之前第一个问题就是装在哪里我的建议是先用虚拟机别一上来就格盘装双系统。虚拟机的好处是可以随便折腾搞坏了删掉重建就行成本极低。常用的虚拟机方案有VirtualBox和VMware Workstation。VirtualBox免费开源功能够用VMware性能更好但收费有免费的个人版。选哪个都行核心配置注意几点内存至少给2GB如果要在里面跑Docker建议4GB起步。Docker容器本身不重但镜像构建和多个容器同时运行会吃内存。磁盘最少20GB建议40GB以上。Docker镜像很占空间一个MySQL镜像就几百MB几个镜像下来十几个GB就没了。网络模式选NAT模式最省事虚拟机能访问外网就行。如果你需要从宿主机直接访问虚拟机的服务用桥接模式或者配置端口转发。CPU虚拟化必须在BIOS里开启VT-x/AMD-V否则后面装Docker Desktop会直接报virtualization support not detected。安装Linux系统时Ubuntu Server版和桌面版都可以。Server版没有图形界面资源占用少适合练习命令行桌面版对新手友好但会多占一些资源。国产Linux发行版如统信UOS、麒麟等操作逻辑基本一致命令层面差异很小。安装过程中分区方案新手直接用自动分区就行。如果你想手动分区记住这个基本方案挂载点建议大小说明/15-20GB根分区系统和软件都在这/home剩余空间的大部分用户数据swap内存的1-2倍交换分区内存不够时兜底/boot512MB-1GB启动文件别太小注意如果你在Windows上通过WSL2使用Linux可以跳过虚拟机这一步。WSL2本身就是轻量级虚拟机性能很好和Windows的文件互访也方便。但WSL2的网络配置和纯Linux有些差异后面会提到。2.2 命令行生存指南从cd到管道Linux命令行的学习曲线确实陡但常用的就那么几十个。我把它们分成几类你按类记会快很多。文件和目录操作是最基础的ls -alh # 列出当前目录所有文件含隐藏文件人类可读的大小 cd /etc # 切换到/etc目录 pwd # 显示当前路径 mkdir -p a/b/c # 递归创建多级目录 cp -r src dst # 递归复制目录 mv old new # 移动或重命名 rm -rf dir # 递归强制删除危险操作确认路径再执行rm -rf这个命令我要多说一句。我见过太多人因为路径写错或者变量为空把整个系统删掉的案例。执行之前一定要pwd确认当前路径或者先用ls看看要删的东西对不对。如果非要保险可以先用mv移到/tmp目录确认没问题再删。文件查看和编辑cat file.txt # 一次性输出全部内容 less file.txt # 分页查看q退出/搜索 head -n 20 file.txt # 看前20行 tail -f /var/log/syslog # 实时追踪日志排查问题最常用 vim file.txt # 编辑文件vim是绕不过去的。新手至少要学会i进入编辑模式Esc退出编辑模式:wq保存退出:q!不保存强制退出。这四个操作能覆盖80%的场景。权限管理是Linux的核心概念之一chmod 755 script.sh # rwxr-xr-x chmod x script.sh # 添加执行权限 chown user:group file # 修改所有者和所属组权限的数字含义r4w2x1。755就是所有者有读写执行7421组用户和其他用户有读和执行541。这个一定要理解不然后面Docker挂载文件时权限报错你都不知道为什么。进程和系统状态ps aux | grep nginx # 查看nginx相关进程 top # 实时系统资源监控 kill -9 PID # 强制杀死进程 systemctl status docker # 查看服务状态 systemctl start/stop/restart docker # 服务管理管道和重定向是Linux命令行的精髓。|把前一个命令的输出作为后一个命令的输入重定向输出到文件追加到文件ps aux | grep python | awk {print $2} | xargs kill -9 # 找到所有python进程提取PID批量杀死这条命令看起来复杂但拆开看就是ps aux列出所有进程grep python过滤出python相关的awk {print $2}取第二列PIDxargs kill -9把PID传给kill命令。管道就是把小工具串起来完成复杂任务这是Linux哲学的核心。2.3 用户管理与权限体系Linux是多用户系统理解用户和权限体系对后面Docker的操作至关重要。useradd -m -s /bin/bash newuser # 创建用户带home目录指定shell passwd newuser # 设置密码 usermod -aG sudo newuser # 加入sudo组Ubuntu usermod -aG wheel newuser # 加入wheel组CentOS su - newuser # 切换用户为什么这个重要因为Docker默认需要root权限。你安装完Docker后直接运行docker ps会报permission denied。解决方案有两种每次都用sudo docker ...麻烦但安全。把当前用户加入docker组sudo usermod -aG docker $USER然后重新登录。这样就不用每次sudo了。但方案2有个安全隐患docker组的成员实际上等同于root权限因为你可以通过Docker挂载宿主机的根目录来获得root访问。所以生产环境要谨慎个人开发环境无所谓。2.4 网络配置与DNS问题排查Linux下网络出问题是新手最常遇到的。我列几个典型场景和排查思路。检查网络连通性ip addr show # 查看网卡和IP地址 ip route show # 查看路由表 ping -c 4 baidu.com # 测试外网连通性 ping -c 4 8.8.8.8 # 测试IP连通性排除DNS问题如果IP能ping通但域名不行那就是DNS问题。检查/etc/resolv.confcat /etc/resolv.conf # 应该看到类似 # nameserver 8.8.8.8 # nameserver 114.114.114.114如果没有或者不对可以手动添加。但注意在某些发行版上/etc/resolv.conf是自动生成的重启后会覆盖。永久修改需要改/etc/systemd/resolved.conf或者/etc/netplan/下的配置文件Ubuntu 18.04。DNS配置的坑我遇到过最诡异的一次是/etc/resolv.conf里明明写了nameserver但就是解析不了。后来发现是systemd-resolved服务在管理需要systemctl restart systemd-resolved才生效。还有一种情况是NetworkManager覆盖了DNS设置需要在网卡配置里加dnsnone。2.5 文件解压与编码问题Linux下解压文件乱码是中文用户的老问题。根本原因是Windows下的zip文件通常用GBK编码而Linux默认用UTF-8。# 查看文件编码 file archive.zip # 解压时指定编码需要unzip支持 unzip -O GBK archive.zip # 如果unzip不支持-O参数用7z 7z x archive.zip -o./output # 或者用Python脚本转换 python3 -c import zipfile with zipfile.ZipFile(archive.zip) as z: for name in z.namelist(): correct_name name.encode(cp437).decode(gbk) z.extract(name, output) import os os.rename(os.path.join(output, name), os.path.join(output, correct_name)) tar包一般不会有编码问题因为tar不存储文件名编码信息直接按字节处理tar -xzvf archive.tar.gz # 解压.tar.gz tar -xjvf archive.tar.bz2 # 解压.tar.bz2 tar -xJvf archive.tar.xz # 解压.tar.xz3. Docker安装不同环境下的正确姿势3.1 Ubuntu/Debian上的安装流程Ubuntu上安装Docker有好几种方式我推荐用官方仓库安装而不是apt install docker.io。原因很简单官方仓库的版本更新功能更全而且和Docker Desktop的兼容性更好。# 1. 卸载旧版本如果有 sudo apt remove docker docker-engine docker.io containerd runc # 2. 安装依赖 sudo apt update sudo apt install ca-certificates curl gnupg lsb-release # 3. 添加Docker官方GPG密钥 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 4. 添加仓库 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 # 5. 安装Docker Engine sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin # 6. 验证 sudo docker run hello-world每一步都在做什么第3步的GPG密钥是用来验证软件包来源的防止被篡改。第4步添加仓库地址$(lsb_release -cs)会自动替换成你的Ubuntu代号如jammy、focal。第5步安装三个核心组件docker-ce是引擎本体docker-ce-cli是命令行工具containerd.io是容器运行时。如果docker run hello-world能输出Hello from Docker!说明安装成功。3.2 CentOS/RHEL系的差异CentOS上的安装步骤类似但包管理器和仓库配置不同# 1. 安装yum-utils sudo yum install -y yum-utils # 2. 添加仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 3. 安装 sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin # 4. 启动并设置开机自启 sudo systemctl start docker sudo systemctl enable dockerCentOS 8之后默认用dnf替代yum命令基本兼容。另外CentOS 8已经停止维护建议用Rocky Linux或AlmaLinux替代安装步骤完全一样。3.3 Windows和macOS上的Docker DesktopWindows上装Docker最省事的是Docker Desktop。但有几个前提条件Windows 10/11 64位专业版或企业版家庭版需要WSL2开启WSL2或Hyper-VBIOS中开启虚拟化安装过程就是下载安装包双击一路下一步。安装完成后重启Docker Desktop会自动启动。最常见的报错virtualization support not detected或Docker Desktop failed to start because virtualisation support wasnt detected。这个问题的原因和对策原因排查方法解决方案BIOS未开启虚拟化任务管理器→性能→CPU看虚拟化是否已启用重启进BIOS找到Intel VT-x或AMD-V设为EnabledHyper-V未开启systeminfo查看Hyper-V要求控制面板→程序→启用或关闭Windows功能→勾选Hyper-VWSL2未安装wsl --list --verbosewsl --install然后重启与其他虚拟化软件冲突是否装了VMware/VirtualBox关闭Hyper-V改用WSL2后端或反之macOS上装Docker Desktop就简单多了下载dmg拖进Applications打开就行。但注意M1/M2芯片要下载Apple Silicon版本Intel芯片下载Intel版本。3.4 离线环境下的Docker安装有些生产环境没有外网需要离线安装。步骤是在有网的机器上下载deb/rpm包# Ubuntu apt-get download docker-ce docker-ce-cli containerd.io docker-compose-plugin # CentOS yumdownloader docker-ce docker-ce-cli containerd.io docker-compose-plugin把包拷到目标机器用dpkg -i或rpm -ivh安装。离线环境下拉不了镜像需要提前在有网的机器上docker pull然后docker save成tar文件拷过去docker load# 有网机器 docker pull mysql:8.0 docker save mysql:8.0 -o mysql8.tar # 离线机器 docker load -i mysql8.tar这个方法也适用于内网服务器批量部署比每台机器单独拉镜像快得多。4. Docker核心概念用生活化类比理解底层原理4.1 镜像、容器、仓库三层结构的关系Docker的三个核心概念我用做菜来类比镜像Image菜谱。它是一份只读的模板描述了怎么做出一道菜。菜谱本身不能吃但可以照着做无数道菜。容器Container做好的菜。它是镜像的运行实例可以吃使用可以加料修改但改的是这道菜不影响菜谱。仓库Registry菜谱图书馆。Docker Hub就是最大的公共图书馆你也可以建私人的私有仓库。技术层面的关系是镜像由多层文件系统叠加而成容器在镜像的最上层加了一个可写层。你所有的修改都在可写层删掉容器可写层也没了但镜像不变。docker pull nginx:latest # 从仓库拉镜像 docker images # 查看本地镜像 docker run -d -p 80:80 nginx # 从镜像创建并启动容器 docker ps # 查看运行中的容器 docker ps -a # 查看所有容器含已停止的 docker exec -it 容器ID bash # 进入容器内部 docker stop 容器ID # 停止容器 docker rm 容器ID # 删除容器 docker rmi nginx:latest # 删除镜像4.2 镜像分层为什么Docker能这么快镜像分层是Docker高效的核心原因。每个Dockerfile指令都会生成一层层与层之间是只读的可以共享。举个例子你有两个镜像都基于ubuntu:22.04那么ubuntu这一层在磁盘上只存一份两个镜像共用。这就是为什么你拉第二个基于ubuntu的镜像时下载速度飞快——基础层已经有了。docker history nginx:latest # 查看镜像的层结构输出会显示每一层的大小和创建命令。你会看到很多层的大小是0B那是因为它们只是元数据变更如ENV、CMD不产生实际文件变化。实操建议写Dockerfile时把不常变动的指令放前面常变动的放后面。这样构建时可以利用缓存只重建变化的那几层。比如FROM node:18 WORKDIR /app COPY package.json . RUN npm install # 依赖不常变放前面 COPY . . # 代码常变放后面 CMD [node, index.js]4.3 数据卷与端口映射容器和外界沟通的桥梁容器默认是封闭的数据写在容器里容器一删就没了。要让数据持久化或者让外部访问容器内的服务就需要数据卷和端口映射。端口映射docker run -d -p 8080:80 nginx # 宿主机的8080端口映射到容器的80端口 # 访问 http://localhost:8080 就能看到nginx页面-p参数的格式是宿主机端口:容器端口。可以映射多个端口也可以指定IPdocker run -d -p 127.0.0.1:8080:80 -p 8443:443 nginx数据卷有三种挂载方式# 1. 匿名卷Docker自动管理路径在/var/lib/docker/volumes/下 docker run -v /data mysql # 2. 命名卷指定一个名字方便管理 docker run -v mysql-data:/var/lib/mysql mysql # 3. 绑定挂载直接挂载宿主机的目录 docker run -v /home/user/mysql-data:/var/lib/mysql mysql绑定挂载最直观但有个坑权限问题。容器内的进程通常以非root用户运行如果宿主机目录的权限不对容器会报Permission denied。解决方案是确保宿主机目录的UID/GID和容器内用户匹配或者用--user参数指定用户。# 查看容器内用户的UID docker exec 容器ID id # 修改宿主机目录所有者 sudo chown -R 999:999 /home/user/mysql-data4.4 Docker网络模式容器之间怎么互相访问Docker默认创建三种网络docker network ls # bridge host nonebridge默认模式。每个容器分配一个独立IP通过docker0网桥和宿主机通信。容器之间可以用IP互访但IP会变不推荐。host容器直接使用宿主机的网络栈没有独立IP。性能最好但端口会冲突。none容器没有网络完全隔离。实际项目中我们通常自定义一个bridge网络这样容器之间可以用容器名互相访问docker network create myapp docker run -d --name mysql --network myapp mysql:8.0 docker run -d --name web --network myapp -p 8080:80 myweb # web容器里可以直接用 mysql 作为主机名连接数据库这个DNS解析是Docker内置的比用IP靠谱得多。容器重启后IP变了但名字不变连接不受影响。5. 实战用Docker部署常用服务5.1 MySQL 8.0的部署与初始化配置MySQL是Docker里最常部署的服务之一。一条命令搞定docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -e MYSQL_DATABASEmydb \ -e MYSQL_USERmyuser \ -e MYSQL_PASSWORDuserpassword \ -v mysql-data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci参数逐个解释-e MYSQL_ROOT_PASSWORDroot密码必须设置否则容器启动失败。-e MYSQL_DATABASE启动时自动创建的数据库。-e MYSQL_USER/PASSWORD创建一个普通用户只能访问MYSQL_DATABASE。-v mysql-data:/var/lib/mysql数据持久化删容器不丢数据。-v /etc/localtime:/etc/localtime:ro让容器时间和宿主机一致避免时区问题。最后的--character-set-server是传给mysqld的参数设置字符集。踩坑记录MySQL 8.0默认的认证插件是caching_sha2_password一些老版本的客户端连不上。如果遇到Authentication plugin caching_sha2_password cannot be loaded需要在MySQL里改ALTER USER myuser% IDENTIFIED WITH mysql_native_password BY userpassword; FLUSH PRIVILEGES;或者启动时加--default-authentication-pluginmysql_native_password。5.2 Redis主从复制的容器化搭建Redis主从用Docker搭非常方便。先起主节点docker run -d --name redis-master -p 6379:6379 redis:7再起从节点指定主节点地址docker run -d --name redis-slave -p 6380:6379 redis:7 \ redis-server --slaveof redis-master 6379但这样有个问题从节点用redis-master这个主机名需要和主节点在同一个自定义网络里。所以正确做法是docker network create redis-net docker run -d --name redis-master --network redis-net -p 6379:6379 redis:7 docker run -d --name redis-slave --network redis-net -p 6380:6379 redis:7 \ redis-server --slaveof redis-master 6379验证主从是否生效docker exec -it redis-master redis-cli set foo bar exit docker exec -it redis-slave redis-cli get foo # 应该输出 bar5.3 GitLab的容器化部署与资源调优GitLab比较重官方推荐至少4GB内存。用Docker部署docker run -d \ --name gitlab \ --hostname gitlab.example.com \ -p 443:443 -p 80:80 -p 2222:22 \ -v gitlab-config:/etc/gitlab \ -v gitlab-logs:/var/log/gitlab \ -v gitlab-data:/var/opt/gitlab \ --shm-size 256m \ gitlab/gitlab-ce:latest--shm-size很重要默认的64MB不够GitLab用会导致502错误。--hostname设置外部访问的域名影响GitLab生成的克隆地址。GitLab启动很慢第一次要等几分钟。可以用docker logs -f gitlab看进度。看到gitlab Reconfigured!就差不多了。资源调优GitLab默认会启动很多进程PostgreSQL、Redis、Nginx、Sidekiq等如果机器配置不高可以在/etc/gitlab/gitlab.rb里关掉一些prometheus_monitoring[enable] false grafana[enable] false然后docker exec -it gitlab gitlab-ctl reconfigure生效。5.4 Docker Compose多容器编排的正确打开方式一个个docker run太麻烦Docker Compose用一个YAML文件描述所有服务version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: appdb volumes: - mysql-data:/var/lib/mysql ports: - 3306:3306 networks: - app-net redis: image: redis:7 ports: - 6379:6379 networks: - app-net web: build: ./web ports: - 8080:80 depends_on: - mysql - redis networks: - app-net volumes: mysql-data: networks: app-net:然后docker compose up -d一键启动所有服务。docker compose down停止并删除容器数据卷保留。docker compose logs -f查看所有服务日志。depends_on只保证启动顺序不保证服务就绪。如果web启动时MySQL还没准备好连接会失败。解决方案是在应用层做重试或者用healthcheckmysql: image: mysql:8.0 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 56. 踩坑实录那些让我熬夜的Docker问题6.1 容器启动即退出排查思路与常见原因docker run之后docker ps看不到容器docker ps -a显示状态是Exited。这是最常见的问题原因通常有几种原因一主进程执行完毕就退出。容器的生命周期和主进程绑定主进程结束容器就退出。比如docker run ubuntuubuntu镜像默认执行/bin/bash但没有交互终端bash立刻退出。解决加-it参数进入交互模式或者让容器执行一个持续运行的命令。docker run -it ubuntu bash docker run -d ubuntu tail -f /dev/null原因二配置错误导致进程启动失败。比如MySQL没设root密码nginx配置文件语法错误。排查docker logs 容器ID看日志。如果日志没有有用信息可以覆盖entrypoint进入容器docker run -it --entrypoint bash mysql:8.0原因三端口冲突。宿主机端口已被占用容器启动失败。排查docker logs会显示bind: address already in use。用ss -tlnp | grep 端口号找到占用进程换端口或杀掉进程。6.2 镜像拉取慢与超时加速方案对比国内拉Docker Hub镜像慢是常态。解决方案是配置镜像加速器。修改/etc/docker/daemon.json{ registry-mirrors: [ https://mirror.ccs.tencentyun.com, https://docker.mirrors.ustc.edu.cn ] }然后sudo systemctl daemon-reload sudo systemctl restart docker。但镜像加速器经常失效因为各种原因。更稳定的方案是用国内云厂商的容器镜像服务它们通常提供Docker Hub的同步镜像。或者直接docker pull的时候指定完整地址docker pull registry.cn-hangzhou.aliyuncs.com/library/mysql:8.0如果还是慢可以考虑在境外服务器上拉好镜像docker save成tar下载到本地docker load。虽然麻烦但最可靠。6.3 数据卷权限问题UID/GID不匹配的解决前面提过绑定挂载的权限问题这里展开说。假设你在宿主机用普通用户创建了/home/user/data目录UID是1000。容器内的MySQL进程UID是999。MySQL尝试写入/var/lib/mysql挂载到/home/user/data发现目录所有者是1000自己没有写权限启动失败。解决方案有三种修改宿主机目录所有者sudo chown -R 999:999 /home/user/data。简单粗暴但宿主机上其他用户可能也需要访问这个目录。用命名卷代替绑定挂载-v mysql-data:/var/lib/mysql。Docker会自动处理权限不用操心。在Dockerfile里调整用户UID让容器内用户的UID和宿主机一致。这个最优雅但需要重新构建镜像。我个人的选择是开发环境用命名卷生产环境用绑定挂载明确的权限设置。命名卷省心但数据不好直接查看绑定挂载直观但权限要自己管。6.4 Docker Desktop启动失败虚拟化检测问题全解Windows上Docker Desktop启动失败90%是虚拟化相关的问题。完整的排查链路第一步确认CPU支持虚拟化。任务管理器→性能→CPU右下角看虚拟化是否为已启用。如果是已禁用进BIOS开启。第二步确认Windows功能已开启。控制面板→程序和功能→启用或关闭Windows功能确保勾选Hyper-V如果不用WSL2后端虚拟机平台Windows Subsystem for Linux如果用WSL2后端第三步确认WSL2已安装并设为默认wsl --install wsl --set-default-version 2 wsl --update第四步检查是否与其他虚拟化软件冲突。VMware Workstation 15.5以下版本和Hyper-V不兼容。如果装了旧版VMware要么升级要么在Docker Desktop设置里切换后端。第五步重置Docker Desktop。如果以上都没问题但还是启动失败在Docker Desktop菜单里选Troubleshoot→Reset to factory defaults。6.5 容器时间不对时区配置的几种方案容器默认用UTC时间和中国差8小时。这会导致日志时间对不上定时任务执行时间错误。解决方案挂载宿主机的localtimedocker run -v /etc/localtime:/etc/localtime:ro ...设置TZ环境变量docker run -e TZAsia/Shanghai ...在Dockerfile里设置ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezone方案1最简单但依赖宿主机的时区配置。方案2和3更独立推荐生产环境用。7. 从能用走向好用我的Docker日常使用心得7.1 镜像瘦身多阶段构建的实际效果镜像越小拉取越快占用磁盘越少安全风险也越低攻击面小。多阶段构建是瘦身利器# 构建阶段 FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED0 go build -o myapp . # 运行阶段 FROM alpine:3.18 RUN apk add --no-cache ca-certificates COPY --frombuilder /app/myapp /usr/local/bin/ CMD [myapp]最终镜像只有alpine基础层一个二进制文件可能不到20MB。而如果直接在golang镜像里运行镜像至少800MB。进一步瘦身用scratch作为基础镜像完全空镜像只放二进制文件。但scratch没有shell调试困难适合最终发布。FROM scratch COPY --frombuilder /app/myapp / CMD [/myapp]7.2 日志管理别让容器日志撑爆磁盘Docker默认的日志驱动是json-file日志无限增长。一个跑了几个月的容器日志文件可能几十GB。查看日志大小docker inspect --format{{.LogPath}} 容器ID | xargs ls -lh限制日志大小在/etc/docker/daemon.json里配置{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这样每个容器最多保留3个日志文件每个10MB总共30MB。重启Docker生效。已经运行的容器需要重建才能应用新配置。或者手动清理truncate -s 0 $(docker inspect --format{{.LogPath}} 容器ID)7.3 资源限制防止单个容器拖垮宿主机默认情况下容器可以使用宿主机的全部资源。一个内存泄漏的容器可能把整台机器搞挂。用--memory和--cpus限制docker run -d --memory512m --cpus1.5 nginx--memory512m最多用512MB内存超了会被OOM Killer杀掉。--cpus1.5最多用1.5个CPU核心。在Compose里services: web: image: nginx deploy: resources: limits: cpus: 1.5 memory: 512M reservations: cpus: 0.5 memory: 256Mlimits是硬限制reservations是软限制保证能拿到的资源。7.4 镜像构建缓存加速迭代的技巧Docker构建镜像时会缓存每一层。如果某一层没变直接用缓存不重新执行。利用这个特性可以大幅加速构建。技巧一把COPY命令拆开。先COPY依赖文件安装依赖再COPY源代码。这样改代码不会触发依赖重装。技巧二用.dockerignore排除不需要的文件。node_modules、.git、__pycache__这些不要拷进镜像既减小体积又避免缓存失效。# .dockerignore node_modules .git *.log __pycache__ .env技巧三构建时用--cache-from指定缓存来源。在CI/CD环境里可以从上次构建的镜像拉缓存docker build --cache-from myapp:latest -t myapp:new .7.5 安全习惯那些容易被忽略的细节Docker用起来方便但安全问题容易被忽视。几个基本习惯不要用root运行容器。在Dockerfile里创建普通用户RUN useradd -m appuser USER appuser不要挂载Docker socket到容器。-v /var/run/docker.sock:/var/run/docker.sock等于把宿主机root权限给了容器。除非你完全信任这个容器否则别这么干。定期更新镜像。基础镜像的漏洞会累积定期docker pull最新版并重建。扫描镜像漏洞。Docker Desktop内置了扫描功能命令行可以用docker scan需要登录Docker Hub。限制容器能力。默认容器有很多Linux capabilities用--cap-drop去掉不需要的docker run --cap-dropALL --cap-addNET_BIND_SERVICE nginx这些习惯看起来麻烦但养成之后就是顺手的事。安全这件事永远是预防成本远低于事后补救。7.6 学习路径建议从命令到原理的进阶路线最后说说学习路径。我自己的经验是分三个阶段第一阶段熟练常用命令。run、ps、exec、logs、stop、rm、images、rmi、pull、push这十个命令覆盖日常90%的操作。配合Docker Compose能部署大部分常见服务。第二阶段理解核心原理。Namespace、Cgroups、UnionFS、镜像分层、网络模型。不需要深入到内核源码但要知道大概是怎么回事。推荐读Docker官方文档的Get Started和Engine部分以及《Docker Deep Dive》这本书。第三阶段掌握编排和运维。Docker Compose进阶、Swarm或Kubernetes、CI/CD集成、监控和日志方案。这个阶段更多是在实际项目中积累没有捷径。我个人的体会是不要为了学而学。找一个实际的需求比如我要搭一个博客、我要部署一个开发环境然后带着问题去查、去试。遇到报错不要怕每个报错都是理解底层机制的机会。我踩过的那些坑现在回头看都是最有价值的学习经历。还有一点文档比教程靠谱。网上很多教程是几年前的命令和配置可能已经过时。遇到不确定的地方直接查官方文档。Docker的文档质量很高而且有中文版。最后分享一个我常用的调试技巧当容器行为不符合预期时用docker run -it --entrypoint bash 镜像进入容器手动执行entrypoint里的命令一步步看哪里出错。这比看日志快得多尤其是日志输出不完整的时候。