1. 项目背景:为什么要把官网搬进容器里还要上 HTTPS
先说结论:这活儿我干过不止一次,每次都有新坑,但整体思路是固定的,今天把这套流程完整捋一遍,按步骤走就能落地。
起因很简单,公司官网最初部署在一台老物理机上,用的还是 HTTP 明文访问。后来客服那边接连收到用户反馈,说浏览器地址栏总是提示“不安全”,部分浏览器干脆直接拦截页面,搞得像钓鱼网站。再加上有一个页面要嵌入支付跳转,第三方支付接口强校验域名证书——HTTP 根本过不了。于是“用 Docker 通过 Nginx 部署官网并支持 HTTPS 访问”这个任务就列上了日程。
为什么选 Docker 而不是直接在宿主机装 Nginx?核心原因是环境隔离和可迁移性。官网虽然流量不大,但涉及静态页面、若干 PHP 业务脚本和图片资源,如果把 Nginx 直接装到宿主机,后续迁移服务器、备份恢复都要重新安一遍环境,烦得很。用 Docker 把 Nginx 镜像固定下来,整个 Web 服务就变成一个容器,数据卷单独挂载,换机器时只要把镜像和挂载目录拷过去,几行命令就能恢复。这个优势在日常维护里非常明显。
再说 HTTPS。现代浏览器对 HTTP 站点的态度已经很明显了,Chrome 地址栏直接标注“不安全”,Firefox 也类似。公司官网上还有企业介绍、联系方式、在线表单等交互内容,用户输入手机号、留言信息时走明文传输,技术上就属于数据泄露风险。所以 HTTPS 不是“可选项”,而是“必选项”。这里不聊政策层面,单从技术角度讲,TLS 加密可以保证用户浏览器和服务器之间传输的数据无法被中间人窃取或篡改,支付类对接也需要它。
结合我自己踩过的坑,把这次部署拆成四块:环境准备、证书申请与配置、Docker 编排落地、日常维护与问题排查。下面逐步展开。
2. 部署前要想清楚的几个关键点
2.1 架构选型:镜像、端口、数据卷怎么设计
官网技术栈是“Nginx + PHP + MySQL”,但我这次只需要 Nginx 处理静态文件和反向代理,PHP 业务脚本由另一组容器负责,MySQL 保持原有独立部署。所以用到的镜像很简单:nginx:1.24-alpine。选 alpine 版本是因为镜像体积小,基础操作系统占用少,攻击面也相对小,适合官网这种轻量级页面服务场景。
端口规划要提前想好,不然容易和宿主机已有服务冲突。我见过很多人把 Nginx 直接映射到80和443,结果发现宿主机上已经装了一个 Apache 占了 80,或者防火墙没放行 443,折腾半天才找到原因。我的建议是先用ss -lntp查看宿主机端口占用情况,确认 80 和 443 空闲再映射。如果被占用,要么停掉旧服务,要么用8080这类高位端口做演示,但公网访问官网还是建议走标准端口,省得用户在浏览器里手动输入端口号。
数据卷设计上,把 Nginx 配置目录、站点根目录、日志目录、证书目录四块分开挂载。目录结构如下:
/opt/company-site/ ├── conf/ │ ├── nginx.conf │ └── conf.d/ │ └── default.conf ├── html/ │ ├── index.html │ ├── css/ │ ├── js/ │ └── images/ ├── logs/ │ ├── access.log │ └── error.log └── ssl/ ├── company.cer ├── company.key └── ca-chain.crt2.2 证书选择:自动签发的免费证书还是商业证书
官网这种场景,证书选择的核心考量是“签发速度、兼容性和维护成本”。
如果给内部测试环境用,自签名证书就够了。一条命令生成,浏览器会提示证书不可信,但至少能验证 Nginx 的 TLS 配置是否正确。命令如下:
mkdir -p /opt/company-site/ssl openssl req -x509 -nodes -newkey rsa:2048 \ -keyout /opt/company-site/ssl/company.key \ -out /opt/company-site/ssl/company.cer \ -days 365 \ -subj "/CN=www.example-domain.com"但正式公网环境绝对不能这么干。用户打开官网看到证书警告,直接关页面,信任度归零。公网环境我推荐使用自动化签发的免费 DV 证书,通过 Certbot 客户端从权威机构免费获取,有效期通常为 90 天,支持自动续期,整个流程可以在命令行里完成。它生成的证书链完整,主流浏览器都信任根证书,官方文档里也有 Nginx 配置示例,适合技术团队自主维护。
商业 OV 或 EV 证书适合银行、政务类网站,因为需要人工审核企业身份,签发周期要几天甚至更久,官网场景没必要花这个钱和时间。
2.3 签发证书前要准备的 DNS 与域名
证书签发的必要条件是“证明你拥有这个域名”。所以正式动手前,先确认域名已经解析到服务器公网 IP,并且 80 端口对外可达。我常用的验证方式:
ping www.example-domain.com curl -I http://www.example-domain.comcurl 返回正常的 HTTP 响应对了。因为 HTTP-01 验证方式的原理是:CA 会访问你的域名下的某个特定路径(例如http://www.example-domain.com/.well-known/acme-challenge/xxx),如果能访问到约定的响应文件,就证明域名控制权在你手上。
这里有个很关键的坑:如果你的官网有 CDN 或 WAF 在前面,防火墙只放行了 443 但挡住了 80,证书签发会失败。因为验证请求是走 80 端口明文 HTTP 过去的。所以要么临时放行 80,要么用 DNS-01 验证方式。DNS-01 方式通过在你的 DNS 解析记录里添加一条TXT记录来验证域名控制权,不依赖 80 端口,但需要证书签发工具能调你的 DNS 服务商 API,配置起来稍微复杂一点。官网场景没有 CDN 的情况下,用 HTTP-01 就够了。
3. 证书申请与 Nginx 配置实操
3.1 用 Certbot 一次性签发证书并自动续期
这里用certbot/certbot容器来操作,好处是不用污染宿主机环境,也不用安装 Python 依赖。先把容器跑起来,通过 webroot 目录方式验证域名:
docker run --rm -p 80:80 \ -v /opt/company-site/html:/var/www/html \ -v /opt/company-site/ssl:/etc/letsencrypt \ certbot/certbot certonly \ --standalone \ -d www.example-domain.com \ -d example-domain.com \ --email admin@example-domain.com \ --agree-tos \ --no-eff-email注意这里用了--standalone模式,意思是 Certbot 临时启动一个独立的 HTTP 服务来响应验证请求。所以运行这个命令时,宿主机 80 端口必须是空闲的,不能被别的服务占用。如果 Nginx 已经在运行,就得用--webroot方式,让 Certbot 把验证文件写入站点根目录,然后由 Nginx 响应请求。
签发成功后,证书文件保存在/opt/company-site/ssl/live/example-domain.com/目录下,其中有四个关键文件:
| 文件 | 作用 |
|---|---|
cert.pem | 站点证书,只包含你的域名证书本身 |
chain.pem | 中间证书链,不包含站点证书 |
fullchain.pem | 站点证书 + 中间证书链,Nginx 常用的ssl_certificate配置项用这个 |
privkey.pem | 私钥,权限必须严格限制,Nginx 的ssl_certificate_key用这个 |
自动续期我写了一个定时任务脚本,放在宿主机上,每天执行一次检查。脚本内容很简单:
#!/bin/bash docker run --rm \ -v /opt/company-site/ssl:/etc/letsencrypt \ -v /opt/company-site/html:/var/www/html \ certbot/certbot renew --webroot -w /var/www/html --quiet # reload nginx to pick up new certs docker exec company-nginx nginx -t && docker exec company-nginx nginx -s reload脚本通过 Cron 每天早上 2 点执行,续期成功后重载 Nginx。注意nginx -s reload是平滑重载,不会中断现有连接,这个操作很重要。我刚做的时候没加重载这一步,结果证书到期自动续期成功,但 Nginx 还在用旧证书,页面直接报错。后来才意识到证书文件虽然更新了,但 Nginx 进程不会自动读取,必须 reload。
3.2 Nginx 配置 HTTPS 和 HTTP 跳转
证书就位后,写default.conf。先看完整配置:
server { listen 80; server_name www.example-domain.com example-domain.com; # Let's Encrypt 验证路径,必须保持可访问 location /.well-known/acme-challenge/ { root /var/www/html; } # 其他 HTTP 请求一律 301 跳转到 HTTPS location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; http2 on; server_name www.example-domain.com example-domain.com; ssl_certificate /etc/nginx/ssl/live/example-domain.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/live/example-domain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; root /var/www/html; index index.html; location / { try_files $uri $uri/ =404; } # 静态资源缓存 30 天 location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2)$ { expires 30d; add_header Cache-Control "public, immutable"; } # 安全响应头 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; }配置里有几个点值得单独说明。
第一个是ssl_protocols。我只开了 TLSv1.2 和 TLSv1.3,没有开 TLSv1.0 和 TLSv1.1。这两个老版本协议存在已知漏洞,如今主流浏览器都已不支持。关掉它们能通过安全检查,也不会影响用户访问。
第二个是http2 on指令。Nginx 1.25 及以上版本支持直接写http2 on;,旧版本要在listen 443 ssl http2;里加参数。HTTPS 下启用 HTTP/2 可以多路复用连接,页面上的多个静态资源可以并行在一个连接里传输,体感上比 HTTP/1.1 快不少。官网有很多图片和 CSS 文件,这个优化立竿见影。
第三个是Strict-Transport-Security,也就是 HSTS。它告诉浏览器:接下来一段时间内,这个域名只允许用 HTTPS 访问,即使用户手动输入http://也会被浏览器内部自动转换成 HTTPS。这个头一旦启用就有“后悔期”,所以第一周我设置的是max-age=86400一天,确认站内没有任何 HTTP 资源残留后才改成长时间。
3.3 证书链文件注意“完整的中间证书”
证书配置最容易被忽略的就是证书链不完整。签发证书时得到的fullchain.pem已经包含了中间证书,直接用没问题。但如果你是从别处拿到的cer和key文件,要确认cer文件里除了域名证书还有中间证书。
判断方法很简单:
openssl crl2pkcs7 -nocrl -certfile /opt/company-site/ssl/company.cer | openssl pkcs7 -print_certs -noout如果输出只有一条证书信息,说明缺中间证书。这时候要把 CA 下发的中间证书内容追加到站点证书后面,拼接成一个完整链:
cat site.cer intermediate.cer > fullchain.cer拼接顺序很重要,站点证书在前,中间证书在后。Nginx 读取时会按顺序构建证书链,顺序乱了可能导致部分老设备校验失败。这个坑我在内网测试机上踩过,浏览器桌面端正常,但某品牌手机浏览器打开就提示“证书链不完整”,排查了半天才定位到是证书拼接顺序问题。
4. Docker 化部署与编排
4.1 Dockerfile 方案:直接用官方镜像还是自定义构建
官网只是静态页面加少量动态代理,我选择直接用nginx:1.24-alpine官方镜像,不额外构建 Dockerfile。理由是官方镜像已经包含了 Nginx 主程序、默认配置和健康检查工具,体积小,安全更新由官方维护,自己写 Dockerfile 反而增加维护成本。
但如果你需要在镜像里内置监控脚本、日志轮转工具或者自定义 Nginx 模块,那就写 Dockerfile。一个典型例子:
FROM nginx:1.24-alpine RUN apk add --no-cache \ tzdata \ curl \ logrotate ENV TZ=Asia/Shanghai COPY conf/nginx.conf /etc/nginx/nginx.conf COPY conf/conf.d/ /etc/nginx/conf.d/ RUN mkdir -p /var/www/html /var/log/nginx这个 Dockerfile 做了三件事:固定时区为东八区,避免容器日志时间和宿主机不一致;安装日志轮转工具,防止 log 文件无限增长;把预先写好的 Nginx 配置文件打进去,容器启动即生效。
4.2 docker-compose 编排:把容器跑起来
docker-compose 是管理单个容器最省心的方式,不用每次敲冗长的docker run命令。写一个docker-compose.yml:
version: "3.8" services: nginx: image: nginx:1.24-alpine container_name: company-nginx restart: always ports: - "80:80" - "443:443" volumes: - /opt/company-site/conf/nginx.conf:/etc/nginx/nginx.conf:ro - /opt/company-site/conf/conf.d:/etc/nginx/conf.d:ro - /opt/company-site/html:/var/www/html:ro - /opt/company-site/ssl:/etc/nginx/ssl:ro - /opt/company-site/logs:/var/log/nginx - /etc/localtime:/etc/localtime:ro environment: - TZ=Asia/Shanghai启动命令:
cd /opt/company-site docker compose up -d docker compose psvolumes那一列的:ro后缀代表只读挂载。配置文件、证书、站点文件都以只读方式挂载进容器,好处是即使容器被攻破,攻击者也无法篡改宿主机上的文件。日志目录不加:ro,因为 Nginx 要往里写入日志。
/etc/localtime挂载是为了让容器时区和宿主机一致。Nginx 错误日志和访问日志里的时间戳默认是 UTC 时间,不统一后面排查问题会特别痛苦。我见过一次凌晨排查线上异常,因为日志时间差了 8 个小时,对半天才对上号。
4.3 启动前的自检与验证
启动容器前养成先检查配置的习惯:
docker run --rm -v /opt/company-site/conf:/etc/nginx/conf.d:ro nginx:1.24-alpine nginx -t这个命令用同一个镜像临时起一个容器,只挂载配置目录,执行nginx -t检查语法,检查完自动删除容器。如果配置文件有错误,会在这一步暴露,不会污染正在运行的服务。
确认无误后再启动 compose 服务。启动完看容器日志:
docker compose logs -f nginx正常情况下会看到类似这样的日志:
/docker-entrypoint.sh: Configuration complete; ready for start up然后在本机验证访问:
curl -I http://localhost curl -I https://localhost需要注意,用curl https://localhost访问时,因为证书是签给域名的,不是签给 IP 和localhost,会报证书不匹配。这时候加-k参数跳过证书校验,目的只是验证 TLS 握手是否正常:
curl -kI https://localhost看到HTTP/2 200就说明 HTTPS 服务已经正常启动了。
4.4 解析到域名后的完整验证
本地验证通过后,把域名解析到服务器公网 IP,然后做完整验证。我的验证清单如下:
# 1. HTTP 自动跳转 HTTPS curl -I http://www.example-domain.com # 期待返回 301,Location 指向 https://www.example-domain.com/ # 2. HTTPS 正常访问 curl -I https://www.example-domain.com # 期待返回 200 # 3. 证书链完整且域名匹配 curl --cacert ca-chain.pem https://www.example-domain.com # 4. TLS 协议版本检查 openssl s_client -connect www.example-domain.com:443 -tls1_2第四步会输出大量证书详情,重点看两个地方:Verify return code: 0 (ok)表示证书链验证通过;SSL-Session中的Protocol: TLSv1.2表示握手协议正常。如果Verify return code是某个非 0 数字,就按上一条讲的方法检查证书链完整性。
5. 常见问题与排查技巧实录
5.1 容器启动失败:“port is already allocated”
这个问题发生的频率最高。执行docker compose up -d时提示Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use。
排查思路分两步。第一步确认宿主机上哪个进程占用了端口:
ss -lntp | grep :80第二步决定如何处理。如果占用者是不需要的服务,直接停掉;如果是需要的服务,Nginx 换端口映射,比如8080:80,然后调整防火墙规则。
如果ss看不到占用进程,但端口仍然冲突,可能是 Docker 内部网络残留的容器没有清理干净。用docker ps -a查看所有容器,找到占用端口的容器删掉,再重新docker compose up -d。这类“幽灵占用”通常出现在你先前跑过手动 docker run,又没及时删除容器的情况。
5.2 证书自动续期不生效
续期脚本我写了,也加了每日定时任务,但翻日志一看,最近一次续期根本就没执行。检查了几个地方。
首先确认定时任务有没有挂上:
crontab -l再看脚本执行权限:
ls -l /opt/company-site/renew-cert.sh没有执行权限就chmod +x加上。
还有一种情况是续期尝试执行了,但因为 80 端口被 Nginx 容器占用,webroot 方式无法工作,报urn:acme:error:unauthorized之类的错误。解决办法是把续期脚本改成在 Nginx 还在跑的情况下用 webroot 方式,也就是把验证文件放到站点根目录,让 Nginx 响应验证请求。修改后的续期命令:
docker run --rm \ -v /opt/company-site/ssl:/etc/letsencrypt \ -v /opt/company-site/html:/var/www/html \ certbot/certbot renew --webroot -w /var/www/html --quiet \ --deploy-hook "docker exec company-nginx nginx -s reload"--deploy-hook参数在证书成功续期之后自动执行重载命令,比在脚本外层手动 reload 更精准,因为只有证书真正更新了才触发,避免无意义的容器操作。
5.3 浏览器提示“不是私密连接”但服务器配置是对的
证书有效、日期正确、域名匹配,但浏览器就是不认。这种情况十有八九是系统时间问题。
Nginx 容器的时间如果和宿主机差太多,TLS 证书的有效期校验就会失败。验证方式:
docker exec company-nginx date date两个输出如果不一致,就检查容器时区设置和挂载。在docker-compose.yml里加上TZ=Asia/Shanghai环境变量并挂载/etc/localtime,重启容器:
docker compose up -d --force-recreate nginx还有一种情况是证书还没到生效时间。新签发的证书如果服务器比签发时间提前太多,比如 NTP 没同步导致机器时间快了几个小时,同样会报证书有效期问题。用date命令确认系统时间,必要时安装ntpdate同步标准时间。
5.4 页面有 HTTPS 但仍显示“不安全”
这种情况发生在站内混用了 HTTP 资源。页面主体是 HTTPS 访问的,但其中某个图片、CSS 或 JavaScript 请求用了http://协议,浏览器会整体降级,在地址栏标出警告。
排查方法很直接,打开浏览器开发者工具的 Console 面板,浏览器会把被阻止的“Mixed Content”请求列出来,并标明具体是哪个地址、哪个资源类型。逐一替换成 HTTPS 或改成相对路径即可。
另外,把页面源码下载下来,用 grep 扫描:
grep -rE 'src="http://|href="http://' /opt/company-site/html/把结果里所有硬编码的http://改成https://或者直接去掉协议头写成//。这里有个小建议,站内资源尽量用//这种 protocol-relative URL,浏览器会根据当前页面的协议自动选择 HTTP 还是 HTTPS,省得以后又从 HTTPS 整体迁移回 HTTP 时改一堆代码。
5.5 访问量不大但 Nginx 日志疯狂增长
官网部署完第三天,我看了一下日志目录,两天下来access.log已经涨到 300MB 多。点开一看,全是某个扫描器在不断探测敏感路径,比如/wp-admin/、/admin/login.php、/.env之类。
说明这台服务器已经被公网扫描器盯上了。处理办法有两步。第一步是加一个 Nginx 层面的访问控制,把明显恶意的请求直接返回 403,不让它进业务逻辑:
if ($request_uri ~* "\.(env|git|svn|bak|sql)$") { return 403; }第二步是给 Nginx 配日志轮转,避免日志把磁盘填满。logrotate 配置:
/opt/company-site/logs/*.log { daily rotate 14 compress delaycompress missingok notifempty copytruncate }copytruncate参数很关键。它先复制日志文件再清空原文件,这样 Nginx 的日志文件句柄不会被破坏,不需要重载 Nginx 也能生效。如果直接mv日志文件再新建,Nginx 还在往旧句柄里写,日志就不更新了,排查问题时会一头雾水。
6. 安全加固与日常维护
6.1 TLS 层面还能做哪些加固
上面配置已经涵盖了基本的安全要求,但既然是公司官网,对内对外的安全形象还是要讲究一点。我在这一步补了几个响应头:
X-Frame-Options: SAMEORIGIN可以防止官网被嵌入到恶意网站的 iframe 里,减少点击劫持风险。X-Content-Type-Options: nosniff告诉浏览器不要自作主张猜测响应内容的 MIME 类型,避免部分浏览器把上传的文本文件当成 HTML 执行。Referrer-Policy: strict-origin-when-cross-origin控制跨域跳转时携带的 referrer 信息量,减少来源泄露。
还有CSP,全称 Content-Security-Policy,是浏览器层面的安全策略,可以限制页面只能加载哪些来源的资源。官网这种页面结构非常简单,写一个保守的 CSP 不难:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;" always;注意script-src里面我加了'unsafe-inline',因为官网页面内嵌了一些行内 JavaScript 写的统计代码。如果没有行内脚本,建议去掉这个值,安全等级会更高。
加完所有响应头,用在线检测工具或浏览器开发者工具确认响应头生效,并且没有触发任何 CSP 报错。我踩过一次坑,CSP 写得太严,把页面自己的一个统计脚本给拦了,页面功能正常但统计数据全丢,排查了一下午才发现是 CSP 惹的祸。所以建议分步上线,先开报告模式确认没有误伤再加强制模式。
6.2 容器运行监控与数据备份
官网稳定性直接影响公司形象,但也没必要上特别复杂的监控系统。我的做法比较轻量:写一个宿主机层面的健康检查脚本,每 5 分钟执行一次,检查容器状态和 HTTPS 响应码:
#!/bin/bash if ! docker exec company-nginx nginx -t >/dev/null 2>&1; then echo "[ERROR] $(date) nginx config test failed" >> /opt/company-site/logs/check.log fi code=$(curl -k -o /dev/null -s -w "%{http_code}" https://localhost) if [ "$code" != "200" ]; then echo "[ERROR] $(date) https status: $code" >> /opt/company-site/logs/check.log fi定时任务加在 crontab 里:
*/5 * * * * /opt/company-site/health-check.sh数据备份的重点是配置文件、证书文件和站点根目录。证书文件不能用普通备份,因为私钥泄露等于证书作废。我的原则是ssl/目录整体打包加密后再存到备份盘:
tar zcf backup_site_$(date +%Y%m%d).tar.gz \ -C /opt/company-site conf html ssl openssl enc -aes-256-cbc -salt -in backup_site_$(date +%Y%m%d).tar.gz \ -out backup_site_$(date +%Y%m%d).tar.gz.enc备份文件保留 30 天,再老的就清理掉。官网代码一般有版本库管理,真正的站点数据量不大,备份策略不用太重。
6.3 上线后的复查清单和使用心得
容器跑起来、HTTPS 生效只是第一步。我在上线后的第二天和第七天各做了一次完整复查。第二天主要看证书是否正常签发、HSTS 是否生效、有没有 Mixed Content 报警。第七天主要看日志增长情况、有没有异常扫描、访问量和证书过期倒计时。
复查命令里我觉得最实用的一条是:
openssl s_client -connect www.example-domain.com:443 -servername www.example-domain.com 2>/dev/null | openssl x509 -noout -dates -subject它会输出当前证书的生效时间和过期时间。每周手动跑一次,比只看邮件提醒踏实。
整个项目落地之后,我再回想最初遇到的问题,其实大部分都是环境层面的小坑,反而证明 Docker 化部署的思路是对的。容器迁移方便这个好处,在后来一次服务器换血中体现得特别明显,旧服务迁移时只要停容器、打包数据目录、新机器启动,半小时就恢复了,完全没有当年配环境的焦虑。
下次有机会的话,我会在官网前面加一层负载均衡,把 TLS 终止放在独立节点,业务容器只走内网 HTTP。但那是后话,先把现有这套稳定跑着,证书续期监控做好,日常就不太需要操心了。
最后分享一个经验:无论规模多大,部署文档和变更记录一定要留。我当时在这套流程里踩过的每个坑,都记录在项目维护文档里,之后每次给内部演示环境部署,照着文档走一遍,十分钟就能把全套服务拉起来。这份文档现在已经成为团队内部的标准参考。