1. 部署的本质与整体思路拆解
1.1 "部署"到底意味着什么
第一次把自己写的代码"搬"到一台服务器上,让它跑起来,对很多刚接触开发的朋友来说,这一步比写代码本身还要让人头疼。我第一次部署项目的时候,整整折腾了一个周末,中间一度怀疑人生:明明本地跑得好好的,怎么换台机器就各种报错?
先说清楚一个概念:部署这件事,本质上就是把你本地电脑上"碰巧能跑"的环境,在另一台永远开机的机器上重新复现一遍,然后把代码传过去,让它对外提供服务。这里的关键词是复现环境,而不仅仅是传文件。
很多小白容易栽跟头,就是因为只想着"把代码传上去",完全忽略了服务器上的操作系统、运行时版本、依赖关系、端口开放、进程守护这些东西。这就好比你把一份菜谱给了别人,但对方厨房里连锅都没有,那肯定做不出菜来。
所以,部署项目需要三条线同时推进:
- 代码线:项目文件怎么从本地变成服务器上的文件
- 环境线:服务器上需要装哪些软件、什么版本,才能把你的项目跑起来
- 服务线:项目启动之后,怎么让它一直活着,还允许别人从公网访问
这三条线都通了,才算真正部署成功。
1.2 两条主流方案:裸机部署与容器化部署
部署方案现在基本分两大流派:传统的"裸机部署"和现在越来越主流的"Docker容器化部署"。
裸机部署的思路很直接:在服务器上手动安装Node.js、Python、MySQL之类的东西,配好环境变量,然后把你项目的代码拉下来,运行相应的启动命令。好处是每一步都看得见摸得着,出了问题你能知道具体是哪个环节挂了,对理解整个部署链路特别有帮助。缺点是环境隔离差,如果你的服务器上同时跑多个项目,一个项目要Python 3.8,另一个要Python 3.11,就很容易打架。
Docker的思路则是把环境整个打包进去。你的项目、运行时、依赖、配置,全部写进一个镜像里,在服务器上直接跑这个镜像。好处是环境完全一致,本地能跑,服务器上就一定也能跑,而且想删就删,不会污染服务器的系统环境。缺点是你得额外理解镜像、容器、数据卷这些概念,对纯小白来说多了一层学习成本。
我自己给新人的建议是:第一条项目别贪快,老老实实走一遍裸机部署。哪怕多花点时间,你也能搞清楚"服务器上跑一个项目到底需要什么"。等有了这个底子,再上Docker,你会发现Docker其实就是把"手动装环境"变成了"自动装环境",理解起来会快很多。这篇文章先按裸机主线路走,后面单独开一个章节讲Docker方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 服务器选购与基础环境准备
2.1 服务器怎么选:配置、系统与厂商
部署的第一步是拥有一台服务器。对新手来说,我建议直接上云服务器,别折腾物理机,也别一开始就搞什么服务器集群。云服务器的核心优势是开箱即用,而且有一堆配套的防护能力,你要做的就是选配置然后付钱。
配置方面,如果你只是想部署自己的练手项目、个人博客或者一个小型应用,1核2G内存起步就够了。这个配置跑一个Node.js或Python后端应用,顺带挂个MySQL,日常访问量不大是完全没问题的。如果你预算稍微充裕一点,或者项目里涉及Java系的东西(比如Spring Boot,启动就吃不少内存),可以选2核4G,用起来会更从容。至于带宽,新人选3M到5M的按流量计费或者固定带宽都行,只是自己测试的话,1M固定带宽也能忍受,就是稍微慢一点。
系统选哪个?Ubuntu 22.04 LTS是我最推荐给新手的选择。原因不是什么高深的理由,纯粹是它用户基数大、出问题搜一下基本都有答案,而且软件包更新及时。CentOS现在已经停止维护了,除非你所在的公司有特定要求,否则没必要在一个新部署的项目上选它。
厂商的选择就看你自己的实际情况了,主流的云厂商都提供新用户优惠。购买时注意地域选择,离你的目标用户近一点延迟就低一点,国内服务器后续如果要做域名备案,这个心里要有数。如果只是想学习练手,完全不想管备案那摊事,选择境外节点也是一个办法,访问速度会略慢,但省心。
2.2 安全组:云服务器第一道坎
服务器买完之后,很多小白会卡在最基础的一步:怎么连不上。这个问题的头号原因就是安全组没配好。
安全组可以理解成云服务器外层的"门禁系统",它在你的操作系统外面挡了一道。你服务器上某个端口即使已经开放了,安全组不放行,公网照样访问不到。购买服务器时,一般会让你选安全组规则或默认的安全策略,很多人直接点了默认就完事,结果默认只放行了22端口(SSH用的),其他全都不通。
所以拿到服务器之后,第一件事就是去云厂商的控制台,找到安全组或防火墙管理页面,确认或者添加入方向规则。对我们接下来的部署来说,以下几个端口是必须要放行的:
| 端口 | 用途 | 说明 |
|---|---|---|
| 22 | SSH远程登录 | 默认开启,别关 |
| 80 | HTTP访问 | Web服务都用这个 |
| 443 | HTTPS访问 | 配证书之后要用 |
| 3000/8080 | 项目调试端口 | 看你的应用实际监听哪个端口,调试期开放,上线后可以关掉只走Nginx |
配置安全组的时候注意,云厂商的规则一般会要求你填写"协议、端口范围、授权对象"。授权对象填0.0.0.0/0表示允许所有IP访问,这个在80和443这种对外端口上是合理的,但如果是数据库端口(比如3306),强烈建议不要对全网开放,后面安全那节我会专门说。
2.3 用SSH登录服务器:装个顺手的终端工具
安全组配好之后,接下来就是登录服务器。在Windows上,以前大家习惯用Xshell这类工具,但现在微软自带的Windows Terminal其实也够用了,你只需要知道一个命令:
bash复制ssh 用户名@服务器IP地址
比如你的服务器用户名是root,IP是123.123.123.123,那就输入:
bash复制ssh root@123.123.123.123
回车之后它会提示你输入密码,输入的时候屏幕上不会显示任何字符,这个别慌,正常现象,敲完回车就行。第一次连接时会问你是否确认主机指纹,输入yes回车即可。
如果你觉得命令行太朴素,也可以装一个MobaXterm或者FinalShell,它们提供图形化的文件管理器,可以从本地直接拖文件到服务器上,对不熟悉命令行的朋友友好很多。我用过一段时间FinalShell,它的监控面板能直接看到CPU、内存、磁盘占用,排查问题的时候比一个个敲命令直观。
另外提一个很多人忽略的点:云服务器一般默认不允许直接用密码登录root,或者登录时会要求你先设置密码。买完服务器后,去控制台的"重置密码"或者"修改密码"入口设个密码,不然你SSH的时候会一直提示认证失败。
2.4 先做两件小事:更新软件源和创建普通用户
登录进服务器之后,我强烈建议你先做两件小事,能帮你省掉后面很多坑。
第一件是更新软件源缓存。在Ubuntu上执行:
bash复制sudo apt update
sudo apt upgrade -y
如果你用的是国内服务器,默认的软件源可能在国外,速度会很慢。可以把源替换成阿里云或清华的镜像源,替换方法很简单:修改/etc/apt/sources.list文件(现在Ubuntu 22.04的源可能在/etc/apt/sources.list.d/ubuntu.sources里),把archive.ubuntu.com替换成mirrors.aliyun.com,然后重新sudo apt update。
第二件是创建一个普通用户来跑项目,不要直接用root。原因很简单,root权限太大了,万一你的项目有漏洞被人利用,整个服务器就沦陷了。创建用户的命令:
bash复制sudo adduser deploy
sudo usermod -aG sudo deploy
这里的deploy是我起的用户名,你可以随便起。创建好后,以后登录就用deploy而不是root,需要管理权限时用sudo。这一步很多人图省事会跳过,但我真心劝你别跳,安全性这种东西,都是出一次事故才知道疼。
3. 裸机部署实操:让你的项目在服务器上跑起来
3.1 做好本地准备:让项目变成一个稳定的运行状态
在上服务器之前,你必须先在本地把项目跑通,这是最基础的前置条件。以最常见的Node.js项目为例,你的项目目录里至少要有一个能启动服务的入口文件(比如app.js或index.js),并且你清楚地记得启动命令是什么——通常是node app.js或者通过package.json里的scripts字段定义的命令。
另外一个特别容易被忽略的事情是:项目里不该提交到代码仓库的东西,也绝不该出现在服务器上。比如node_modules这个依赖目录,本地执行npm install时自动生成的内容,一般不会传到服务器,到了服务器上再重新安装。配置文件中如果有数据库密码、API密钥这类敏感信息,建议用环境变量的方式注入,而不是硬编码在代码里。新手往往图省事直接写死,一旦代码上传到公开仓库,相当于把钥匙交给了路人。
准备传送代码时,有两条路可以走:
- 推荐路线:把项目推到GitHub(或国内的Gitee)上,然后在服务器上用
git clone拉下来。这样以后更新代码就只需要在服务器上git pull,非常方便。 - 临时路线:直接用
scp命令把本地文件复制到服务器,适合体量不大、不想初始化仓库的快速验证场景。
3.2 在服务器上安装Node.js运行环境
代码传到服务器之后,接着就是装环境。这里有个很多新手踩过的坑:直接用apt install nodejs装到的版本可能非常老,而且你还需要额外装npm。更推荐的方式是用nvm(Node Version Manager)来管理Node.js版本,好处是以后要换版本、切换版本都方便。
安装nvm的方式是在服务器上执行:
bash复制curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
装完nvm之后,重新登录一下终端让它生效,然后安装你项目需要的Node版本。假设你用Node 18:
bash复制nvm install 18
nvm use 18
装完验证一下:
bash复制node -v
npm -v
能看到版本号,说明环境就绪了。顺带提一句,如果服务器在国内且npm下载依赖特别慢,可以先换成淘宝镜像:npm config set registry https://registry.npmmirror.com,整个安装过程会快很多。
3.3 安装依赖并启动项目
环境就绪,接下来进入项目目录安装依赖。假设你的项目在/home/deploy/my-app:
bash复制cd /home/deploy/my-app
npm install
如果你的项目有.env之类的配置文件,记得在服务器上也创建一份,把里面涉及密码、密钥的变量改成服务器的实际值。缺少这一步,代码里读不到环境变量,应用启动时会直接报错,你都不知道去哪找原因。
然后先以前台方式启动,验证项目能不能跑起来:
bash复制node app.js
如果看到类似Server running on port 3000的日志,说明项目没问题。这时候先按Ctrl+C把它停掉,我们要换更专业的进程管理工具来守护它,不能指望着手动开着这个终端窗口。
3.4 用pm2让项目保持在线,重启也不怕
直接node app.js启动的问题是:一旦你关掉SSH终端,进程就被干掉了。就算你一直挂着终端,万一进程因为异常崩溃了,也没有人自动帮你拉起来。这时候pm2就派上用场了,它是一个Node.js的进程管理器,常被用来守护应用进程,让进程崩溃后自动重启,还支持开机自启。
安装pm2:
bash复制npm install -g pm2
然后启动你的项目:
bash复制pm2 start app.js --name my-app
这时查看进程状态:
bash复制pm2 status
你会看到类似下面的输出:一个名为my-app的进程,状态是online。到这里,你的项目已经具备"挂了自动拉起"的能力了。接下来还要做两件重要的事,让pm2在服务器重启后也能自动恢复进程:
bash复制pm2 save
pm2 startup
pm2 save会把当前进程列表存成一个快照,pm2 startup会生成一个开机自启的脚本并提示你执行它。执行提示里给的那条命令(一条带sudo的env ... pm2 startup systemd -u deploy之类的命令)即可。这一步做完,哪怕云服务器因为维护被重启了,你的应用也能自己活过来。
3.5 用Nginx做反向代理:别让用户记住你的端口号
项目跑起来之后,如果它监听的是3000端口,那你现在应该可以通过http://服务器IP:3000访问到了。但把这个地址直接给人用,有几个问题:端口号不好记、看起来不专业,而且直接把应用端口暴露在公网上,也增加了被扫描攻击的风险。更常规的做法是,让Nginx监听80端口,再把请求转发到你项目实际监听的端口上,也就是所谓的反向代理。
安装Nginx:
bash复制sudo apt install nginx -y
装完直接访问服务器IP,大概率能看到Nginx的默认欢迎页,这说明80端口已经通了。接下来我们要把默认配置替换成自己的配置。创建一个站点配置文件:
bash复制sudo vim /etc/nginx/sites-available/my-app
写入如下配置:
bash复制server {
listen 80;
server_name _; # 没有域名时用下划线占位
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
关键就一行proxy_pass http://127.0.0.1:3000;,意思是把所有请求都交给本机的3000端口处理,后面的几行header配置是为了正确处理WebSocket连接和传递真实的客户端IP,加上它们能省掉你后续调试WebSocket问题的麻烦。
创建好之后启用这个站点配置:
bash复制sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx -t是测试配置语法,如果显示syntax is ok,就可以reload让配置生效。现在直接访问http://服务器IP,看到的应该就是你的项目了,不再需要带3000端口。
4. Docker方案:懒人级别的部署体验
4.1 为什么要从裸机转向Docker
裸机部署走完一遍之后,你应该对"上服务器要装什么"心里有数了。但如果你是那种"不想折腾环境""本地能跑就行,线上也要一样跑"的性子,Docker就是为你准备的解法。
Docker解决的核心痛点是环境一致性。裸机部署时,你在一台新机器上手动装环境,只要版本和你本地开发环境有一点点偏差,就可能翻车。而Docker把"操作系统层以下的软件环境"和你本地的保持一致,通过镜像的方式打包出来,部署时一行命令就能跑起来。
另外还有一个很现实的场景:如果你的服务器上要部署多个项目,这些项目对运行环境的要求各不相同,用裸机部署就是一场环境冲突的噩梦。而Docker天然隔离不同容器,互不干扰,彻底解决这个问题。
4.2 安装Docker并编写Dockerfile
服务器上安装Docker,在Ubuntu上可以直接用官方提供的安装脚本:
bash复制curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
装完后让当前用户免sudo执行docker命令:
bash复制sudo usermod -aG docker $USER
然后重新登录一下终端,执行docker -v验证是否安装成功。
Docker部署的核心是Dockerfile,它描述了你这个项目的运行环境应该长什么样。拿一个Node.js项目举例:
dockerfile复制FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
解释一下每一行的作用:FROM指定基础镜像,这里选了Node 18的Alpine精简版,体积小、安全;WORKDIR设置容器内的工作目录;先COPY依赖清单文件,执行RUN npm ci安装依赖,这里先把package.json拷进来再装依赖,是为了利用Docker的缓存机制——只要依赖清单不变,重构建就不会重新下载一遍依赖;接着把整个项目代码拷进镜像(可以在项目里加.dockerignore文件排除node_modules等无用目录);EXPOSE声明容器内监听的端口;最后CMD是容器启动时执行的命令。
4.3 构建镜像并启动容器
在项目根目录下执行:
bash复制docker build -t my-app:latest .
-t指定镜像的名称和标签。构建完成后,用docker images可以查看到这个镜像。
启动容器:
bash复制docker run -d --name my-app-container -p 3000:3000 my-app:latest
参数含义:-d让容器在后台运行,--name给容器起个名字,-p 3000:3000把宿主机的3000端口映射到容器的3000端口。跑起来之后用docker ps查看状态,再用docker logs my-app-container查看应用日志,确认有没有启动成功。
这时候Nginx配置都不用改,因为映射关系还是宿主机的3000端口,之前配的proxy_pass http://127.0.0.1:3000依然有效。改一个数字,把3000换成3306这种数据库端口,你的MySQL也能用同样的方式跑到容器里。
4.4 用docker-compose管理多服务项目
前面这几步只解决了应用本身的容器化。但真实项目往往是"应用+数据库+缓存"的结构,总不能一个个容器手动建、手动连。这时候就轮到docker-compose上场了,它允许你用一份YAML配置,把多个服务的启动方式一次性定义好。
项目根目录下创建一个docker-compose.yml文件:
yaml复制services:
app:
build: .
ports:
- "3000:3000"
environment:
- DB_HOST=mysql
- DB_PORT=3306
- DB_PASSWORD=mysecret
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=mysecret
- MYSQL_DATABASE=myapp
volumes:
- mysql-data:/var/lib/mysql
ports:
- "3306:3306"
volumes:
mysql-data:
这里的services下定义了app和mysql两个服务。应用的DB_HOST直接填mysql,这是因为在同一个compose网络内,服务名就是主机名,容器之间可以直接通过服务名互相访问,不需要再关心IP地址。volumes把MySQL的数据目录挂载到命名卷mysql-data里,这样哪怕容器删了重建,数据也还在。
启动全部服务:
bash复制docker-compose up -d
查看状态:
bash复制docker-compose ps
这样就完成了一次多容器部署。以后更新代码,只需要在项目目录下重新构建并重启:
bash复制docker-compose up -d --build
整个流程简洁得不像话,而且换一台服务器,你只需要确保有Docker和compose,执行同样的命令就能复现一模一样的环境。这也是为什么现在很多开源项目直接提供docker-compose配置,部署门槛被大大降低了。
5. 域名、HTTPS与安全加固
5.1 域名解析:给IP一个人类友好的名字
让你的项目可以凭http://服务器IP访问,功能上是够了,但一个纯数字的IP既不好记,也显得不专业。如果想让自己的项目像个正经服务,买一个域名配上是值得的。
买域名的平台很多,主流云厂商都提供域名注册服务,首年价格也就几十块钱。买好之后,在域名解析控制台添加一条记录:主机记录根据需要填www或者@,记录类型选A,记录值填你的服务器IP。等DNS生效,几分钟到几小时不等,就能通过域名访问你的项目了。
有了域名之后,还记得之前Nginx配置里的server_name _;吗?现在可以把它改成你的真实域名了,比如server_name example.com www.example.com;,这样你在这个服务器上配置多个项目时,Nginx就能根据域名把不同请求分发到不同端口,互不干扰。
5.2 用免费证书搞定HTTPS
现在浏览器对没有HTTPS的站点越来越不友好了,尤其涉及登录、支付这种敏感操作的页面,没有HTTPS基本等于告诉用户"我这里不安全"。好在有Let‘s Encrypt这种免费证书服务,配合Certbot工具,配置HTTPS也就是几条命令的事。
安装Certbot和Nginx插件:
bash复制sudo apt install certbot python3-certbot-nginx -y
执行自动获取并配置证书的命令:
bash复制sudo certbot --nginx -d example.com -d www.example.com
Certbot会自动检查你对域名的控制权(它会通过HTTP验证),然后自动修改Nginx配置加入SSL证书路径和443端口监听,还会自动配好HTTP自动跳转HTTPS。整个过程基本无脑,执行完再访问你的域名,地址栏就会带上小锁了。
证书有效期一般是90天,到期之前记得续期。手动续期的命令是sudo certbot renew,但更稳妥的办法是配置一条crontab定时任务,每两个月自动执行一次。我个人的习惯是设成每天早上执行一次,反正Certbot会自己判断快过期才续,逻辑上不会出问题。
5.3 安全加固三件套:SSH、防火墙和不用的端口
部署上线只是开始,安全才是长期要面对的事。服务器暴露在公网上,每一分钟都有大量扫描程序在试图探测你的开放端口、猜你的SSH密码。以下三件事,强烈建议你在正式提供对外服务之前做完。
第一,修改SSH默认端口。把22端口改成其他高位端口(比如2222),虽然不能完全防住扫描,但能过滤掉绝大多数无差别攻击。修改方法:编辑/etc/ssh/sshd_config,找到#Port 22改成Port 2222,然后重启SSH服务。注意改之前确保你的安全组也放行了新端口,不然你把自己锁门外就尴尬了。
第二,用密钥登录,关闭密码登录。密钥登录的安全性远高于密码,只要私钥不泄露,暴力破解根本无从下手。本地生成密钥对:
bash复制ssh-keygen -t ed25519
然后把公钥追加到服务器的~/.ssh/authorized_keys文件里。确认可以正常用密钥登录之后,再把/etc/ssh/sshd_config里的PasswordAuthentication改成no,并重启SSH。
第三,只开放必要端口。用ufw(Uncomplicated Firewall)管理服务器防火墙,只放行SSH、80、443这些必要的端口:
bash复制sudo ufw allow 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
注意安全组和ufw是两层防护,两个都要配好。另外,像MySQL的3306、Redis的6379这种数据库和缓存端口,除非确实需要远程访问,否则一律不对公网开放。我见过太多人图省事把3306暴露到公网,然后数据库被勒索加密,那真是欲哭无泪。
6. 常见问题与排查技巧实录
6.1 本地能跑,服务器上一跑就报错
这个问题出现的频率极高,原因一般逃不出这三类:版本不一致、路径写死了、环境变量缺失。
版本不一致最常见的是Node.js或Python的版本差异。本地用的Node 18,服务器上apt默认装的是Node 12,很多语法和包API不兼容,一启动就报错。解决办法就是别偷懒,用nvm或版本管理工具把版本对齐成和本地一样。
路径写死的问题也很典型,比如代码里用了/Users/你的用户名/xxx这种绝对路径,服务器上当然找不到。写代码时养成习惯,文件路径一律用相对路径,或者基于__dirname(Node.js)这类动态获取的目录来拼接,就不会有这种坑。
环境变量缺失的情况在裸机部署里最常见,尤其项目里用了.env文件但没传到服务器上,或者传了但格式不对(比如Windows的换行符和Linux不兼容),都会导致配置读取失败。排查方法很简单:在项目目录下先打印一下process.env(Node.js)或者用配置项的默认值做兜底,看它到底缺什么。
6.2 端口明明开了,外网就是访问不了
这个问题的排查思路要按从外到内的顺序来捋。第一层看云厂商的安全组是否放行了对应端口;第二层看服务器操作系统防火墙(ufw或firewalld)是否放行;第三层看服务本身是否监听在了正确的地址上。
第三层特别容易被忽视。很多框架默认监听的地址是127.0.0.1,也就是只能本机访问,外网进不来。比如Node.js里app.listen(3000)默认监听所有接口没问题,但有些框架会明确指定host,如果你看到类似app.listen(3000, '127.0.0.1')的代码,必须改成0.0.0.0才能让外网访问。用netstat -tlnp命令可以查看服务的实际监听地址,如果显示127.0.0.1:3000而不是0.0.0.0:3000,问题就出在这了。
6.3 访问网站返回502 Bad Gateway
Nginx的502错误,含义是Nginx收到了请求,但没法把请求成功转发到后端的应用服务器。绝大多数情况下,原因是后端应用挂了或没启动。
排查步骤非常固定:先看后端进程在不在(pm2用pm2 status,Docker用docker ps),再看应用日志(pm2用pm2 logs,Docker用docker logs)。如果进程在、日志也正常,那就检查Nginx配置里的proxy_pass端口是否真的对应着应用监听的端口。还有一个常见坑是:应用在本地测试时监听正常,但换了服务器后监听的IP变了,Nginx转发到127.0.0.1:3000时,应用却只监听在别的地址上,也会出现502。解决方法是把应用监听的host明确设为0.0.0.0,确保本机任何地址都能访问到它。
6.4 服务器磁盘突然满了
服务器的磁盘空间是需要持续关注的指标。项目日志越堆越多、Docker镜像和容器越攒越多、数据库binlog文件持续增长,都是常见的磁盘杀手。
排查思路是先用df -h看整体使用率,再用du -sh /home/* /var/* 2>/dev/null | sort -hr | head -20找出具体是哪个目录占用最多。如果是日志文件膨胀,用journalctl --vacuum-size=200M可以快速清理systemd日志;如果是Docker堆积了太多无用的镜像和容器,执行docker system prune -a可以一键清理所有未被使用的镜像、容器、网络和数据卷缓存。另外别忘了检查一下是不是自己写代码时不小心把uploads目录或临时文件写到了磁盘上,这类业务层面的文件增长往往才是大头。
6.5 排查问题的总原则:先看日志,再看配置
我踩过的坑多了之后,总结出一条铁律:出问题先看日志,不要上来就改配置。日志告诉你的通常是真实原因,而配置只是你的猜测。应用日志在pm2里是pm2 logs,在Docker里是docker logs,在systemd服务里是journalctl -u 服务名 -f,Nginx的错误日志在/var/log/nginx/error.log。把日志打开,跟着报错信息一行行走,绝大多数问题都能顺藤摸瓜找到答案。改配置之前先备份,改完用小流量验证,不要一次性直接上生产环境改完就跑。
我自己在实际操作中还有一个体会是:部署这件事,第一次一定要抄作业式地做下来,不要在一开始就追求理解每一个细节。等你完整地把一个项目从本地搬到公网,看到浏览器里输入域名后出现自己的应用界面,那种成就感会推着你继续搞懂底层原理。而且有了第一次的全流程经验,后面不管是给项目加HTTPS、上Docker,还是部署第二个、第三个项目,都只是对这套流程的查漏补缺和升级改造而已。最后再分享一个小技巧:先在本地装好Docker Desktop,把你的项目在本地用Docker跑通,确认没问题之后再带着Dockerfile上服务器,这样省掉的时间远比你想象中要多。
