部署这个词,这几年在技术圈都快被说烂了,但真正落到自己手上,要装个数据库、起个中间件、跑个AI模型,依然能把人折腾到半夜。我最近半个月连续部署了MySQL 8.0、Redis主从、还有两套本地大模型推理环境,全程都是Docker搞定,没有一次因为环境问题翻车。这篇东西就围绕“Docker部署”这件事,把从零安装、常用命令、网络数据这些基本功,到MySQL、Redis、AI大模型这些真实场景的完整操作,一次性讲清楚。适合刚接触Docker想自己动手的开发者,也适合那些已经用了一段时间但总在网络、数据持久化上踩坑的老哥。
1. 部署这件事,Docker为什么绕不开
1.1 镜像、容器、仓库:三个概念把部署说清楚
很多人一上来就被Docker的一堆名词劝退,其实核心就三个:镜像、容器、仓库。
镜像可以理解成一台预装好所有软件的“虚拟电脑的光盘镜像”,里面包含了操作系统精简层、运行环境、应用代码、配置文件,全打包成一个只读模板。容器就是基于这个模板启动出来的“运行实例”,一个镜像可以同时启动多个互不干扰的容器。仓库则是存放镜像的地方,公开的Docker Hub相当于应用商店,公司内部的私有仓库相当于内部软件源。
用生活化类比就是:镜像=菜谱+配菜打包好的预制菜包,容器=按菜谱炒出来摆在桌上的那盘菜,仓库=存放各种预制菜包的超市货架。你在服务器上执行docker pull就是去货架拿菜包,docker run就是起锅烧油。
这三个概念理清了,后续所有部署操作都是围绕“搞到镜像”和“跑成容器”这两件事展开的。实际工作中,Docker项目“——部署”的完整链路,本质上就是选择或构建镜像、配置运行参数、管理容器生命周期。
1.2 解决的根本问题:环境一致性与资源隔离
传统部署方式为什么让人头疼?最典型的就是“在我机器上明明是好的”。开发用Windows,测试用CentOS,生产用Ubuntu,哪怕代码完全一样,依赖库版本、系统库差异、路径不一致,都可能让服务起不来。Docker把应用和它的运行环境整个打包进镜像,镜像到哪,环境就到哪,彻底消除了环境差异。
另一个关键是资源隔离。每个容器有自己独立的文件系统、网络栈、进程空间,通过Linux内核的namespace和cgroup实现,不需要额外的虚拟机操作系统开销。这一点让Docker在服务器资源利用上比虚拟机高得多,一台4核8G的机器照往常跑两个服务就吃力,用Docker跑五六个轻量服务完全没问题,这就是容器技术的底层魅力。
所以当你看到“Docker——部署”这种需求,本质上是在寻找一种能把服务交付成本降到最低的方案:不需要写冗长的部署文档,不需要担心环境冲突,一条docker run或者一份docker-compose.yml文件,拉起来就完事。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:把Docker装起来
2.1 Linux服务器安装Docker
绝大多数生产环境和服务器的Docker部署都在Linux上,Ubuntu和CentOS是两大主流。Ubuntu的安装最省心,官方推荐用apt仓库方式:
bash复制sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /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
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
CentOS这边稍微不一样,注意用yum仓库和yum-config-manager,装上之后记得启动服务并设置开机自启:
bash复制sudo systemctl start docker
sudo systemctl enable docker
装完验证一下:docker version能看到客户端和服务端版本,docker run hello-world能跑起来就算成了。这里有个高频坑:默认情况下docker命令需要root权限,普通用户执行会报权限错误,解决办法是把当前用户加入docker组:
bash复制sudo usermod -aG docker $USER
newgrp docker
之后退出终端重进,docker命令就能免sudo执行了。这个细节如果你是用云服务器新装的系统,几乎必踩,提前处理好能省很多事。
2.2 Windows和macOS使用Docker Desktop
Windows上的Docker部署最常见的形式不是裸装docker引擎,而是装Docker Desktop。它是图形化管理工具,底层靠WSL2或者Hyper-V跑一个Linux虚拟机来提供真正的Docker环境。
Docker Desktop安装包的下载和双击安装本身没什么难度,真正的门槛在于启动。很多人在Windows上遇到报错“Docker Desktop failed to start because virtualization support not detected”,这个错误字面意思是检测不到虚拟化支持,但实际原因分三类:
一是BIOS里没开CPU虚拟化,Intel的VT-x或AMD的SVM被禁用,需要进BIOS开启;二是Windows功能里没启用“虚拟机平台”和“适用于Linux的Windows子系统”,需要在“启用或关闭Windows功能”里勾上这两项;三是已经开了WSL2却跟Docker Desktop集成失败,此时执行wsl --shutdown再重启Docker Desktop往往能解决。
另外还会碰到“failed to connect to the docker api at npipe:////./pipe/docker_desktop_linux”这类Windows管道连接错误,通常发生在WSL后端异常或Docker引擎还没就绪时,重启服务基本都能恢复。
macOS的Docker Desktop相对省心,苹果芯片用ARM版、Intel芯片用x86_64版,别下错架构就行。装好后默认配置就能用,唯一值得注意的是资源配额,Docker Desktop默认给的内存往往不够跑重负载服务,在设置里调到4G以上更稳妥。
在Windows上装了Docker Desktop后,你执行docker命令时其实不是在Windows本机直接跑的,而是通过管道转发到了WSL里的Linux环境,所以容器里的文件路径、网络行为都表现出Linux特征,这一点新手容易懵,提前有个心理预期就好。
2.3 镜像加速配置与常见安装问题
镜像拉取慢是国内外团队部署时最容易炸心态的环节。拉一个几百MB的镜像卡半天,进度条纹丝不动,最后超时失败,非常常见。解决办法是配置镜像加速器,让Docker从更近的公共镜像服务拉取。
配置方法很简单,编辑/etc/docker/daemon.json(没有就新建),加一段registry-mirrors:
json复制{
"registry-mirrors": ["https://your-mirror-address"]
}
改完执行sudo systemctl restart docker。Windows的Docker Desktop在Settings -> Docker Engine里改同样的JSON,保存后会自动重启引擎。注意镜像加速服务地址要用你自己实际可用、能连通的那个,网上流传的公共地址经常失效,以官方渠道或你所在服务商提供的为准。
还有个观念上的提醒:镜像加速只是缓解拉取压力,如果加速服务不稳定,可以改用多线程拉取工具或者从别的机器导出镜像再导入,docker save和docker load这两个命令专门干这个活,内网服务器之间传镜像非常实用。
3. 部署基本功:镜像、容器、网络与数据
3.1 常用命令速查与执行逻辑
Docker的命令看起来多,其实围绕生命周期就那几组,下面这张表是我平时用得最频繁的,照着抄就行:
| 命令 | 作用 | 我的使用习惯 |
|---|---|---|
| docker pull 镜像名:标签 | 拉取镜像 | 明确版本标签,尽量不用latest |
| docker images | 查看本地镜像 | 配合grep找镜像 |
| docker run -d --name xx -p 端口:端口 镜像 | 启动容器 | 生产环境必加-d后台运行 |
| docker ps | 查看运行中容器 | 加-a看所有含退出的 |
| docker exec -it 容器名 bash | 进入容器 | 排查内部问题时用 |
| docker logs -f 容器名 | 查看实时日志 | 服务起不来第一件事 |
| docker stop/start/restart 容器名 | 生命周期管理 | 日常重启首选 |
| docker rm 容器名 | 删除容器 | 先stop再rm |
| docker rmi 镜像名 | 删除镜像 | 清理磁盘空间 |
| docker cp 宿主机路径 容器名:容器路径 | 文件拷贝 | 临时传配置文件 |
关于镜像标签多说一句:默认的latest标签看着省事,但它会随上游更新变动,今天拉的和下周拉的可能是不同版本。部署上生产环境,务必指定明确的版本号,比如mysql:8.0.36,这样镜像哈希确定,回滚、复现都容易。
docker run是整套命令里参数最多的,但没必要全背,核心就那么几个:-d后台运行、--name起名、-p端口映射、-e注入环境变量、-v挂载数据卷、--network指定网络。把这几个掌握,90%的部署场景都能覆盖。
3.2 端口映射与容器网络
容器有自己独立的网络命名空间,对外提供服务必须做端口映射。这也是新手最常问“为什么我在服务器上curl不通”的根源所在。端口映射的逻辑是:宿主机的某个端口 -> 容器内的某个端口。
bash复制docker run -d --name nginx-test -p 8080:80 nginx:alpine
这条命令把宿主机的8080端口转发到容器的80端口,外部访问http://服务器IP:8080就能看到Nginx页面。如果你不写-p,容器内部服务只有容器自己访问得到,外部完全够不着,这是很多人“明明容器跑起来了却访问不到”的第一个排查方向。
容器之间的网络通信有两种方式。默认的bridge网络下,容器通过IP地址互相访问,但容器重建后IP会变,靠IP通信很不稳定。更推荐的做法是创建自定义网络,让容器通过服务名(即容器名)通信:
bash复制docker network create my-net
docker run -d --name app1 --network my-net ...
docker run -d --name app2 --network my-net ...
在app2里直接访问app1:端口就能连通,Docker内置DNS会解析容器名。Redis主从、微服务互相调用、前后端分离项目联调,都建议用这个方案,网络隔离性和稳定性都更好。
3.3 数据持久化:容器删了数据不能丢
容器是出了名的“关了就没了”,容器创建时写入的数据在容器删除后默认全部丢失。如果部署的是有状态服务,比如数据库、消息队列、文件存储,不做数据持久化等于给生产环境埋雷。
Docker提供两种持久化方式:bind mount(绑定挂载)和volume(数据卷)。
bind mount就是把宿主机目录直接映射进容器,改宿主机文件,容器里立刻生效,适用于配置文件和源码目录:
bash复制docker run -d --name nginx -v /opt/nginx/html:/usr/share/nginx/html -p 80:80 nginx
volume是Docker托管的数据卷,数据存在docker管理的目录里,适合数据库这类需要稳定存储、不需要直接修改文件的场景:
bash复制docker volume create mysql_data
docker run -d --name mysql8 -v mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
两者本质都是把数据落在宿主机上,区别在于管理方式。bind mount依赖宿主机路径,换机器部署时路径得跟着调;volume是Docker统一管理,迁移时用docker run --volumes-from或者直接挂同名卷就能带过去。我个人的习惯是:配置文件用bind mount,数据文件用volume,兼顾灵活和稳定。
4. 一个完整的部署实操:MySQL 8.0与Redis主从
4.1 部署MySQL 8.0并用命令行连接
以MySQL 8.0为例,部署命令不长,但有几个参数必须说清楚。
bash复制docker run -d \
--name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=MyStr0ngPass! \
-e MYSQL_ROOT_HOST=% \
-v mysql_data:/var/lib/mysql \
mysql:8.0
MYSQL_ROOT_PASSWORD是初始化时设置root密码,MYSQL_ROOT_HOST=%允许root从任意主机远程连接,这在本地开发时很省心,但生产环境建议收紧,只允许应用服务器IP连接。数据卷挂载到/var/lib/mysql,这是MySQL在容器内存储数据文件的默认路径,一定得挂,否则容器一删数据全没。
启动后检查容器状态:
bash复制docker ps | grep mysql8
docker logs --tail 50 mysql8
看到“ready for connections”字样基本就成功了。进容器连接MySQL的方式:
bash复制docker exec -it mysql8 mysql -uroot -p
但从宿主机直接连接才是最常见的场景,你需要装个MySQL客户端,或者用Navicat、DBeaver这些图形工具,连接地址填宿主机IP、端口3306即可。
这里有个实际部署中特别容易踩的坑:MySQL 8.0的默认字符集。官方镜像默认字符集是latin1,直接建表存中文会出现乱码风险。最好的做法是在启动时追加参数:
bash复制docker run -d --name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=MyStr0ngPass! \
-e MYSQL_ROOT_HOST=% \
-v mysql_data:/var/lib/mysql \
-v /opt/mysql-config/my.cnf:/etc/mysql/conf.d/my.cnf \
mysql:8.0
my.cnf里加一行:
ini复制[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
utf8mb4比utf8更全,能存储emoji和四字节字符,现在新项目默认都是utf8mb4。
4.2 用一条命令搭出Redis主从
Redis主从部署比MySQL更轻量。先确认网络环境,让两个Redis容器在同一个自定义网络中,从节点能通过服务名找到主节点。
bash复制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 --replicaof redis-master 6379
第二条命令末尾的redis-server --replicaof redis-master 6379是关键,它覆盖容器的默认启动命令,让这个Redis实例以从节点身份运行,并指向主节点的服务名。这里能直接用redis-master而不是IP,就是因为自定义网络提供了DNS解析。
验证主从是否生效:
bash复制docker exec -it redis-master redis-cli info replication
docker exec -it redis-slave redis-cli info replication
从节点的输出里看到role:slave,并且master_link_status:up,说明主从关系建立成功。在主节点写入数据,到从节点查询,数据一致就完事。
这个方案的实用价值在于:它演示了不需要改任何配置文件、不需要手动编译安装Redis,两条命令就完成了主从架构。同样的思路可以扩展到哨兵模式、Cluster模式,只是需要更多的容器编排,这时候就该上Docker Compose了。
4.3 用Docker Compose固化部署方案
单条docker run命令适合临时起服务,但一个正经项目往往有多个服务:数据库、缓存、后端、前端、消息队列,逐条执行命令既啰嗦又容易漏参数。Docker Compose把整套部署方案写进一个YAML文件,一条docker compose up -d全部搞定,这才是“部署”该有的样子。
以MySQL加Redis主从为例,docker-compose.yml长这样:
yaml复制services:
mysql:
image: mysql:8.0
container_name: mysql8
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: MyStr0ngPass!
MYSQL_ROOT_HOST: "%"
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-net
redis-master:
image: redis:7
container_name: redis-master
ports:
- "6379:6379"
networks:
- app-net
redis-slave:
image: redis:7
container_name: redis-slave
ports:
- "6380:6379"
command: redis-server --replicaof redis-master 6379
depends_on:
- redis-master
networks:
- app-net
volumes:
mysql_data:
networks:
app-net:
在docker-compose.yml所在目录执行docker compose up -d,三个容器会按依赖顺序启动。depends_on保证从节点在主节点起来之后才启动,volume和network的定义在文件底部统一声明,整个部署方案就变成了一份可版本管理的配置文件。
这套玩法的价值不只是“少敲几条命令”。Compose文件可以提交到Git仓库,环境重建时git clone下来docker compose up -d就恢复整套服务,比任何部署文档都来得可靠。这也是为什么现在遇到“Docker部署”需求,我第一个想到的就是Compose。
5. Docker部署AI大模型:本地推理不再是折腾
5.1 Ollama跑DeepSeek,几步搞定
今年本地部署AI大模型的热度一直很高,热搜词里DeepSeek本地部署、Ollama本地部署、本地部署AI都是高频搜索。你要是用传统方式去装,先配置Python环境、搞CUDA、下模型权重、再写推理脚本,光折腾环境就能耗掉一整天。Docker化之后,整个流程被压到了几条命令。
以Ollama部署DeepSeek为例,Ollama本身就是一个把模型管理、推理服务、API接口打包好的工具,官方提供Docker镜像。先拉镜像:
bash复制docker pull ollama/ollama
docker run -d --name ollama -p 11434:11434 \
-v ollama_data:/root/.ollama \
ollama/ollama
在这个容器里下载并运行DeepSeek模型:
bash复制docker exec -it ollama ollama run deepseek-r1:7b
注意模型名后面的尺寸标签:deepseek-r1有1.5b、7b、8b、14b、32b、70b等不同参数规模。7b对应大约需要8G内存,14b建议16G以上,70b至少64G内存且强烈建议有独立显卡。选什么尺寸取决于你的机器配置,别一上来就拉70b,内存不够直接把机器卡死。
如果机器有NVIDIA显卡,想让Docker容器用上GPU,需要额外装NVIDIA Container Toolkit,启动时加--gpus all参数。没有GPU也能跑,纯CPU推理就是要慢一些,7b模型大约每秒输出几个token,做本地问答体验是够的。
容器化部署Ollama的核心优势是干净和可迁移。本地跑过的模型缓存都在ollama_data卷里,换机器时备份这个卷,或者直接在新机器上docker run再执行ollama pull,环境完全一致。好多依赖GraphRAG、私有知识库的本地推理应用,底层都是这么起服务的。
5.2 Dify这类AI应用平台的Compose部署
如果只是跑单个模型,Ollama就够了。但要做完整的AI应用,比如带知识库问答、Agent工作流、模型管理后台,那就要用Dify这类平台型工具。Dify官方提供docker-compose部署方式,这个部署工程量大、组件多,但它恰恰是Docker Compose价值的完美展示。
Dify部署的常规流程是拉取官方docker-compose.yml文件、配置环境变量、然后启动:
bash复制git clone https://github.com/langgenius/dify.git
cd dify/docker
cp .env.example .env
docker compose up -d
第一次启动会拉取十几个镜像,包括API服务、Worker、PostgreSQL、Redis、Weaviate等,耐心等它跑完。启动完成后访问http://服务器IP(默认80端口),会进入初始化页面,设置管理员账号,之后就能在界面里配置模型供应商、上传知识库文档、编排Agent流程了。
这类部署的实际体验非常考验机器配置。Dify全家桶跑起来至少需要8G内存,知识库向量化时CPU会飙高,16G内存的机器才能跑得从容。部署完别急着关终端,先docker stats看看各容器的资源占用,哪个组件吃满就给它多分配资源。
这里特别提醒一点:Dify的docker-compose.yml默认开放了很多端口,如果你是在公网服务器上部署,务必提前在安全组和防火墙里限制访问,或者用Nginx反向代理加HTTPS,别把数据库、消息队列的端口直接裸奔到公网。AI应用涉及的数据往往比较敏感,安全这块宁可多做不要少做。
5.3 部署后要看的资源与日志
AI模型跑起来之后,常见的坑反而不在部署,而在运行期。我遇到过最典型的两个问题:内存不够导致容器被杀、显存分配失败导致服务崩溃。
容器被杀通常表现为docker ps看不到容器,docker ps -a显示Exited状态,查看日志最后一行的OOM Killed字样就是内存超限了。排查办法:docker stats看容器实际内存占用,确认后要么换更小规格的模型,要么给Docker加大内存配额(尤其用Docker Desktop时,默认2G内存跑大模型必挂)。
显存问题多出现在GPU环境下,启动时也有应对参数:
bash复制docker run -d --name ollama-gpu --gpus all \
-e OLLAMA_NUM_GPU=1 \
-v ollama_data:/root/.ollama \
ollama/ollama
OLLAMA_NUM_GPU控制模型加载到显卡的层数,显存小就设置小一点,剩下的层跑在CPU上,性能折中但至少不崩。日志查看用docker logs -f ollama就能看到模型加载进度和推理请求记录,遇到问题第一反应别去百度,先看日志。
6. 部署踩坑实录:问题排查与解决经验
6.1 常见问题速查表
做了多年部署,踩坑踩出经验来了。下面这张表覆盖了我在Docker部署中遇到过的高频问题,几乎每个都能对应到具体场景。
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| docker pull慢或超时 | 网络到Docker Hub不稳定 | 配置镜像加速,或用docker save/load离线转移 |
| Docker Desktop无法启动,提示虚拟化未检测到 | BIOS虚拟化未开、Windows功能未启用 | 开启VT-x/SVM,勾选虚拟机平台和WSL2组件 |
| 容器起来了但外部访问不到 | 端口映射遗漏或映射错方向 | 检查-p参数,确认宿主机端口没被防火墙拦截 |
| 容器删除后数据全部丢失 | 未挂载数据卷或bind mount | 数据目录全部用volume/bind mount持久化 |
| 容器一启动就退出 | 前台进程退出、启动命令错误 | 检查docker logs,确认CMD是否被正确覆盖 |
| 容器间Ping不通 | 不在同一自定义网络 | docker network create并加入同一网络,用服务名访问 |
| 磁盘被镜像占满 | 旧镜像、悬空镜像堆积 | 定期docker system prune -a清理 |
| MySQL容器中文乱码 | 字符集默认latin1 | 挂载my.cnf,设置utf8mb4 |
这张表不是让你背的,而是遇到问题时先对照定位方向。绝大多数部署失败都逃不出这八类原因,先确认现象属于哪一类,再去查具体细节,比漫无目的地百度高效得多。
6.2 网络不通的排查套路
“docker网络不通”是部署时最让人抓狂的问题,因为表现千奇百怪:外部访问不了、容器之间连不上、容器内无法上网。我总结了一套排查顺序,先按这个思路走一遍,多半能定位。
第一步确定是外部访问还是容器间访问。外部访问不通,先查宿主机的端口监听状态:ss -tlnp | grep 端口,如果没监听,说明端口映射没生效,回去看docker ps的PORTS列;如果在监听但外部不通,查安全组和防火墙,云服务器还要单独看安全组规则。
第二步容器间访问不通,先确认是否在同一个自定义网络:docker network inspect 网络名,看看两个容器是否都在列表里。默认bridge网络虽然也能互访,但得用IP且每次重建都会变,用服务名解析是自定义网络的特权。
第三步看容器内部网络本身。docker exec -it 容器名 bash进去ping一下网关,再ping一下宿主机IP。容器能ping通宿主机但外网不通,多半是宿主机的iptables规则或内核IP转发没开,检查net.ipv4.ip_forward是否为1。
遇到网络问题最忌瞎试,按这个顺序排查,五分钟内基本能定位到具体环节。
6.3 给初学者的几条实用心得
最后分享几条我个人在大量部署操作中沉淀下来的心得,不算什么高深理论,但每条都是真金白银换来的。
第一,所有有状态服务必须挂载数据卷。这是我的血泪教训:早期部署一个内部工具,Redis没挂数据卷,一次docker system prune把数据全清了,还好只是缓存不是业务数据。从那时候起,我任何容器启动前先确认数据的落盘位置。
第二,标签永远写明确版本,不用latest。docker-compose文件里更是如此,镜像版本升级、API变更都可能因为latest的漂移让你部署的“上次能用”变成“这次跑不起来”。用固定版本号,哪怕出问题也能稳定复现,排查起来有据可依。
第三,写Compose文件比敲命令更值得投入。docker run单命令适合临时验证,但要长期维护的服务,从一开始就写成docker-compose.yml,后续改端口、加配置、换版本都只改文件,git提交后所有人都能复现。团队协作时,Compose文件就是最好的部署文档。
第四,容器不是虚拟机,不要在里面装各种调试工具。遇到问题第一反应应该是docker logs看日志、docker exec进去看进程,而不是把vim、net-tools全装上把它改造成一个小服务器。容器要的是轻量和不可变,排查完就该把容器删了,用镜像重新起一个干净的。
