如果你还在每天手动npm run build然后打开 FileZilla 把 dist 目录拖到服务器上这篇文章就是写给你的。我自己之前就是这么干的项目小的时候还能忍等到前后端联调频繁、一天要发好几个版本的时候手动部署就成了纯粹的体力活。GitLab 上同事刚合入代码我还要跑去问“哪个分支、要不要重新构建”问完再开终端、切目录、build、上传…… 来回折腾十分钟等真正上线的时候新需求的热乎劲早就过去了。所以后来我把整个流程重构成Docker 1Panel GitLab Webhook。这套组合的核心思路是——把 GitLab 装进 Docker用 1Panel 管理 Docker 和网站代码推送到 GitLab 触发 WebhookWebhook 再执行一个部署脚本自动拉代码、构建、发布到站点目录。全程不需要人到服务器上敲命令。这篇保姆级教程会覆盖从零安装 Docker、1Panel到 GitLab 容器化部署、Webhook 配置、部署脚本编写再到常见问题排查的完整链路。适合手里有一台 4G 内存以上 Linux 服务器、正在用或准备用 GitLab 管理前端代码的开发者。1. 这套方案要解决什么为什么偏偏是 1Panel Docker在动手之前我想先把方案的选择逻辑讲清楚。很多人一上来就去装 Jenkins、配 GitLab CI结果搞了两天 pipeline 还没跑通项目已经要上线了。其实你的需求可能没有想象中那么复杂。1.1 你真正要解决的问题是什么大多数前端项目自动部署本质就三件事代码从远程仓库拿到服务器上在服务器上执行依赖安装和构建产出静态文件把静态文件放到 Nginx 能访问到的目录并处理好旧文件你说它是 CI/CD 也行说它是“自动执行脚本”也没错。很多中小团队根本用不到多阶段流水线、并行构建、制品管理这些高级功能他们要的就是“push 代码后网站自动更新”。所以我在选型时定了一个原则解决问题的最小方案。只要能稳定、可复现、出问题时能快速排查就不要引入太多复杂的组件。1.2 为什么不是 Jenkins也不是 GitLab CI这里我想说点可能会得罪人的实话。Jenkins 功能确实强大插件生态也成熟。但 Jenkins 本身是个很重的应用Java 环境、插件安装、权限控制、系统配置每一项都要花时间去维护。如果你团队里没有专人负责运维Jenkins 很容易变成“装完就没动过”的僵尸系统。而且 Jenkins 对资源的要求不低小服务器上再跑一个 Jenkins内存就已经很紧张了。GitLab CI 是跟 GitLab 配套的最佳选择如果你的项目已经上了 GitLab且 Runner 也装了用它做流水线是非常顺的。但 GitLab CI 有个学习门槛.gitlab-ci.yml怎么写、Runner 和 Executor 怎么选、缓存和 artifact 怎么配这些对前端同学来说不是那么直观。我见过不少团队把.gitlab-ci.yml改了一个下午还没能跑出第一个绿色任务。而 1Panel Docker Webhook 的组合胜在直观所有东西都有界面Docker 的容器、镜像、网络状态一眼就能看到Webhook 的触发链路短代码 push 过去一个 HTTP 请求就能让服务器执行脚本。这套方案不需要 Jenkins 的庞大体系也不需要理解完整的 CI 概念。它适合预算有限、人手有限、但希望部署自动化的团队。1.3 这套方案的数据流向我用最简单的文字描述一下完整的链路开发者 push 代码到 GitLab ↓ GitLab 触发 Webhook向 1Panel 上的 webhook 服务发送 POST 请求 ↓ webhook 服务执行部署脚本 ↓ 脚本拉取最新代码 → 安装依赖 → 构建 → 同步到站点目录 ↓ Nginx 直接服务新的静态文件整条链路里需要人工参与的只有第一步提交代码。后面的所有动作都是自动完成的。这也就是为什么我说 1Panel 是合适的选择它本身自带开源的 webhook 应用可以直接接收 HTTP 请求并触发命令省去了自己写一个 webhook 接收端的功夫。同时 1Panel 有可视化的 Docker 管理界面GitLab 容器挂没挂、日志打什么了鼠标点几下就能看到这对习惯了“看到界面才安心”的朋友非常友好。2. 环境准备到 1Panel 上线的完整过程方案想清楚了就可以开始动手。这一节我按“从零到能打开 1Panel 面板”来写每一步都给你一个可以直接抄的命令和解释。2.1 服务器需要准备什么先说说硬件配置。GitLab 社区版对内存比较挑剔官方建议是 4GB 起步。我实际测试下来2GB 的机器跑 GitLab 会很吃力——不是不能跑是跑一会儿就 OOM。如果你预算允许直接上 4GB 内存的机器硬盘至少 40GB。系统上用 Ubuntu 22.04 或者 Debian 12 都行CentOS 7 已经过了最佳时期不建议新装环境再选它。服务器可以不用先装 Docker。1Panel 的安装脚本会有选项让你顺便装 Docker但我不太推荐这么做——我遇到过网络不太好导致安装脚本卡在 Docker 安装步骤的情况。分开装哪一步出问题都能看得更清楚。2.2 安装 Docker 和 Docker Compose 插件Docker 的安装命令不同系统略有差异。Ubuntu/Debian 系统我用的是国内环境中比较稳定的一行命令curl -fsSL https://get.docker.com | bash -s docker这个脚本会自动检测系统、配置软件源并安装 Docker Engine。装完以后执行docker version能看到 Client 和 Server 两段信息都正常返回就说明装好了。注意 Server 段如果报错或者连接不上多半是 docker daemon 没起来执行systemctl start docker再试。如果你用的是 Windows 电脑想先本地实验情况稍有不同。Windows 上跑 Docker 基本都要靠 WSL2 后端常见的问题是 Docker Desktop 启动时报 “virtualization support not detected” 或 “virtualisation support wasnt detected”。这个报错的意思是你的 CPU 虚拟化没开或没生效。解决办法是进 BIOS 打开 Intel VT-x 或 AMD SVM然后在 Windows 功能里勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”执行wsl --update更新内核再重启 Docker Desktop。我试过在 Windows 11 上这样弄好之后跑 1Panel 的 Linux 版其实意义不大所以 Windows 装 Docker 更适合用来开发调试真正部署还是建议放到 Linux 服务器上。Docker Compose 是 GitLab 部署时常用的工具。新版 Docker 已经包含 compose 插件你可以验证一下docker compose version能输出版本号就不用额外安装。如果提示没有这个命令再单独装 docker-compose-plugin 的二进制包即可。2.3 安装 1Panel 并初始化1Panel 的安装命令在官方文档里可以直接拿到大概是这样curl -sSL https://resource.fit2cloud.com/1panel/package/production/install.sh -o quick_start.sh sudo bash quick_start.sh脚本会先检查环境然后让你选择安装目录和监听端口。我建议把默认的 8443 端口改掉改成一个不常用的高位端口减少被扫描的概率。安装完成后脚本会输出一个面板访问地址和随机生成的用户名密码。第一次打开面板地址浏览器可能会提示证书不安全因为是自签名证书。直接继续访问就行。进去第一件事是设置新的管理员密码然后尽快到「设置 → 面板设置」里把端口、域名、HTTPS 证书这些做一次调整。如果你有自己的域名强烈建议用 1Panel 申请一个免费的 Lets Encrypt 证书挂到面板上这样以后打开面板不会老被浏览器拦一道。2.4 装完 1Panel 先做三件事这里分享我每次搭新环境都会做的三件基础操作看起来不起眼但后面能省不少事开启系统防火墙并只放行必要端口。1Panel 默认会管理防火墙你可以在面板的「安全」里配置把 SSH、80/443、面板端口放行其余全关。创建网站所需的目录结构。1Panel 会自动建好/opt/1panel这个目录后续 OpenResty1Panel 内置的 Nginx 分支产生的站点文件都在里面。建议你先建一个/data/wwwroot统一存放前端项目的发布目录这样部署脚本里面路径不容易搞混。配置邮件告警。1Panel 支持设置告警通知当 CPU、内存、磁盘超过阈值或者容器异常时可以发邮件。我吃过一次亏GitLab 容器半夜挂了第二天早上才发现。有了告警至少能第一时间知道。3. GitLab 容器化部署端口、权限和第一次登录1Panel 上手之后接下来就是把 GitLab 跑起来。这一步看似简单但端口规划、初始密码、权限配置这几个坑如果不提前踩一下后续会让你怀疑人生。3.1 在 1Panel 里一键装 GitLab 社区版1Panel 的应用商店里直接搜索 GitLab会看到社区版的一键安装入口。安装前会让你填端口映射这里要特别注意。GitLab 容器内部默认监听三个端口HTTP 80、HTTPS 443、SSH 22。但你宿主机上的 80 和 443 很可能要留给 Nginx 服务网站22 是 SSH 登录端口也不能动。所以你要做的是把容器内部的端口映射到宿主机的其他端口容器内部端口宿主机映射端口说明808920GitLab 网页访问端口4438921备用 HTTPS 端口222222GitLab SSH 端口这里我解释一下为什么建议把 SSH 端口改成 2222 而不是直接用 22。如果 GitLab 和宿主机共用 22 端口一旦 GitLab 容器出问题你连服务器的 SSH 都进不去风险很大。映射到 2222 后GitLab 自己的 Git 操作走ssh://git服务器IP:2222/xxx.git跟服务器的系统 SSH 完全隔离。我实际用下来没有任何不便反而排障时清晰很多。端口映射填完启用“端口外部访问”再点确认。1Panel 会先从 Docker Hub 拉取gitlab/gitlab-ce镜像这个镜像比较大大概 1GB 多网络不好的时候可能要等很久。拉取期间你可以在面板的「容器」页面看到容器状态是 running但实际上 GitLab 内部还要做初始化需要等几分钟到十几分钟不等。判断 GitLab 是否真正就绪可以在服务器上执行docker logs -f gitlab等日志里出现Congratulations! GitLab has been installed字样就说明初始化完成了。读秒不如看日志别光凭感觉刷新网页。3.2 第一次登录要做的几件事GitLab 初始化完成后用浏览器访问http://服务器IP:8920。第一次打开时它会让你设置 root 用户的初始密码。这里要提醒一件事新版 GitLab 的初始登录密码其实是自动生成在容器内的/etc/gitlab/initial_root_password文件里的而且这个文件会在 24 小时后自动删除。如果你打开页面时没有被引导设置密码那就在服务器上执行docker exec -it gitlab cat /etc/gitlab/initial_root_password拿到初始密码然后用root账号登录登录后马上去「偏好设置」里改一个自己的强密码。3.3 配置项目和导入代码的方式登录 GitLab 后第一步是创建项目。在 1Panel 界面里找到 GitLab 容器的端口直接访问。创建项目有两种常用路径新建空项目适合项目还没用 Git 管理的场景。创建完会在项目主页显示远程仓库地址你本地按提示执行git remote add origin http://服务器IP:8920/用户名/项目名.git即可。导入已有项目如果你本地已经有完整 Git 仓库直接选“导入项目”填本地仓库地址或直接上传文件。我日常用得最多的是本地git push上去因为历史提交记录能完整保留。我自己的习惯是先在 GitLab 上建一个空项目再在本地把 remote 指过去。这样操作最简单也不容易出现 push 被拒、分支不匹配之类的问题。3.4 权限设置developer 到底能不能提交到 master这个属于热词里很多人搜的问题我单独说一下。GitLab 的默认权限模型中developer 角色默认是可以 push 到自己有权限的分支但对于master或main这种受保护分支默认是不允许直接提交的除非项目管理员在「设置 → 仓库 → 受保护的分支」里调整规则。这里我给一个实际建议不要为了图省事把所有成员都提升成 Maintainer。正确的做法有两种保留master为受保护分支开发走 Merge Request由 Maintainer 审核后合并。如果团队很小、信任度高可以把master的“允许合并”和“允许推送”都改成Developers Maintainers这样所有人可以直接 push部署链路也能跑通。自动部署的 Webhook 触发的是 push 事件所以只要你把目标分支设置成允许 developer 推送Webhook 就能正常工作。这个版本的热词里有“gitlab developer 可以提交代码到 master 吗”你在面板里打开受保护分支配一下就行不需要跑到命令行去改配置。4. 自动部署的心脏Webhook 触发与部署脚本GitLab 容器跑起来之后自动部署的核心就来了怎么让代码“推上去就生效”。这一节是整个教程里最需要耐心看的部分我会把触发方式和脚本内容分开讲透。4.1 触发方式选择Webhook 还是 GitLab CI你可能听过 GitLab CI但我不想在这个方案里默认推荐它。原因前面说过CI 需要额外的 Runner 组件、YAML 语法学习成本、还有内存开销。而 Webhook 是一个非常轻量的东西——实际上就是一个 HTTP POST 请求GitLab 在特定事件发生时往你指定的 URL 发送一段 JSON。我在 1Panel 里装的是应用商店的 webhook 应用它负责接收 GitLab 发来的请求然后执行预先写好的命令。装好之后配置面板会给你一个触发地址格式类似http://服务器IP:8080/hooks/部署脚本名这个 webhook 应用本身也是个 Docker 容器端口映射时注意不要跟其他端口冲突。装好之后记得测一下浏览器能不能打开那个 8080 端口。如果你用的不是 1Panel 自带的 webhook 应用也可以用极简方案写一个 30 行的 Python Flask 服务接收 POST 请求后调用 shell 脚本。但既然 1Panel 自带这个功能能少写代码就少写代码。4.2 部署脚本怎么写不管用什么接收请求最终要执行的还是那个部署脚本。我贴一份我自己目前正在用的脚本里面的路径和分支名你按实际情况替换。#!/bin/bash # 配置区 PROJECT_DIR/data/wwwroot/my-project GIT_URLhttp://gitlab:8920/mygroup/my-project.git BRANCHmaster DIST_DIR$PROJECT_DIR/dist SITE_DIR/opt/1panel/apps/openresty/openresty/www/sites/my-site/index LOCK_FILE/tmp/deploy_my_project.lock LOG_FILE/var/log/deploy_my_project.log GIT_TOKEN你的GitLab访问令牌 # # 防止并发部署如果上一个部署还在跑直接退出 if [ -f $LOCK_FILE ]; then echo [$(date %Y-%m-%d %H:%M:%S)] 已有部署任务执行中跳过本次 $LOG_FILE exit 0 fi touch $LOCK_FILE trap rm -f $LOCK_FILE EXIT # 1. 更新代码 cd $PROJECT_DIR || exit 1 git fetch origin $BRANCH git reset --hard origin/$BRANCH # 2. 安装依赖 npm ci || npm install # 3. 构建 npm run build # 4. 同步到站点目录使用 rsync --delete 删除旧文件 rsync -a --delete $DIST_DIR/ $SITE_DIR/ echo [$(date %Y-%m-%d %H:%M:%S)] 部署完成: $BRANCH $(git rev-parse --short HEAD) $LOG_FILE注意几个细节git fetch git reset --hard而不是git pull。你部署目录里的代码工作区不应该有手动本地修改用 reset 可以保证服务器上的代码跟远程完全一致。npm ci比npm install更严格会严格按照package-lock.json的锁文件安装避免同一个项目在本地和服务器构建出不同结果。rsync --delete是关键。很多前端项目 build 之后 dist 里会有 hash 文件名旧文件不清理的话站点目录会越积越乱。--delete会删除目标目录里源目录没有的文件保证站点目录跟最新的 build 产物完全一致。我这里用的是访问令牌Personal Access Token来拉取私有仓库。你可以让 git 在 http URL 里带上 tokengit remote set-url origin http://root:${GIT_TOKEN}服务器IP:8920/mygroup/my-project.git这里也顺便回应了另一个常见疑问部署脚本用的是服务器的 Git 身份跟开发者的账号无关。没有必要把某个人开发者的账号密码配在脚本里用 GitLab 的访问令牌或者部署令牌更安全权限还可以精确限制在单一项目上。4.3 在 webhook 里注册这个脚本脚本文件命名为deploy.sh放在/usr/local/bin/下面然后给执行权限chmod x /usr/local/bin/deploy.sh然后在 1Panel 的 webhook 应用里添加一个新的 hook把这个脚本的绝对路径作为要执行的命令填进去。保存后你就能得到一个专属的触发 URL。这一步配置完成后你在服务器上手动执行一遍脚本确认构建流程没问题再往下接 GitLab Webhook。4.4 GitLab 侧配置 Webhook回到 GitLab 项目页面进入「设置 → Webhooks」。填写两样东西URL填刚刚 webhook 应用给你的触发地址触发器勾选Push events并且可以只填你希望自动部署的分支master保存之后GitLab 会有一个“测试”按钮。点一下测试然后去 webhook 应用的后台或者服务器日志里看看有没有收到 POST 请求。我遇到过很多新手在这里直接信了 GitLab 的“成功”提示结果部署根本没触发原因就是 outbound requests 被拦截了。这里有一个必备设置GitLab 默认允许发送出站请求但对自己所在的服务器地址普遍默认拦截。你要打开「管理员 → 设置 → 网络 → 出站请求」勾选Allow requests to the local network from webhooks and integrations。这一步不做Webhook 永远只会显示 404 或超时。4.5 为什么要用部署令牌而不是账号密码上面提到 GIT_TOKEN我再展开说两句安全设计。很多人图方便直接拿一个开发人员的 GitLab 账号密码放在部署脚本里。听起来能跑但隐患很大如果这个人离职密码一改部署就挂如果密码泄露别人可以拿到你整个 GitLab 的读写权限。正确做法是在 GitLab 项目里进入「设置 → 仓库 → 部署令牌」创建一个只针对该项目、只有read_repository权限的 token。把 token 直接拼接在仓库地址里或者放到服务器的~/.git-credentials文件里避免命令日志里暴露。我发现用部署令牌之后因为权限只覆盖单个仓库即便意外泄露攻击者也只能读到那一个项目不能控制整个 GitLab。这个隔离效果是我比较看重的。5. 实测排错从 404 到权限不足再到容器丢数据这一节我把自己实际踩过的坑和排查思路整理出来。你不一定每条都遇到但真遇到的时候按这个思路查基本都能快速定位。5.1 Webhook 一直显示 404 或请求失败这是自动部署里最常见的故障。99% 的情况不是 webhook 应用没装好而是 GitLab 的出站请求没放开。解决方法上面已经说了在管理员后台放行本地网络。如果你已经放行了仍然失败就用下面的排查顺序在服务器上先curl -X POST http://localhost:8080/hooks/xxx手动触发如果本机能通、外网不通检查 8080 端口是不是只绑定到了 127.0.0.1。在 1Panel 的 webhook 容器里看日志确认请求到底有没有到达。确认 GitLab 侧填写的 URL 里的 IP 是服务器公网IP还是内网 IP。如果你通过 1Panel 反代域名访问那 URL 要按域名填。5.2 部署脚本执行失败怎么定位脚本的日志我统一写进了/var/log/deploy_my_project.log。每次部署失败先看这个文件再去看 webhook 应用的容器日志。按我的经验脚本失败多半是以下几个原因npm ci卡住或超时多半是服务器上的 npm 源比较慢。解决办法是给 npm 配置国内镜像源npm config set registry https://registry.npmmirror.comgit reset --hard报权限错误说明脚本执行用户的 SSH key 或者 token 没有拉取该仓库的权限。检查部署令牌的权限或确认 remote URL 里的 token 是否仍有read_repository权限。rsync报目录不存在多半是站点目录路径不对。你先手动执行ls看一下 1Panel 建的站点目录实际路径再去改脚本里的SITE_DIR。5.3 GitLab 容器的数据会丢吗我在 1Panel 里安装 GitLab 时会自动挂载数据卷比如/opt/1panel/apps/gitlab/gitlab/data。只要挂载了容器删除重建、升级镜像数据都还在。但我也遇到过有人手滑点了“删除”之后连同数据卷一起删了整个 GitLab 的项目全没了。我的习惯是每次升级 GitLab 前先用 1Panel 的“备份”功能把数据卷打包一次。你也可以在计划任务里加一个每周全量备份到另一块磁盘或者对象存储的任务。这个动作在开发环境看似多余但生产数据一旦丢了你真的会后悔没做。5.4 GitLab 版本相关的坑热词里有句 “login failed. gitlab versions older than 14.0 are not supported”这是 GitLab 官方对旧版本客户端/API 的限制提醒。如果你在浏览器或 IDE 里登录 GitLab 时看到类似提示说明你的 GitLab 版本太老了。GitLab 社区版 14.0 以下版本已经进入 EOL不但功能受限很多安全漏洞也没有修复。之前新闻里披露过几个高危漏洞基本都通过升级到 14.x 以上版本解决。所以我的建议是安装时直接拉gitlab/gitlab-ce:latest或指定一个比较新的稳定版本标签不要用几年前的旧镜像。1Panel 升级应用也比较方便点一下重建容器即可只要数据卷没丢版本升级基本无损。6. 一些实战建议和后续可以扩展的方向整套自动部署跑通之后日常使用会非常省心。但我还是想再分享几个自己在实际使用中沉淀出来的习惯以及如果你想把这套方案做得更完善可以从哪些方向入手。第一个建议是给部署脚本加上“构建失败自动回滚”的机制。我现在脚本里只是set -e让失败就退出但更稳的做法是构建前把旧的 dist 目录备份一下一旦构建或同步失败能够快速恢复上一版。你可以这样改cp -r $SITE_DIR ${SITE_DIR}.bak # 构建... # 同步... # 成功后再删掉 .bak虽然我很少真的用到回滚但有一次 Node 依赖升级后构建报错导致线上直接被影响了还好有备份目录能顶一下那之后我就一直保留了这层保险。第二个建议是给部署脚本加上“钉钉/企业微信/邮件”通知。1Panel 的 webhook 应用本身可以配置执行成功或失败后的通知动作动作可以是一个新的 webhook 请求。这样每次部署完成后团队群或者你的邮箱里就能收到一条“项目 master 分支部署完成commit: xxxxxx”的消息比大家都去问“发了吗”要体面很多。第三个建议是如果你后续需要更复杂的流程比如多环境部署、自动化测试、镜像构建再考虑走 GitLab CI。其实你已经把 GitLab 跑在 Docker 里了再装一个 GitLab Runner 也是一条命令的事数据流跟现在这套 Webhook 方案是共存的。到时候你可以把“部署”这个动作交给 Runner但把“触发”的部分继续交给 GitLab不用推翻重来。最后说一句我实操下来的感受这套方案里最容易让人卡住的不是 Docker也不是 GitLab而是“第一次把 Webhook 触发到脚本执行串起来”的那个环节。只要你先把脚本手动跑通再让 GitLab 去触发那个手动已经跑通的东西整条链路的故障率就会大幅降低。动起手来先把服务器上那台 GitLab 装好把第一个手动部署跑通再去接 Webhook——你会发现自动部署真的就是一层窗户纸。