模板与清单
复制即用的配置与清单——拿过去、替换占位符、直接上线,省去一行行从头敲的工夫。
这一篇是「复制即用」中心:把现成的配置和清单放在这里,复制过去、改掉占位符、直接发布。所有模板都尽量保持正确且精简,没有多余内容。
占位符统一用 example.com(你的域名)、/var/www/app(你的应用目录)、youruser(你的部署账号),用之前记得全局替换成自己的真实值。
工具 / Tools
下面三个交互式工具能帮你边查边做,配合本页模板一起用:
上线自查清单
上线前逐项勾选的完整检查表,确认没漏掉关键步骤。
费用估算器
估算服务器、域名、CDN 等一年大概要花多少钱。
SEO 代码生成器
一键生成 meta 标签、robots.txt、sitemap 等代码片段。
复制即用模板 / Copy-paste templates
每个模板都先用一两句说明用途,再给出可直接粘贴的代码。改完占位符就能用。
Nginx 反向代理 + HTTPS
把公网流量反向代理到本机上跑的应用(这里假设应用监听 127.0.0.1:3000),同时强制 HTTPS、加上常用安全响应头和 gzip。证书路径按 Certbot 自动签发的默认位置填写。配套阅读 接入 Cloudflare 与 运维安全。
# /etc/nginx/sites-available/example.com
# 80 端口:全部 301 跳转到 HTTPS。
# 跳转目标写死成固定的规范域名(而不是 $host),
# 避免伪造的 Host 头把它变成开放重定向。
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# 443 端口:真正提供服务
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
# Certbot 自动管理的证书路径
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# 安全响应头
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;
# gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
# 反向代理到本机应用
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}启用并重载:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxDockerfile(Node 应用)
多阶段构建:第一阶段装依赖并构建,第二阶段只拷贝运行所需文件,镜像更小。基于 node:20-alpine,以非 root 用户运行,只装生产依赖。
# ---- 构建阶段 ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- 运行阶段 ----
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# 只装生产依赖
COPY package*.json ./
RUN npm ci --omit=dev
# 拷贝构建产物
COPY --from=builder /app/dist ./dist
# 以非 root 用户运行(alpine 自带 node 用户)
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]docker-compose.yml
一个应用服务 + 一个 Postgres 服务。Postgres 用具名卷持久化数据,并配了健康检查;应用通过 depends_on 等数据库就绪后再启动。敏感配置走 env_file(即下面的 .env,不要提交进 Git)。应用端口绑定到 127.0.0.1,只让反向代理(nginx)能访问——如果写成 3000:3000 会暴露在所有网卡上,而且 Docker 自己的 iptables 规则会绕过 UFW。
services:
app:
build: .
restart: unless-stopped
ports:
# 只绑定到本机回环;由 nginx 负责 TLS 并反代到这里
- "127.0.0.1:3000:3000"
env_file:
- .env
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
restart: unless-stopped
env_file:
- .env
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:GitHub Actions 部署
推送到 main 分支后自动构建,再通过 SSH + rsync 把产物同步到你的服务器并重启服务。服务器地址、用户名、私钥,以及服务器的 known_hosts 都放在仓库的 Settings → Secrets and variables → Actions 里,不要写进文件。先在本地用 ssh-keyscan your.server.com 生成 SSH_KNOWN_HOSTS 的值并粘贴进 Secret,这样部署时会校验服务器身份,而不是盲目信任拿到的任意主机密钥。
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
- name: Install & build
run: |
npm ci
npm run build
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
# 固定服务器主机密钥(用 `ssh-keyscan your.server.com` 生成此 Secret)
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Deploy with rsync
run: |
# 同步到 dist/ 子目录(而非应用根目录),这样 --delete 不会删掉 .env,
# 路径也与下面 systemd 的 ExecStart 一致。
rsync -az --delete ./dist/ \
"${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/var/www/app/dist/"
ssh "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" \
"sudo systemctl restart myapp"不想碰服务器?用 Cloudflare Pages
如果是静态站点或全栈框架,可以省掉 SSH,直接用 Cloudflare Pages 部署。把上面的 Deploy 步骤换成:
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy ./dist --project-name=my-project详见 接入 Cloudflare。
robots.txt
放在站点根目录,告诉搜索引擎爬虫可以抓取全站,并指向你的 sitemap。生成更完整的版本可以用 SEO 代码生成器。
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xmlsshd_config 加固片段
禁掉 root 直接登录和密码登录,只允许密钥登录——这是服务器安全最重要的一步。改完务必先用密钥登录测试新会话再关旧会话,免得把自己锁在门外。更完整的加固见 运维安全。
# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
X11Forwarding no
MaxAuthTries 3
LoginGraceTime 20
# 只允许指定用户登录(可选)
AllowUsers youruser改完重载 SSH 服务:
sudo sshd -t && sudo systemctl reload ssh.env.example
把这个文件提交进 Git 当作「字段说明书」,让协作者知道需要哪些变量;真正带密钥的 .env 则绝不能提交,记得加进 .gitignore。
# 复制为 .env 并填入真实值
# 注意:.env 含密钥,务必加入 .gitignore,绝不要提交!
NODE_ENV=production
PORT=3000
# 数据库连接串
DATABASE_URL=postgres://youruser:CHANGE_ME@localhost:5432/myapp
# 会话 / JWT 密钥,用随机长字符串:openssl rand -hex 32
SESSION_SECRET=CHANGE_ME_to_a_long_random_string别把真实 .env 提交进 Git
密钥一旦进了 Git 历史,即使后续删除也等于已经公开了。务必在第一次提交前就把 .env 加进 .gitignore。
systemd service
把 Node 应用做成系统服务,让它开机自启、崩溃自动重启,并以非 root 用户运行。环境变量从 EnvironmentFile 读取(即上面的 .env)。
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node App
After=network.target
[Service]
Type=simple
User=youruser
Group=youruser
WorkingDirectory=/var/www/app
EnvironmentFile=/var/www/app/.env
ExecStart=/usr/bin/node /var/www/app/dist/server.js
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target启用并启动:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myappufw 防火墙设置
最小开放原则:只放行 SSH、HTTP、HTTPS,其余端口一律拒绝。先放行 SSH 再 enable,否则可能把自己挡在门外。完整防护策略见 运维安全。
# 默认拒绝所有入站、放行所有出站
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 放行必要端口
sudo ufw allow OpenSSH # 22
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
# 启用并查看状态
sudo ufw enable
sudo ufw status verbose接下来
模板只是起点。每个主题的完整讲解在对应的指南里:
- 服务器安全(SSH、防火墙、备份、监控)→ 运维安全
- DNS、CDN、缓存与基础防护 → 接入 Cloudflare
- 让搜索引擎找到你 → SEO 优化
本步骤检查清单
- 所有模板里的占位符(
example.com、/var/www/app、youruser)都已替换成真实值 .env已加入.gitignore,密钥没有提交进 Git- 部署用的 Secrets(SSH 私钥、API token)都配在平台里,没有写死在仓库文件中
- 改
sshd_config后,先用新会话验证能登录,再关掉旧会话 - 启用 ufw 前已先放行 SSH,确认没把自己锁在门外
完整清单见 上线自查清单。