
刚开始做站的时候,我改一个文件就用 FTP 拖一次,一天拖几十回。后来站点多了,经常出现本地改了、服务器没传全、线上还是旧版本的情况,排查一次能耗掉半小时。换成 Git 部署之后,这一类问题基本消失了。
为什么别再靠 FTP 拖文件
FTP 最大的问题是没人知道你传了什么。传了一半断线,服务器上就是半个文件,页面直接白屏,你还以为代码写错了。
第二个问题是没法回滚。改错了想把昨天的版本弄回来,只能靠你自己留的备份包,而多数人根本不留。
第三个问题是多人协作。两个人同时改同一个文件,谁后传谁覆盖,前面那人的改动就没了,而且没有任何记录。
Git 把这三个问题一次解决:每次部署有明确版本,出错一条命令回滚,谁改了什么在 log 里写得清清楚楚。
最简方案:服务器上 git pull
这套方案不需要任何额外服务。服务器上把仓库克隆到站点目录,本地 push 到远端,然后 SSH 上去执行 git pull,完事。
具体三步。第一,服务器上 git clone 你的仓库到临时目录,再把文件挪到站点目录,或者干脆直接克隆到站点目录但排除掉不该覆盖的文件。第二,本地改完 commit 再 push。第三,登录服务器 cd 到目录执行 git pull。
要注意 .git 目录不要暴露在网站根目录下,nginx 里加一条规则禁掉对 .git 的访问,否则别人能把你整个仓库下载走。
还有一点,服务器上的仓库建议用只读方式,不要给它配置推送权限。它只是拉代码的地方,不需要往回写。
用钩子让它自动跑起来
每次都手动 SSH 上去 pull 太烦,可以用 Git 的 post-receive 钩子。原理是本地 push 到服务器上的一个裸仓库,裸仓库收到推送后自动执行一段脚本,把代码检出到站点目录。
流程是:服务器上建一个 bare 仓库,在里面 hooks 目录放一个 post-receive,内容就是 git --work-tree=站点目录 --git-dir=裸仓库 checkout -f。本地把远端指向这个裸仓库,push 一下,代码就自动落地了。
钩子脚本记得给执行权限,并且要用绝对路径。很多人写完不生效,就是因为脚本没有可执行位,或者 PATH 里找不到 git。
这套方案的好处是快,push 完一两秒就上线。坏处是没有回滚记录,所以要配合下面的做法。
回滚怎么做才不慌
最直接的办法是记版本号。每次部署前把当前 commit 的短哈希记下来,出问题就 checkout 回那个哈希,再强制检出一次,几十秒恢复。
更省事的办法是打标签。上线前打个 tag,比如 release-20261108,回滚的时候直接切到上一个 tag,不用记哈希。
如果你用的是钩子自动部署,建议在钩子里加一步:把当前版本号写到一个文件里,页面上留个入口能看。出问题第一时间知道线上跑的是哪个版本,比瞎猜强太多。
回滚只解决代码问题。如果这次上线还改了数据库结构,回滚代码不一定能恢复,所以涉及表结构的改动要单独做预案,别混在一次部署里。
只传改动的文件,能省不少事
有些站点的资源目录特别大,几百兆的图片和视频,全量检出一次要好几分钟。这种情况可以把大文件目录加到 .gitignore,用 rsync 单独同步,代码归代码,资源归资源。
另一个办法是用 git 的 sparse-checkout,只检出你需要的子目录。适合那种一个仓库放多个站的情况。
同步资源的时候 rsync 记得加 --delete 要谨慎,它会删掉目标端多余的文件,如果目标目录里有人手动放了东西,会被一起清掉。第一次跑先加 --dry-run 看一眼要删什么。
分支该怎么用
个人站不用搞得太复杂,两个分支就够:main 放线上版本,dev 放你自己折腾的东西。改完在 dev 上测,没问题再合并到 main 触发部署。
千万别在 main 上直接改。你以为改一行没事,结果一个语法错误整站白屏,回滚还得再花几分钟。
如果同时改两个不相关的东西,可以开临时分支,改完合回去就删掉。分支多了自己都记不清,反而容易搞混。
合并冲突是常态,遇到别慌,先看冲突的文件,多数情况是两边都改了同一段,手工取一个正确的版本就行。合并前先 commit 当前改动,别带着未提交的修改去合并。
最常见的几个坑
第一个,服务器上的文件和仓库不一致。有人在服务器上直接改了文件,下次 pull 就冲突。规矩定死:只改本地,服务器只读。
第二个,权限变了。git 检出出来的文件属主可能不是 www,导致 PHP 读不了。钩子里补一条 chown 和 chmod,一次配好省很多事。
第三个,缓存没清。代码更新了但页面还是旧的,多半是缓存。部署脚本最后一步固定加一个清缓存的命令。
第四个,忘了装依赖。composer 或者 npm 的依赖变了但没重新安装,页面报类找不到。依赖文件有变动的提交,部署脚本里要跟着跑一次安装。
部署脚本本身要写多细
脚本不用长,二十行以内就够,但顺序要对。第一步拉代码,第二步同步依赖,第三步改权限,第四步清缓存,第五步重启需要的服务,最后一步打日志记下这次部署的版本号和时间。
每一步都要判断上一步的结果,前一步失败就立刻退出,不要往下走。用 set -e 让脚本遇到命令失败就停,这是最省事的做法。
日志一定要留。最简单的做法是把每次部署的输出追加到同一个文件,出问题翻这个文件,能看到当时到底卡在哪一步。日志文件要定期清理,不然几个月就撑到几百兆。
脚本本身也要纳入版本管理。很多人把部署脚本临时写在服务器上,改来改去最后自己都不记得最新版是哪个。放进仓库,跟代码一起维护。
还有个小习惯值得养成:每次部署完,脚本最后 curl 一下首页,把返回码打到日志里。这一步能立刻发现白屏,不用等用户来告诉你。
