先把这个“Dock”说清楚
先说个容易踩的坑。你搜"Dock的安装部署",网上能翻出两拨东西:一拨是苹果Mac电脑底下的那个Dock栏,怎么调自动隐藏、怎么改图标大小;另一拨是真正的容器引擎Docker,用来跑Nginx、MySQL、Redis这些服务。结合热度榜里"docker安装部署""k8s安装部署""jenkins安装部署"这些词,我判断你要的是后者——哪怕把字母打成了Dock,也不影响它火到今天这个程度。
Docker能干的事,说白了就一句话:把你的应用连同它依赖的运行环境一起打包成一个标准盒子,扔到哪台机器上都能直接跑,不用再为"我这环境怎么跟你那儿不一样"头疼。它的安装部署并不复杂,基础命令也就二三十条,但很多人栽在几个隐蔽的地方,比如内核版本不对、镜像源没配、权限没处理、容器退出找不着原因。这篇文章就按我从零部署一台机器到跑起真实项目的完整流程来写,把我踩过的坑和验证过的命令全部放进来,照着敲基本能一次过。
这套东西适合谁?一是刚接触云原生、准备折腾CI/CD或者Kubernetes的运维和开发,Docker是绕不开的第一道门槛;二是自己买了个云服务器、想在上面跑个小网站或应用但受够了环境配置的人。老手也能扫一眼命令排查部分,当个速查表用。
1. 安装前的准备:先搞清楚Docker在什么环境下才能跑
1.1 系统要求和内核检查
Docker对Linux的依赖比大多数人想象得深。它不是像普通软件那样装完就完事,而是要借助Linux内核的namespace(命名空间)、cgroups(控制组)、overlayfs(联合文件系统)这些机制来隔离进程和文件。所以内核版本太旧,功能就是缺的,装上也跑不起来。
不同系统版本对应关系大概是这样的:
| 操作系统 | 支持版本要求 | 说明 |
|---|---|---|
| Ubuntu | 内核3.10及以上,官方推荐20.04/22.04/24.04 | 老版本Ubuntu 16.04也能装,但建议不要用 |
| CentOS/RedHat | 7.x以上,内核3.10初始版本可能不够 | CentOS 7需要额外开启overlay2存储驱动 |
| Debian | 8.0+(老版本建议升级) | 12.x体验最好 |
| Windows | Win10/11 64位,必须开启WSL2或Hyper-V | 只能跑在Docker Desktop里 |
| macOS | 10.14+(Intel),11.0+(Apple Silicon) | 同样借助Docker Desktop |
查看Linux内核版本用下面这条命令:
uname -r如果输出类似3.10.0-1160.el7.x86_64或者5.15.0-91-generic这种3.10以上的版本号,基本没问题。CentOS 7还有个隐藏操作——需要额外确认overlay2驱动被正确加载,因为老内核默认走的可能是devicemapper,性能差一个量级。用docker info可以查看当前存储驱动。
1.2 清理旧版本:避免新老冲突
如果你机器上曾经装过Docker,或者系统自带了Podman、containerd这些容器运行时(有的Linux发行版确实默认装了一部分),建议先清理干净再装新的,不然经常出现命令找不到、服务起不来的奇怪问题。
# 查看是否装了旧版docker which docker docker --version # 如果存在,先停服务再卸载 sudo systemctl stop docker sudo apt-get remove docker docker-engine docker.io containerd runc # Ubuntu/Debian sudo yum remove docker docker-client docker-common docker-engine # CentOS这一步不做也行,但如果后面装完docker run hello-world报"command not found"或者"cannot connect to the Docker daemon",回来检查这个地方。
1.3 选对安装方式:官方脚本最省心
Docker的安装方式有好几种,官方推荐的是设置仓库后用包管理器安装,这样后续升级、卸载都方便;还有一个Docker官方提供的一键安装脚本,适合新手快速上手:
curl -fsSL https://get.docker.com | bash这个脚本会自动识别系统发行版、配置好官方仓库、装上所有依赖,然后启动服务。我试过在Ubuntu 20.04、22.04、Debian 11/12上执行,成功率很高。唯一的问题是网络不好的时候下载慢,解决办法是给这个脚本指定镜像源:
curl -fsSL https://get.docker.com | sudo sh -s -- --mirror Aliyun注意用--mirror Aliyun只是下载脚本和安装包时会从国内镜像拉,并不等于后面拉镜像就快了。镜像加速是另一回事,后面专门讲。
2. 主流系统上的完整安装部署
2.1 Ubuntu/Debian 系:仓库安装法
如果你想一步步来,不走一键脚本,那用仓库安装最标准。先装几个依赖工具:
sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-release然后添加Docker官方GPG密钥和仓库:
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 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这里有个坑:$(lsb_release -cs)会返回你的系统代号,比如jammy。如果你用的是Ubuntu 22.04,但内核/软件源是老的,那个代号可能解析不对,仓库就404了。遇到这种情况,直接把代号写死就行。
更新源并安装:
sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin注意现在Docker官方已经把docker-compose插件、buildx构建工具都拆开单独打包了,如果后续要跑docker-compose,记得把最后的docker-compose-plugin一起装。装完之后启动服务并验证:
sudo systemctl start docker sudo systemctl enable docker sudo docker run hello-worldhello-world是个只有几KB的小镜像,如果输出Hello from Docker!就说明整套流程通了。
2.2 CentOS/RHEL 系:yum安装法
CentOS 7虽然已经过了主流支持期,但存量服务器还很多,所以单独说。先装依赖:
sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo安装Docker CE:
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginCentOS 7上如果报缺少container-selinux依赖,手动装上再重试:
sudo yum install -y container-selinux启动服务:
sudo systemctl start docker sudo systemctl enable dockerCentOS 7有个特殊问题:默认存储驱动可能是devicemapper,性能很差,而且经常出现镜像层损坏。解决办法是在/etc/docker/daemon.json里明确指定用overlay2(这个文件一会儿细说)。改完重启Docker服务。
2.3 Windows 和 macOS:Docker Desktop 方案
Windows系统不能直接跑Linux容器,必须靠WSL2或者Hyper-V在虚拟机里跑一个轻量Linux内核。Docker Desktop就是个图形化的壳,帮你在底层自动搞定这些事。
安装步骤很简单:下载Docker Desktop安装包(一般100MB左右),双击安装,勾选"Use WSL 2 instead of Hyper-V"这个选项,然后重启电脑。装完后打开Docker Desktop,设置里把"Start Docker Desktop when you sign in"勾上,平时就不会忘了启动。
macOS也是同样思路,Apple Silicon芯片(M1/M2/M3)有专门版下载,别下错成Intel版。装完以后Linux容器在后台跑得顺滑,基本感知不到虚拟机的存在。
Windows下有个常见的坑:如果你同时装了VMware Workstation,WSL2和Hyper-V可能跟它冲突,导致虚拟机起不来。要么用Docker Desktop的配置改成WSL2后端,要么开Hyper-V后VMware得用15.5+版本才能共存。
2.4 全局配置:启动需要的都在 daemon.json
装完Docker后,绝大多数生产环境的初始化配置都会落在/etc/docker/daemon.json这个文件里。它相当于Docker守护进程的总配置,常见项包括镜像加速、日志大小限制、存储驱动、数据目录位置。
我自己的服务器上一般长这样:
{ "registry-mirrors": ["https://docker.m.daocloud.io"], "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" }, "data-root": "/data/docker", "exec-opts": ["native.cgroupdriver=systemd"] }解释一下每一项的作用:
registry-mirrors:镜像加速地址,拉镜像的时候会先去这里找,找不到再回官方源。这一步非常关键,不配置的话国内拉个MySQL镜像都能看到进度条以个位数KB/s在爬。log-driver和log-opts:限制容器日志文件大小。没有这个配置,一个容器长期跑下来日志文件能占满整个磁盘,太常见了。>sudo systemctl restart docker2.5 非root用户权限:别整天sudo
Docker刚装完,只有root用户能直接执行
docker命令,其他用户要加sudo才能跑。每次都打sudo docker太痛苦,正确做法是把当前用户加入docker组:sudo usermod -aG docker $USER newgrp docker执行完
newgrp docker或者重新登录终端,当前用户就能直接敲docker ps了。这里要注意一个安全隐患:能操作Docker的用户实际上等同于拥有root权限,因为可以挂载宿主机任意目录到容器里再进去改文件。所以在多用户服务器上,加docker组的操作要谨慎,只给真正需要的人加。
3. 基础命令全集:从镜像到容器的一次完整理解
3.1 镜像相关命令:应用的"源代码包"
Docker镜像就是应用运行时的"模板",它包含了代码、运行时、系统库、配置文件等等。几乎所有操作都从镜像开始。
命令 作用 常见参数/说明 docker pull 镜像名:标签从仓库拉取镜像 标签不写默认latest docker images查看本地已有镜像 -a查看全部,--digests显示摘要docker search 关键词在仓库搜索镜像 一般用的少 docker rmi 镜像ID/名称删除镜像 -f强制删除docker tag 原镜像 新名称:新标签给镜像打标签 常用于推送到私有仓库 docker save -o 文件.tar 镜像名导出镜像为tar文件 离线环境迁移用 docker load -i 文件.tar导入tar文件为镜像 配合save使用 拉镜像举例:
docker pull nginx:1.25-alpinealpine版本比完整版小很多,适合生产环境用,完整版的nginx有100多MB,alpine版只有不到50MB,功能基本一样。在没有外网的环境里,docker save和docker load是迁移镜像的救命稻草。删镜像有个坑:如果某个镜像正被容器使用(哪怕容器已停止),直接删会报冲突,提示你"image is being used by stopped container"。需要先删除对应的容器,或者用
-f强制删,但强制删容易留下悬空镜像,不推荐。一般流程是先docker rm 容器ID,再docker rmi 镜像ID。3.2 容器生命周期命令:应用运行时的"沙盒"
镜像跑到内存中就变成了容器。容器的启动、停止、删除是使用频率最高的命令。
命令 作用 说明 docker run [参数] 镜像 [命令]创建并启动容器 最重要,参数极多 docker ps查看运行中的容器 -a看所有(含已停止)docker start 容器ID启动已存在的容器 不会重新创建 docker stop 容器ID优雅停止容器 默认等待10秒再kill docker restart 容器ID重启容器 等同stop+start docker rm 容器ID删除容器 需先stop docker logs 容器ID查看容器日志 -f实时跟踪docker run是最核心的命令,常见参数组合如下:docker run -it -d --name mynginx -p 8080:80 -v /data/nginx:/usr/share/nginx/html nginx拆开看每个参数:
-it:用交互模式运行。-t分配一个伪终端,-i保持标准输入打开。如果只是想跑后台服务,可以只写-d。-d:后台运行(detached),容器启动后回到宿主机命令行。--name:给容器起个名字,后面操作就不用记那一长串容器ID了。-p 8080:80:端口映射。宿主机8080端口转发到容器的80端口。左边是宿主机,右边是容器,别搞反了。-v /data/nginx:/usr/share/nginx/html:数据卷挂载。把宿主机/data/nginx目录直接挂到容器内部那个目录,改宿主机文件相当于改容器文件,容器删了数据还在。- 最后一个
nginx是镜像名。
3.3 进入容器和查看详情:排查问题的基础
容器跑起来后,如果想进去里面看环境、敲命令,用
exec:docker exec -it mynginx bash-it同样表示交互模式。如果容器里没有bash(很多alpine镜像只有sh),就改成docker exec -it mynginx sh。查看容器详情用
inspect:docker inspect mynginx这个命令会输出一长串JSON格式的信息,包括容器IP、端口映射、挂载卷、环境变量、启动参数等等。排查问题的时候非常有用,尤其是看容器内部的IP地址和环境变量。
docker inspect --format='{{.NetworkSettings.IPAddress}}' mynginx这种
--format的写法可以直接提取某个字段,不用翻整段JSON。看资源占用用
top和stats:docker top mynginx docker statsdocker stats会实时显示每个容器的CPU、内存、网络、磁盘IO情况,和宿主机上top的效果类似。排查内存泄漏、CPU飙高问题时第一个就会想到它。3.4 网络和数据卷:容器间通信与数据持久化
默认情况下,Docker容器是隔离的网络环境,容器和宿主机、容器和容器之间网络都是独立的。要让外界访问容器里的服务,最常用的就是前面提到的
-p端口映射。Docker里自带几种网络模式:
docker network ls默认有bridge、host、none三种。单机部署时用bridge就够了;如果想多个容器之间互相访问,比如Nginx反向代理后面的Node.js容器,可以把它们放到同一个自定义网络里,用容器名直接互相ping通:
docker network create my-net docker run -d --name nginx --network my-net nginx docker run -d --name app --network my-net myapp:latest在自定义网络里,
app容器可以直接通过nginx这个名字访问Nginx容器,不需要查IP,这比手动管理IP方便太多了。数据卷Volume是Docker持久化存储的一个官方方案:
docker volume create>docker run -d \ --name web-nginx \ -p 80:80 \ -v /data/wwwroot:/usr/share/nginx/html:ro \ -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \ nginx:1.25-alpine这里我额外挂载了
conf.d目录,用来放Nginx的站点配置文件。:ro表示只读挂载,宿主机上的文件容器内只能读不能改,杜绝了容器里被人篡改的可能。启动完成后,用
curl -I http://localhost看返回的状态码,200就说明服务正常。用docker logs web-nginx可以看访问日志,Nginx的访问日志默认输出到标准输出,正好被Docker日志机制收集。4.2 部署MySQL并实现数据持久化
有状态服务比无状态服务麻烦得多,核心就是数据不能丢。这里我演示一个生产环境中比较稳妥的MySQL 8.0部署方案:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=你的密码 \ -e TZ=Asia/Shanghai \ -v mysql-data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci几个关键点:
MYSQL_ROOT_PASSWORD环境变量是初始化时设置root密码用的,要设置一个足够复杂的密码。-v mysql-data:/var/lib/mysql:这里用的是命名数据卷,数据存到宿主机Docker管理的卷里,容器删了重建数据还在。TZ=Asia/Shanghai和挂载/etc/localtime:防止容器内时间跟宿主机差8小时。时间错乱会导致日志时间不对,排查问题容易误判。- 最后面的
--character-set-server参数不是Docker的,而是传给MySQL服务的启动参数。MySQL 8.0默认字符集已经是utf8mb4,但显式指定更稳妥。
验证服务是否正常:
docker exec -it mysql8 mysql -uroot -p输入密码后能进入MySQL命令行就说明一切正常。
4.3 用docker compose编排多容器
当容器变多,手动敲
docker run就不现实了。docker compose可以把所有容器定义在一个docker-compose.yml文件里,一条命令全部搞定。举个Nginx+MySQL的简单例子:version: '3.8' services: web: image: nginx:1.25-alpine ports: - "8080:80" volumes: - /data/wwwroot:/usr/share/nginx/html:ro depends_on: - db db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 你的密码 TZ: Asia/Shanghai volumes: - mysql-data:/var/lib/mysql volumes: mysql-data:文件放在项目的根目录,然后执行:
docker compose up -d-d表示后台运行。之后用docker compose ps看服务状态,docker compose logs -f看日志,docker compose stop停止所有服务,不用的环境下线后还能用docker compose down把容器和默认网络一起清掉。第一次用compose的人经常犯一个错误:改了
docker-compose.yml里的配置后,直接执行docker compose up -d发现容器没变化。实际上要加--build参数重新构建,或者先down再up,才能让改动生效。5. 常见问题与排查技巧实录
5.1 拉取镜像非常慢
这个问题十有八九是没配镜像加速,或者加速地址失效。检查当前配置:
docker info | grep -A 6 "Registry Mirrors"如果没有输出,说明没配。按前面说的在
/etc/docker/daemon.json里加入registry-mirrors,重启Docker即可。还有一种情况:即使配了加速,某些大镜像比如
pytorch/pytorch、mysql:latest,体积动辄几个GB,网络差的时候还是要等。建议尽量选择带alpine或slim标签的镜像,体积能小一半以上。5.2 Permission denied 权限被拒绝
现象是执行
docker ps报错:Got permission denied while trying to connect to the Docker daemon socket原因就是当前用户不在docker组里。按前面的
usermod -aG docker操作即可。如果加完组还在报错,可能是当前终端会话没有刷新组权限,重新登录一次就好了。5.3 端口被占用:bind地址已在用
docker: Error response from daemon: driver failed programming external connectivity意味着宿主机那个端口已经被别的进程占用了,常见的有MySQL占了3306、Nginx占了80。用
ss -tlnp看看谁占了端口,要么换一个宿主机端口,要么把占用端口的进程处理掉。5.4 容器启动后立刻退出
这是新手最容易蒙圈的问题。用
docker ps -a看到容器状态是Exited (0)或者Exited (1),不一定是坏事,要分情况:- 如果镜像本身是跑一次性任务的(比如
hello-world),执行完就退出,正常。 - 如果确实是常驻服务(比如MySQL、Nginx),那说明配置有问题。
先看日志:
docker logs 容器IDMySQL报权限、Nginx报配置文件错误、环境变量没配全,日志里基本都有明确提示,按提示修就好。还有一个常见原因是前台没有进程驻留,比如某些Dockerfile的最后是
CMD ["./start.sh"],如果脚本执行完就退出了,容器也会跟着退出。这时候可以临时用docker run --entrypoint sh 镜像名进去手动排查。5.5 宿主机重启后容器没自动启动
重启服务器后发现服务全挂了,这是Docker默认策略的问题。解决方法是启动容器时加
--restart参数:docker run -d --name nginx --restart unless-stopped nginxunless-stopped的意思是:除非你手动docker stop,否则Docker服务启动时它也跟着启动。这种策略最适合长期运行的服务。如果是已经创建的容器,可以用docker update补上:docker update --restart always nginx5.6 daemon.json写错导致Docker起不来
改完
/etc/docker/daemon.json后执行sudo systemctl restart docker,发现服务起不来了,也不要慌,先看Docker有没有报错信息:sudo systemctl status docker sudo journalctl -xu docker.service最常见的问题就是JSON里多了一个逗号、少了一个引号。用这个命令验证JSON格式是否合法:
python3 -m json.tool /etc/docker/daemon.json如果输出的不是解析后的内容,而是报
Expecting property name enclosed in double quotes之类的错,说明JSON格式有问题,改回去再重启即可。5.7 容器日志把磁盘塞满了
跑了一段时间后,发现服务器磁盘被占满了,很大概率就是容器日志。
检查方法:
du -sh /var/lib/docker/containers/*/*.log按前面说的在
daemon.json里限制max-size和max-file,然后手动清掉已有的大日志:truncate -s 0 /var/lib/docker/containers/*/*-json.logtruncate直接清空文件而不删除文件,Docker还在写这个文件,不能直接rm,否则文件句柄还占着磁盘。6. 一段写给自己的经验记录
在我维护的几台服务器上,Docker现在承担了Nginx、MySQL、Redis、MinIO、Jenkins、监控面板这些常驻服务,以及大批跑完即删的构建任务。一年的使用过程中最深的体会是:Docker真的把"部署"这个事从"写一堆文档、录视频教人装依赖、配置环境变量"变成了"给一条run命令或一个docker-compose文件",而这个文件可以放进代码库跟项目一起版本化。团队里任何人拿到它,都能在五分钟内拉起一个和线上几乎一模一样的环境。
但也要说点冷水。容器不等于银弹,有状态服务(数据库、消息队列)在容器里跑,对运维的要求不是降低了,而是换了种难度——你得理解数据卷、网络、存储驱动这些底层概念,才能在出问题时快速定位。尤其是生产环境的数据库,如果你没有足够的时间和精力去维护容器化数据库的备份、恢复、高可用方案,那我还是建议数据库单独跑在物理机或云厂商托管实例上,让Docker先服务好那些无状态应用。
最实用的一条经验:给每个容器都加上
--restart unless-stopped,所有日志都限制大小,所有数据都落到命名卷或宿主机目录。这三条规矩立住,Docker基本能给你省下百分之八十的半夜守护时间。别嫌麻烦,等你经历过一次"睡一觉起来服务全没了、查了半天发现是日志把磁盘写满了"的深夜,你就明白这些规矩为什么值得坚持。