1. 从零开始理解 Docker:镜像、容器和仓库到底是什么
接触 Docker 这么久,我发现很多新手上来就急着敲命令,结果镜像拉不下来、容器起不来、数据一删就丢,折腾一晚上直接劝退。其实 Docker 的核心概念就三个:镜像、容器、仓库。把这三个东西想通了,后面所有操作都是顺水推舟。
镜像(Image)可以理解成一个“模板”,像装系统用的 Ghost 镜像一样,里面打包好了操作系统基础环境、运行时、依赖库和应用程序代码。你拉下来的镜像文件只读、不可修改,但是可以被复制、被导出、被反复使用。容器(Container)则是镜像运行时的实例,相当于用 Ghost 镜像装出来的一台台电脑。每个容器相互隔离,有自己的文件系统、网络栈和进程空间,但底层共用宿主机的内核。仓库(Registry)就更直白了,用来存放和分发镜像的地方,公开的 Docker Hub 就像手机应用商店,国内各大云厂商也有自己的镜像加速仓库。
这三者的关系我用一句大白话总结:镜像定义了“跑什么”,容器决定了“怎么跑”,仓库解决了“去哪拿”。搞懂这三件事,你就不会被 Docker 的各种概念绕晕了。
这篇教程我会从安装开始,覆盖镜像加速、MySQL 8.0 与 Redis 主从实战、docker compose 编排、以及高频故障排查,把我实际生产环境里踩过的坑和总结出来的经验全部写出来。适合刚接触容器化、或者已经部署过几个容器但对原理和排障还比较模糊的开发者。
2. 安装 Docker:Linux 和 Windows 两条路线怎么选
2.1 Linux 环境下用脚本一键安装
我最早是在 Ubuntu 服务器上接触 Docker 的,那会儿最怕的就是各种依赖冲突和源的问题。现在服务器端安装基本就两种方式:官方脚本和手动配置。对于绝大多数人,我推荐直接用官方安装脚本,省时省力。
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh脚本会自动识别系统版本,配置官方源,安装 Docker Engine、containerd 和 docker-compose-plugin 等一整套组件。执行完以后设置开机自启并验证版本:
sudo systemctl enable docker && sudo systemctl start docker sudo docker version如果系统是 CentOS、Debian 或者麒麟这类国产系统,脚本也都兼容。只是国内服务器访问官方脚本偶尔会超时,这种情况下可以先把脚本下载到本机再上传,或者直接改用清华、阿里云等开源镜像站配置源后手动安装。手动安装虽然步骤多,但胜在可控,对于内网环境尤其友好。
验证 Docker 是否装好,最快的方式是跑一个 hello-world 镜像:
sudo docker run --rm hello-world这条命令会从仓库拉取 hello-world 镜像并创建容器,容器执行完输出提示信息后自动退出,--rm参数让容器退出后自动清理,适合用来测试环境。如果能正常打印信息,说明整个链路已经通了。
2.2 Windows 安装 Docker Desktop 的完整步骤
Windows 上面装 Docker 的体验比 Linux 复杂不少,因为 Docker 本身是跑在 Linux 内核上的,Windows 需要依赖虚拟化技术。现在的 Docker Desktop 默认使用 WSL 2 后端,比老版本的 Hyper-V 方案轻量很多、启动也更快。
安装前先确认自己 Windows 版本是 10 64 位专业版/企业版/教育版,或者 Windows 11 任意版本。然后在“控制面板 -> 启用或关闭 Windows 功能”里勾选“适用于 Linux 的 Windows 子系统”,没有勾选的话后续大概率报 WSL 相关的错。
接下来以管理员身份打开 PowerShell,执行:
wsl --install装好 WSL 2 后重启电脑,再去 Docker 官网下载 Docker Desktop Installer.exe。双击安装时有个细节:安装向导里有一个 “Use WSL 2 instead of Hyper-V” 的勾选框,务必保持勾选状态。装完打开 Docker Desktop,等待引擎启动,看到鲸鱼图标变成绿色,就说明成功了。
有人会问:既然是本地开发环境,能不能不装 Docker Desktop,直接用 WSL 里的 Docker Engine?当然可以,甚至我后来在 Windows 上写项目时就是这么干的:在 WSL 的 Ubuntu 里装 Docker Engine,然后用 VS Code 的 Remote-WSL 插件连进去,既省掉了 Docker Desktop 这个图形壳,资源占用也更低。但如果你需要 GUI 界面、需要 K8s 单机集群支持、或者希望借助 Docker Desktop 统一管理容器和镜像,那还是直接装它更方便。
2.3 启动失败排查:virtualization support not detected 等常见坑
Windows 安装 Docker Desktop 最常见的错误就是标题里那句:Docker Desktop failed to start because virtualization support was not detected。出现这个提示,先别急着重装,按顺序排查三层问题:
第一层,BIOS 里虚拟化开关没开。开机按 F2/F10/Del 进入 BIOS,找到 Intel Virtualization Technology 或者 AMD SVM Mode,改成 Enabled 并保存退出。进系统后打开任务管理器 -> 性能 -> CPU,右下角能看到“虚拟化: 已启用”才说明 OK。
第二层,Windows 可选功能没启用。以管理员身份运行 PowerShell,执行systeminfo查看 Hyper-V 要求那一项,如果显示“已检测到虚拟机监控程序。将不显示 Hyper-V 所需的功能”,说明虚拟化功能已经被占用,这种情况下需要在“启用或关闭 Windows 功能”里把“Windows 虚拟机监控程序平台”和“虚拟机平台”都勾上。
第三层,VBS(基于虚拟化的安全性)导致冲突。有时候 WSL2 和 VBS 会抢资源,解决办法是在 PowerShell 里执行:
bcdedit /set hypervisorlaunchtype auto然后重启再试。如果一直卡在启动阶段,可以执行wsl --shutdown关闭所有 WSL 实例,再重启 Docker Desktop。我遇到过最离谱的情况是 VMware Workstation 和 WSL2 抢 Hyper-V,后来升级了 VMware 并开启 Hyper-V 支持才解决。
3. 镜像加速与存储配置:告别下载慢和 C 盘爆满
3.1 为什么镜像拉取这么慢,以及加速器配置方法
刚接触 Docker 的人大概率会问:为什么docker pull mysql:8.0拉了半天进度条都不动?原因说起来也简单:默认走的 Docker Hub 仓库服务器在国外,国内直连速度不稳定,尤其是大镜像,经常卡在某一层几十秒不动。
解决办法就是给 Docker 配置国内镜像加速器。加速器的原理是各大容器服务商把 Docker Hub 的热门镜像同步到了国内节点,客户端请求加速器地址时,从就近节点拉取数据,吞吐量提升非常明显。
以 Ubuntu 和 CentOS 为例,修改/etc/docker/daemon.json:
{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.nju.edu.cn" ] }改完执行sudo systemctl daemon-reload && sudo systemctl restart docker使配置生效。重启后用docker info命令查看Registry Mirrors字段,能看到已生效的加速器地址就说明配置成功了。
这里多说一句:网上有些加速器地址可能已经失效或者需要登记内测,建议配置多个备选地址。我之前踩过坑,只配了一个地址,结果过几天加速器维护,所有镜像都拉不下来了,后来配置了三个备选才稳定。实测下来,几 MB 的小镜像速度差别不明显,但像 MySQL、Redis、GitLab 这种几百 MB 甚至上 GB 的镜像,用加速器前后体感完全是两个级别。
3.2 daemon.json 里的其他实用配置项
很多教程只讲了 registry-mirrors,但 daemon.json 里还有几个高频配置,实际部署时非常有用。
第一个是"data-root",用来指定 Docker 的数据存储目录。默认情况下,Docker 的镜像、容器、卷全部存放在/var/lib/docker,如果系统盘不够大,几年下来会被镜像撑爆。我工作中常用的一招是先把 Docker 目录迁移到数据盘,修改 daemon.json:
{ "data-root": "/data/docker" }然后执行sudo systemctl restart docker。需要注意的是,迁移前要确保新目录有足够的磁盘空间,并且旧数据路径没有残留容器。如果只是想清掉缓存文件,用docker system prune -a也会释放大量空间,但会删掉所有未使用的镜像和容器,操作前务必确认没有需要保留的环境。
Windows 上同理,Docker Desktop 的Settings -> Resources -> Advanced里可以调整镜像存储位置和磁盘镜像大小,默认是放在 C 盘的,如果 C 盘空间紧张,建议尽早改到其他盘。
第二个是"log-driver"和"log-opts",控制容器日志的大小。默认情况下,容器日志无限增长,容易撑爆磁盘。我习惯给全局日志加上限制:
{ "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "3" } }这样每个容器单个日志文件不超过 20MB,最多保留 3 个文件。对于日志量大的服务(比如 Nginx、Java 应用),这条配置能显著延长磁盘寿命。不过要注意,daemon.json 里的 log 配置是全局生效的,默认情况下也会覆盖容器启动时未指定的日志配置。
第三个是"live-restore"。设置成 true 以后,Docker 守护进程重启时不会杀掉运行中的容器,这个在线上环境触不及防的维护场景里很实用,能避免重启 Docker 导致服务中断。
{ "live-restore": true }但要注意:部分老版本 Docker 对该功能的支持不完善,生产环境升级前建议先在测试环境验证。
3.3 拉取镜像后修改存储位置的思路
还有一个经常被问的问题:Docker 可以安装到其他盘吗?这个分两种情况。
如果是 Linux 原生 Docker,改>docker image prune -a docker container prune
前者删除所有未被容器引用的镜像,后者清理所有已停止的容器。结合日志限制,生产服务器跑几个月磁盘依然干净。
4. 实战部署:MySQL 8.0 安装与使用
4.1 创建网络与数据目录,先做前置准备
学习 Docker 最好的方式就是拿真实的服务练手。我选 MySQL 8.0 作为第一个实战项目,因为数据库对持久化、网络配置、端口映射的要求特别典型,把 MySQL 部署熟了,后面部署任何中间件都会轻松很多。
先创建一个专用的 Docker 网络,叫 mysql-net。为什么要自己建网络?因为 Docker 默认的 bridge 网络虽然也能容器互联,但容器重启后 IP 可能变化,服务和数据库之间用 IP 通信会不稳定。创建一个自定义网络后,容器之间可以通过容器名互相访问,IP 变了也不影响。
docker network create mysql-net接下来创建 MySQL 数据目录和配置文件目录。容器删除以后数据还能保留的关键,就是把宿主机目录挂载进容器,我习惯这样规划:
mkdir -p /data/mysql/{data,conf,logs}这三个目录分别用来存放实际数据、自定义配置文件和运行日志。接下来写一个基础配置文件/data/mysql/conf/my.cnf,核心参数如下:
[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default-storage-engine=INNODB max_connections=500 lower_case_table_names=1lower_case_table_names=1让表名不区分大小写,国内大多数业务都要求这个配置,避免开发环境 Windows 和线上 Linux 行为不一致。utf8mb4是必选字符集,兼容全量 Unicode 字符,包括 emoji,这是老项目谈之色变的编码问题。
4.2 拉取镜像并启动 MySQL 8.0 容器
前置工作做完后,拉取镜像并启动容器:
docker pull mysql:8.0镜像比较大,配合加速器的话大约一两分钟就能拉完。启动命令:
docker run -d \ --name mysql8 \ --network mysql-net \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPassword123 \ -v /data/mysql/data:/var/lib/mysql \ -v /data/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf \ -v /data/mysql/logs:/var/log/mysql \ --restart=always \ mysql:8.0逐个解释一下关键参数。-d表示后台运行,--name给容器命名,--network指定刚才创建的专用网络,-p 3306:3306把宿主机的 3306 端口映射到容器的 3306 端口,-e MYSQL_ROOT_PASSWORD设置 root 密码,-v是目录挂载,--restart=always表示 Docker 或宿主机重启后自动拉起容器。
启动后查看实时日志:
docker logs -f mysql8日志中如果出现ready for connections,说明 MySQL 已经启动成功。用宿主机上的 mysql 客户端验证一下:
mysql -h 127.0.0.1 -P 3306 -uroot -p如果宿主机没有安装 mysql 客户端,也可以进入容器操作:
docker exec -it mysql8 mysql -uroot -p这里有个细节,MySQL 8.0 默认的认证方式是 caching_sha2_password,有些老的客户端工具(比如 5.x 的 Navicat)连不上。需要改成 mysql_native_password:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'YourPassword123'; FLUSH PRIVILEGES;4.3 连接信息与常见使用误区
我见过不少人在使用容器化 MySQL 时掉进同一个坑:容器重启后数据丢了。实际上,只要你挂载了宿主机目录到/var/lib/mysql,即使容器被删除,数据还在宿主机上。删除容器后用同样参数重新 run 一次,数据就回来了。
另外要注意端口占用问题。如果宿主机已经有 MySQL 实例在跑,-p 3306:3306会启动失败,报错port is already allocated。解决方式有两个:要么停掉宿主机的 MySQL,要么把容器端口映射改到 3307,比如-p 3307:3306。在开发环境,我建议数据库这类基础设施尽量放在容器里跑,宿主机保持干净,也方便多版本切换。
MySQL 8.0 默认开启了二进制日志,日志增长非常快,尤其是建表频繁或者大批量导入数据时,/data/mysql/logs所在的磁盘经常被写满。经验之谈:除非要做主从,否则可以关闭二进制日志,或者定期清理:
PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;如果已经出现磁盘满的情况,且开启了 binlog,登录进去执行上面的清理语句,再配合docker system prune清除无用镜像,磁盘就释放出来了。
5. 容器化部署 Redis 主从复制,手把手配置
5.1 Redis 主从架构为什么值得用容器化方式部署
Redis 在中小型项目里的角色通常是缓存、Session 存储、或者排行榜这类实时数据服务。单机部署的 Redis 有一个致命问题:进程挂了数据就丢了,虽然有 RDB 和 AOF 持久化,但恢复也需要时间。主从架构可以做到自动故障转移(配合哨兵或 Cluster),从节点分担读压力,是生产环境的高频需求。
用 Docker 部署 Redis 主从有一个非常大的好处:复制配置文件超简单。传统方式下,你需要在一台机器上准备两套不同端口、不同配置文件的 Redis 实例,还要注意目录隔离。容器化以后,每个容器天然隔离,端口和数据目录自己管自己的,主从关系只需要几行配置就能搭好。
我的规划是:
- master 节点:容器名 redis-master,端口 6379
- slave 节点:容器名 redis-slave1,端口 6380
- slave 节点:容器名 redis-slave2,端口 6381
三个容器共用同一个自定义网络,通过内网互相通信,外部只能访问映射出来的端口。
5.2 编写 Redis 主从配置并启动容器
先准备好配置目录:
mkdir -p /data/redis/{master,slave1,slave2}主节点/data/redis/master/redis.conf内容如下:
bind 0.0.0.0 protected-mode yes port 6379 daemonize no dir /data appendonly yes appendfsync everysec requirepass RedisMasterPwd123这里说明三个重点。bind 0.0.0.0在容器环境下必须设置,否则 Redis 只监听容器回环地址,宿主机和其他容器都连不上;daemonize no防止 Redis 在容器内后台化,因为 Docker 容器需要前台进程作为主进程,一旦 Redis 转为后台进程,容器会认为进程结束了直接退出;requirepass给主节点设置密码,从节点同步也需要认证。
从节点redis.conf和主节点基本一致,只是端口不同,并多了replicaof和masterauth两行:
bind 0.0.0.0 protected-mode yes port 6380 dir /data appendonly yes appendfsync everysec requirepass RedisMasterPwd123 replicaof redis-master 6379 masterauth RedisMasterPwd123replicaof redis-master 6379告诉从节点主节点的主机名和端口。在自定义网络中,redis-master就是主节点的容器名。注意 Redis 5.0 之前语法是slaveof,之后的版本已经统一改成replicaof,虽然老参数兼容,但新项目建议直接用新语法。
启动主节点:
docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ -v /data/redis/master:/data \ -v /data/redis/master/redis.conf:/etc/redis/redis.conf \ --restart=always \ redis:7.0 redis-server /etc/redis/redis.conf注意最后的redis-server /etc/redis/redis.conf,它是容器启动命令。官方镜像的默认 CMD 命令是直接启动 redis-server,不指定配置文件的话,容器内所有自定义配置都不会生效,这是个非常容易踩的坑。
从节点启动命令完全相同,只需要把名字、端口、配置文件路径替换掉,例如:
docker run -d \ --name redis-slave1 \ --network redis-net \ -p 6380:6379 \ -v /data/redis/slave1:/data \ -v /data/redis/slave1/redis.conf:/etc/redis/redis.conf \ --restart=always \ redis:7.0 redis-server /etc/redis/redis.conf这里有个小技巧:从节点容器内部 Redis 仍然监听 6379 端口,只是映射到宿主机的 6380,所以配置文件里的port 6380反而不需要写,保持容器内的 6379 即可。不过为了可读性,我在配置里保留了 6380,这种情况下要保证/data/redis/slave1/redis.conf里的port和容器映射一致,否则客户端连接会出错。
启动后用 docker exec 进入从节点,执行:
docker exec -it redis-slave1 redis-cli -a RedisMasterPwd123 info replication如果看到role:slave且master_link_status:up,说明主从同步已经建立了。往 master 写一个 key,再到 slave 上能读出来,就说明同步链路完全正常。
5.3 主从切换和故障恢复的实操心得
主从配置好以后,不要以为就万事大吉了。实际生产中 Redis 主节点宕机后,从节点只会被动接受同步,不会自动提升为主节点,需要引入 Redis Sentinel(哨兵)或者 Cluster 模式才能实现自动故障转移。如果只是测试环境,手动切换也很快:
- 停掉主容器:
docker stop redis-master - 选一个从节点提升为临时主节点:
docker exec -it redis-slave1 redis-cli -a RedisMasterPwd123 replicaof no one- 把另一个从节点指向新的主节点:
docker exec -it redis-slave2 redis-cli -a RedisMasterPwd123 replicaof redis-slave1 6379这样架构就变成了 slave1 为主、slave2 为从。但要注意,容器 IP 和容器名在 Docker 内部是动态解析的,一旦原主节点被重新启动,这个提升过程会被打断。生产环境建议直接上哨兵模式,三个节点加三个哨兵进程。
我还想特别提醒一个坑:从节点一旦配置了replicaof,它就变成了只读节点,往从节点写入数据会报错READONLY You can't write against a read only replica。有朋友因为分不清请求打到了从节点,调试半天以为代码有问题,结果发现是客户端配置的端口是 6380。业务连接时一定要明确读写分离策略:读可以走从节点,写必须走主节点。
6. docker compose 编排:一条命令拉起整套环境
6.1 为什么单条 docker run 命令不够用
当你管理的容器变多,比如 MySQL、Redis、应用服务三个容器,每条都用docker run启动不仅命令冗长,参数还容易写错,而且不便于版本管理。docker compose 的定位就是解决单容器编排的问题:用 YAML 文件描述整套环境的所有服务和配置,一条docker compose up -d全部搞定。
我最早被 docker compose 吸引,是因为用一个docker-compose.yml文件就能把某套项目的全部中间件配置下来,换机器部署时只需要把目录和文件拷过去,一条命令环境就起来了。相比逐个敲 docker run,效率和可维护性完全是两个维度。
当前最新版本 docker compose 已经不需要单独安装 compose 插件,因为 Docker Engine 20.10+ 的安装包通常会包含docker-compose-plugin,直接使用docker compose命令即可。老版本的docker-compose命令和docker compose主要区别是中间多了一个空格,语法大部分兼容。
6.2 MySQL + Redis 主从的 compose 文件实战
接下来用一个真实案例展示 docker compose 的编写。我把前面手动部署的 MySQL 8.0 和 Redis 主从环境统一写成docker-compose.yml,这样后续换机器部署时不用记一堆 docker run 参数。
version: "3.8" services: mysql8: image: mysql:8.0 container_name: mysql8 restart: always ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: YourPassword123 TZ: Asia/Shanghai volumes: - /data/mysql/data:/var/lib/mysql - /data/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:cached - /data/mysql/logs:/var/log/mysql networks: - backend redis-master: image: redis:7.0 container_name: redis-master restart: always ports: - "6379:6379" volumes: - /data/redis/master/redis.conf:/etc/redis/redis.conf command: redis-server /etc/redis/redis.conf networks: - backend redis-slave1: image: redis:7.0 container_name: redis-slave1 restart: always ports: - "6380:6379" volumes: - /data/redis/slave1/redis.conf:/etc/redis/redis.conf command: redis-server /etc/redis/redis.conf depends_on: - redis-master networks: - backend networks: backend: driver: bridge这里有几个 compose 专属的配置细节值得注意。volumes里的:cached是 macOS 上的优化选项,在 Linux 上无效,我保留它是为了兼容 Mac 开发环境。depends_on只控制启动顺序,并不能保证服务已经就绪——也就是说,redis-slave1会在redis-master容器启动后立即启动,但 Redis 主进程可能还没有 ready,从节点连接不上的话会不断重试,这也算是一个默认行为下的容错机制。
写完文件后执行:
docker compose up -d查看状态:
docker compose ps如果某个服务启动失败,先看日志:
docker compose logs mysql8停止所有服务用docker compose down,停止并删除数据卷用docker compose down -v。请注意,down -v会把 volumes 里配置的数据卷一并删除,数据就真没了,这个命令在生产环境一定要谨慎,我见过有人手滑把本地数据全部清空,血泪教训。
6.3 环境变量和 .env 文件的使用技巧
compose 文件里如果把数据库密码、端口这些参数硬编码进去,换环境的时候改起来很麻烦。更专业的做法是引入.env文件,在docker-compose.yml里通过${VAR}引用变量。
定义.env:
MYSQL_ROOT_PASSWORD=YourPassword123 MYSQL_PORT=3306 REDIS_REQUIREPASS=RedisMasterPwd123 REDIS_MASTER_PORT=6379 REDIS_SLAVE1_PORT=6380修改docker-compose.yml对应位置:
mysql8: ports: - "${MYSQL_PORT}:3306" environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}其实 compose 文件本身不需要预设一个固定密码,也可以使用MYSQL_RANDOM_ROOT_PASSWORD: "yes"让系统随机生成密码,然后从容器日志里查看。这种方式更适合临时测试环境,生产环境不建议,因为密码不好管理。
.env文件要加入.gitignore,避免把密码提交到代码仓库。部署时只需要分发.env和docker-compose.yml,别人不需要知道密码也能了解整套服务结构。
7. 高频实战场景:部署 GitLab、KodBox 和打包镜像
7.1 用 Docker 快速搭建 GitLab 代码仓库
GitLab 是很多团队内部代码托管的第一选择,但原生安装依赖非常多,装了容易、维护难。用 Docker 部署可以把这些乱七八糟的依赖全部隔离掉,缺点是镜像特别大,启动也比较慢,首次启动可能要等几分钟。
先准备挂载目录:
mkdir -p /data/gitlab/{config,logs,data}启动命令:
docker run -d \ --name gitlab \ --network backend \ -p 80:80 \ -p 443:443 \ -p 22:22 \ -v /data/gitlab/config:/etc/gitlab \ -v /data/gitlab/logs:/var/log/gitlab \ -v /data/gitlab/data:/var/opt/gitlab \ --restart=always \ gitlab/gitlab-ce:latest这里映射了三个端口:80 是 Web 访问,443 是 HTTPS,22 是 SSH 提交代码用的。如果宿主机的 80 或 22 端口已经被其他服务占用,建议改成非标准端口,比如8080:80和2222:22。改完以后,需要通过外部 URL 访问 GitLab,需要在/data/gitlab/config/gitlab.rb中修改:
external_url 'http://gitlab.example.com:8080' gitlab_rails['gitlab_shell_ssh_port'] = 2222启动过程中可以用docker logs -f gitlab观察。看到类似gitlab Reconfigured!的日志时,说明 GitLab 配置完成。初始管理员账号是root,初始密码会在首次启动时生成并打印在日志里,或者存放在/etc/gitlab/initial_root_password文件中,该文件会在 24 小时后自动删除。
GitLab 是资源大户,最低 4GB 内存才能跑得流畅,8G 内存会更从容,我这台测试机 8G 内存跑起来 CPU 占用率长期在 20% 左右。部署 GitLab 前建议检查可用内存和磁盘,否则即使容器能启动,访问起来也很卡。
7.2 私有云盘 KodBox 的一键部署
如果你的需求是搭建一个私有网盘,KodBox 是个不错的轻量选择,官方提供了容器镜像,部署非常简单:
docker run -d \ --name kodbox \ -p 8088:80 \ -v /data/kodbox:/var/www/html \ --restart=always \ kodcloud/kodbox部署完成后访问http://服务器IP:8088,按 Web 向导初始化。KodBox 安装向导要求填写数据库信息,也就是说它需要依赖一个 MySQL 实例。可以复用前面部署的 MySQL 8.0,创建一个专门给 KodBox 用的数据库和用户。KodBox 的体积很小,部署速度非常快,适合个人用户快速搭建私有云盘。
这里有一个细节:KodBox 默认会把上传的文件存储在容器内的/var/www/html/data目录,我把整个/var/www/html挂载出来了,这样升级容器时数据不会丢。升级 KodBox 镜像时,先备份宿主机上的/data/kodbox目录,再拉新镜像重新创建容器,旧数据挂载进去后会自动识别。
7.3 PHP 和 Java 项目怎么用 Docker 打包镜像
程序员问得最多的问题之一就是:我写了代码,怎么把项目打成 Docker 镜像?这里我分别说 PHP 和 Java 两类最常见的情况。
PHP 项目的打包思路是基于官方 PHP 镜像,把代码复制进去。Dockerfile示例如下:
FROM php:8.2-fpm RUN docker-php-ext-install pdo_mysql mysqli WORKDIR /var/www/html COPY . /var/www/html EXPOSE 9000 CMD ["php-fpm"]然后执行docker build -t my-php-app .即可构建出镜像。这个手法对 PHP 项目特别友好,因为官方 php 镜像里已经把编译扩展的工具链都准备好了,docker-php-ext-install一条命令就能装好 pdo_mysql、redis 等扩展,不用手动解决依赖。
Java 项目通常用多阶段构建,先用 Maven 镜像编译出 jar,再复制到精简的 JRE 镜像里运行,最终镜像体积会小很多:
FROM maven:3.8-jdk-11 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests FROM openjdk:11-jre-slim WORKDIR /app COPY --from=builder /app/target/my-app.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]第一个 FROM 是构建阶段,用来编译项目;第二个 FROM 是运行阶段,只需要一个运行环境和一个 jar 包。多阶段构建是减少最终镜像体积的必经之路,一个几 MB 的 jar 包如果直接用 maven 镜像当运行环境,体积可能膨胀到几百 MB,多阶段构建后只需要 JRE 基础镜像就够了。
在 IntelliJ IDEA 里,可以通过 Docker 插件实现“一键打包并部署”。安装 Docker 插件后,在 Run/Debug Configuration 里选择 Dockerfile 文件指定好路径,点击运行就会自动 build 镜像并启动容器。这种方式很适合本地开发调试,改完代码直接跑容器,不需要手动敲 docker build 命令。
8. 常用命令速查与故障排查实录
8.1 日常操作必会命令清单
Docker 的命令多但成体系,掌握核心命令就能覆盖大多数场景。我把频率最高的命令整理成了一张速查表,新手照着用就行。
| 操作 | 命令示例 | 说明 |
|---|---|---|
| 查看容器 | docker ps -a | 查看所有容器状态,不加-a只看运行中的 |
| 进入容器 | docker exec -it <容器名> bash | 进入容器操作内部环境 |
| 查看镜像 | docker images | 列出本地所有镜像 |
| 构建镜像 | docker build -t 名称:版本 . | 在当前目录找 Dockerfile 构建 |
| 查看日志 | docker logs -f <容器名> | 实时跟踪容器日志输出 |
| 停止/启动 | docker stop <容器名>/docker start <容器名> | 停止和启动已有容器 |
| 删除容器 | docker rm -f <容器名> | 强制删除容器 |
| 删除镜像 | docker rmi <镜像名> | 删除本地镜像 |
| 清理资源 | docker system prune -a | 清理未使用的镜像、容器、网络 |
| 查看资源占用 | docker stats | 实时查看容器 CPU、内存、网络 |
| 端口映射查看 | docker port <容器名> | 查看容器的端口映射关系 |
补充一个非常实用的排查技巧:docker inspect <容器名>可以查看容器的完整配置,包括网络模式、IP 地址、挂载情况、环境变量、健康检查结果等等。排查网络问题的时候我会先用这个命令确认容器实际 IP 和挂载路径,调试问题能少走很多弯路。
8.2 服务启动失败的常见原因与处理思路
场景一:docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock
Linux 上出现这个报错,通常原因是当前用户不在 docker 组里,或者 Docker 服务没启动。解决方式:
sudo systemctl status docker sudo systemctl start docker # 如果权限问题,把当前用户加入 docker 组 sudo usermod -aG docker $USER newgrp docker场景二:Windows 下failed to connect to the docker api at npipe:////./pipe/docker_engine
这是 Docker Desktop 引擎没启动,或者 WSL 2 环境异常导致的。我的排查顺序是:先确认 Docker Desktop 是否在托盘区运行,再执行wsl --shutdown后重启,然后打开 Docker Desktop 等引擎图标变绿,如果还不行就重置 WSL 发行版。
场景三:容器秒退,日志为空
常见原因是容器内的主进程没有以前台方式运行。比如启动 Redis 时忘记使用redis-server /etc/redis/redis.conf,而 Redis 默认配置是后台运行,容器以为进程结束就退出了。解决办法是确认命令里主进程保持前台运行。
场景四:端口映射失败port is already allocated
宿主机的端口已经被占用,用sudo netstat -tunlp | grep 端口号看看是哪个进程占用的,改用其他映射端口即可。
场景五:容器内时区错误
默认时区是 UTC,国内应用会出现日志时间差 8 小时的问题。在 docker run 里增加环境变量解决:
-e TZ=Asia/Shanghai场景六:docker pull一直超时
参考第 3 节的内容配置镜像加速器,或者检查服务器网络是否能够访问外部镜像仓库。
8.3 Kafka 和 DVWA 等特殊镜像的几个注意点
热搜词里有人搜 Kafka 部署报错error while fetching metadata with correlation id,这个问题在容器化 Kafka 环境里非常典型。Kafka 部署时,客户端通过bootstrap.servers获取元数据时,如果 Kafka 配置里暴露的advertised.listeners是容器内网 IP,而宿主机客户端通过映射端口访问,就会出现这个错误。简单来说,宿主机客户端请求的 IP:端口和实际能够连通的地址对不上。
Kafka 部署得用KAFKA_ADVERTISED_LISTENERS指定外部可达的地址,斜杠后是监听协议。比如容器部署在宿主机上,外部通过PLAINTEXT://宿主机IP:9092访问,那么就需要这么配置:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://192.168.1.100:9092 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092另一个常见的搜素词是 Kali 搭建 DVWA 靶场,DVWA(Damn Vulnerable Web Application)是做 Web 安全测试训练用的靶场环境,通过 Docker 部署可以避免污染宿主机环境:
docker run -d -p 8080:80 vulnerables/web-dvwa启动后访问http://localhost:8080,默认账号 admin 密码 password。这个镜像自带 MySQL 配置向导,安装完后需要登录并初始化数据库才能正常使用。DVWA 部署本身很简单,环境上越隔离越好,容器化也是网络安全爱好者和相关专业学生做实验的好帮手。
8.4 Docker Desktop 在 Windows 上的特殊维护
Windows 用户还需要知道 Docker Desktop 的“Close”和“Restart”其实只是关掉前端程序,WSL 2 里的 docker daemon 可能还在运行。如果你改了daemon.json或者重置了网络,建议在任务栏图标菜单里选Restart,让整个引擎彻底重启,否则配置不生效。
Docker Desktop 有一个容易忽略的坑:Windows 防火墙可能会拦截容器端口转发,导致宿主机能访问localhost:3306,但局域网内其他机器访问不了。排查方式:在防火墙高级设置里添加入站规则,允许 Docker 使用的端口;或者直接关闭防火墙测试,确认问题后放行具体端口。还有如果 Docker Desktop 设置里选的网络模式是 NAT,没那么容易出这种问题,但改过网络模式的话要多留个心眼。
另外,Docker Desktop 的虚拟机磁盘文件默认是动态增长,每次 build 镜像或拉取大镜像都会占用磁盘。经常用 Docker 的话建议在Settings -> Resources里手动设置一个 disk size 上限,比如 64GB,并在磁盘空间紧张时用docker system prune清理缓存,避免磁盘占满导致 WSL 直接崩溃。
9. 我个人在长期使用 Docker 后的一些体会
前面讲了很多操作细节和踩坑处理,最后聊一点实践经验。如果你是从零开始学 Docker,我的建议非常简单:不用追求把所有概念和命令都背下来,先跑起来一个 MySQL、一个 Redis,把“拉镜像、起容器、看日志、进容器、删容器”这个流程走一遍,比看十篇教程都管用。
我自己的学习路径是:一开始在虚拟机里反复练习 docker run,不小心删过数据库、清空过环境,也从这些失误里学会了备份的重要性。等命令熟悉以后,再花一个晚上把 docker compose 用起来,你的部署能力会上一个台阶。等到需要多机部署、需要弹性扩缩容的时候,再去学 Docker Swarm 或者 K8s,基础扎实了之后上手也快。
最后再分享一个小技巧:所有 docker run 命令我都建议加上--restart=always,所有需要持久化的目录都明确挂载到宿主机,所有密码都不要写死在命令历史里,放到.env文件中管理。这三点守住了,线上出问题的概率能降一大半。Docker 不是银弹,但掌握了正确的使用姿势,它确实能从“玩具”变成真正提高生产力的工具。