cloudflare + vercel + supubase 迁移至腾讯云

原因

之前搞了个自动化播客,详情见字节篝火播客。因为播客本身需要处理一些 Hacker news 以及 github trending 的数据,为了保存下来后续做了个配套的网站 lumifire.io。起初为了快速上线,直接用了 vercel + supubase 的免费方案, 最近因 vercel + supubase 相继超出免费额度,所以搞了两个腾讯云轻量服务器部署应用, 一台部署 nextjs,另一台部署 postgres。最终确保从浏览器到 Cloudflare 以及从 Cloudflare 到你的服务器的全程流量都经过加密验证,下边会罗列大致流程以及会用到的配置文件,非傻瓜式教程,仅供参考。

supubase 迁移至自建 postgres

  1. 生成配置文件(可以用 pgtune.leopard.in.ua/ 生成配置文件),配置文件见下文, 然后启动容器。(注意:请务必使用云服务商的防火墙,仅对你的应用服务器IP开放5432端口,避免数据库暴露在公网)
yml 复制代码
    services:
    db:
        image: postgres:15
        restart: always
        env_file:
        - .env
        ports:
        - "5432:5432"
        volumes:
        - pgdata:/var/lib/postgresql/data
        - ./postgresql.conf:/etc/postgresql/postgresql.conf
        command: postgres -c config_file=/etc/postgresql/postgresql.conf
        healthcheck:
        test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
        interval: 10s
        timeout: 5s
        retries: 5
        logging:
        driver: "json-file"
        options:
            max-size: "10m"
            max-file: "3"

    volumes:
    pgdata:
sh 复制代码
# postgresql.conf
    listen_addresses = '*'

    max_connections = 100
    shared_buffers = 512MB
    effective_cache_size = 1536MB
    maintenance_work_mem = 128MB
    checkpoint_completion_target = 0.9
    wal_buffers = 16MB
    default_statistics_target = 100
    random_page_cost = 1.1
    effective_io_concurrency = 200
    work_mem = 18724kB
    huge_pages = off
    min_wal_size = 1GB
    max_wal_size = 4GB

    log_destination = 'stderr'
    logging_collector = off
    log_min_duration_statement = 200
    log_line_prefix = '%m [%p]: [%l-1] user=%u,db=%d,client=%h '
    log_statement = 'ddl'
  1. 迁移数据
sh 复制代码
    #!/bin/bash

    # !!! 重要:请填写你的 Supabase 项目信息 !!!
    SUPABASE_PROJECT_REF="YOUR_SUPABASE_PROJECT_REF" # 在 Supabase 项目设置的 URL 中可以找到,例如 xyz.supabase.co 中的 xyz
    SUPABASE_HOST="db.${SUPABASE_PROJECT_REF}.supabase.co" 
    SUPABASE_PASSWORD="YOUR_SUPABASE_DB_PASSWORD" # 在 Supabase 项目的 Database -> Password 中找到

    # 你为新数据库设置的密码
    LOCAL_POSTGRES_PASSWORD="YOUR_NEW_SUPER_STRONG_PASSWORD"

    SCHEMAS_TO_DUMP="public"
    DUMP_FILE="data_dump.sql"

    info() {
        echo -e "\033[0;32m[INFO]\033[0m $1"
    }

    warn() {
        echo -e "\033[0;33m[WARN]\033[0m $1"
    }

    error() {
        echo -e "\033[0;31m[ERROR]\033[0m $1"
        exit 1
    }

    set -e
    set -o pipefail

    if [ "$SUPABASE_PROJECT_REF" == "YOUR_SUPABASE_PROJECT_REF" ] || [ "$SUPABASE_PASSWORD" == "YOUR_SUPABASE_DB_PASSWORD" ] || [ "$LOCAL_POSTGRES_PASSWORD" == "YOUR_NEW_SUPER_STRONG_PASSWORD" ]; then
        error "请先在脚本中填写 SUPABASE_PROJECT_REF, SUPABASE_PASSWORD, 和 LOCAL_POSTGRES_PASSWORD 变量!"
    fi

    info "检查并安装 postgresql-client (包含 pg_dump 和 psql)..."
    if ! command -v pg_dump &> /dev/null; then
        sudo apt-get update && sudo apt-get install -y postgresql-client
        info "postgresql-client 安装完成。"
    else
        info "postgresql-client 已安装。"
    fi

    info "正在从 Supabase 数据库导出数据..."
    info "主机: $SUPABASE_HOST"

    SCHEMA_ARGS=""
    for s in $SCHEMAS_TO_DUMP; do
        SCHEMA_ARGS+="--schema=$s "
    done

    export PGPASSWORD=$SUPABASE_PASSWORD
    pg_dump \
    --host="$SUPABASE_HOST" \
    --port=5432   \
    --username="postgres" \
    --dbname="postgres" \
    $SCHEMA_ARGS \
    --no-owner \
    --no-privileges \
    --format=plain \
    --file="$DUMP_FILE"

    unset PGPASSWORD

    if [ -f "$DUMP_FILE" ]; then
        info "数据成功导出到 $DUMP_FILE"
    else
        error "数据导出失败!"
    fi

    info "正在将数据导入到新的本地 PostgreSQL 实例..."
    export PGPASSWORD=$LOCAL_POSTGRES_PASSWORD
    psql \
    --host=localhost \
    --port=5432 \
    --username=postgres \
    --dbname=db \
    --file="$DUMP_FILE"
    unset PGPASSWORD
    info "数据导入完成。"

    info "进行简单验证..."
    export PGPASSWORD=$LOCAL_POSTGRES_PASSWORD
    TABLE_COUNT=$(psql --host=localhost --port=5432 --username=postgres --dbname=db --tuples-only -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';")
    unset PGPASSWORD
    info "验证完成。Public schema 中的表数量为: $(echo $TABLE_COUNT | xargs)"

部署 nextjs 应用

  1. nextjs 容器镜像 (Dockerfile)
yml 复制代码
    FROM node:20-alpine AS base

    # Install dependencies only when needed
    FROM base AS deps
    # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
    RUN apk add --no-cache libc6-compat
    WORKDIR /app

    # Install dependencies based on the preferred package manager
    COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
    # Copy prisma schema for postinstall script
    COPY prisma ./prisma
    RUN \
    if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
    elif [ -f package-lock.json ]; then npm ci; \
    elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
    else echo "Lockfile not found." && exit 1; \
    fi

    # Rebuild the source code only when needed
    FROM base AS builder
    WORKDIR /app
    COPY --from=deps /app/node_modules ./node_modules
    COPY . .

    # Next.js collects completely anonymous telemetry data about general usage.
    # Learn more here: https://nextjs.org/telemetry
    # Uncomment the following line in case you want to disable telemetry during the build.
    # ENV NEXT_TELEMETRY_DISABLED=1

    RUN \
    if [ -f yarn.lock ]; then yarn run build; \
    elif [ -f package-lock.json ]; then npm run build; \
    elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
    else echo "Lockfile not found." && exit 1; \
    fi

    # Production image, copy all the files and run next
    FROM base AS runner
    WORKDIR /app

    ENV NODE_ENV=production
    # Uncomment the following line in case you want to disable telemetry during runtime.
    # ENV NEXT_TELEMETRY_DISABLED=1

    RUN addgroup --system --gid 1001 nodejs
    RUN adduser --system --uid 1001 nextjs

    COPY --from=builder /app/public ./public

    # Automatically leverage output traces to reduce image size
    # https://nextjs.org/docs/advanced-features/output-file-tracing
    COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
    COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

    USER nextjs

    EXPOSE 3000

    ENV PORT=3000

    # server.js is created by next build from the standalone output
    # https://nextjs.org/docs/pages/api-reference/config/next-config-js/output
    ENV HOSTNAME="0.0.0.0"
    CMD ["node", "server.js"]
  1. next.config.js 配置
js 复制代码
    module.exports = {
      output: "standalone",
    };
  1. Cloudflare 生成 Origin Certificate 并下载, 然后配置 nginx 证书
nginx 复制代码
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    upstream nextjs_server {
        server nextjs-app:3000;
    }

    server {
        listen 80;
        server_name domain.com;
        # 强制跳转到 https
        if ($http_x_forwarded_proto != 'https') {
            return 301 https://$host$request_uri;
        }
        location / {
            proxy_pass http://nextjs_server;
            proxy_set_header Host $host;
        }
    }

    server {
        listen 443 ssl http2; 
        server_name domain.com;

        ssl_certificate /etc/nginx/certs/domain.com.pem;
        ssl_certificate_key /etc/nginx/certs/domain.com.key;

        ssl_protocols TLSv1.2 TLSv1.3;
        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_prefer_server_ciphers off;

        location / {
            proxy_pass http://nextjs_server;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            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_cache_bypass $http_upgrade;
        }

        location /_next/static {
            proxy_cache_valid 200 302 1y;
            proxy_pass http://nextjs_server;
        }
    }
}
  1. 容器编排配置 (docker-compose.yml)
yml 复制代码
services:
  nextjs-app:
    build:
      context: ../
      dockerfile: Dockerfile
    image: nextjs-app:latest
    container_name: nextjs-app
    restart: always
    env_file:
      - ../.env
    networks:
      - app-network

  nginx:
    image: nginx:stable-alpine
    container_name: nextjs-nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/certs:/etc/nginx/certs:ro  # Cloudflare 生成的证书
    depends_on:
      - nextjs-app
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

修改 Cloudflare DNS

  1. 登录你的 Cloudflare 账户,进入 domain.com 域名的 DNS 设置页面。
  2. 添加或修改 A 记录,名称为 domain.com(或 @),内容为你部署 Next.js 应用的腾讯云服务器公网 IP 地址
  3. 确保"代理状态 (Proxy status)"为"已代理 (Proxied)",即云朵图标为橙色。
  4. 导航到 "SSL/TLS" -> "概述 (Overview)" 页面,将 SSL/TLS 加密模式设置为 "Full (Strict)"(完全-严格)。这是最安全的模式,因为它能确保从浏览器到 Cloudflare 以及从 Cloudflare 到你的服务器的全程流量都经过加密验证。
相关推荐
不念霉运11 小时前
河南农担携手Gitee企业版:构建农业金融数字化研发新基建
金融·gitee·开源·devops·权限管理·ci/di
测试者家园1 天前
从DevOps到AIOps:智能体如何接管持续交付流程
软件测试·devops·aiops·敏捷测试·质量效能·智能化测试·测试开发和测试
不念霉运2 天前
开源生态新势能: 驱动国产 DevSecOps 与 AI 工程新进展
人工智能·开源·github·devops
不爱学英文的码字机器4 天前
持续交付的进化:从DevOps到AI驱动的IT新动能
运维·人工智能·devops
猴哥聊项目管理6 天前
什么是DevOps智能平台的核心功能?
运维·项目管理·制造·devops·软件·项目管理软件·软件分享
NineData8 天前
NineData云原生智能数据管理平台新功能发布|2025年5月版
数据库·云原生·oracle·devops·ninedata
一ge科研小菜鸡8 天前
云原生 DevOps 实践路线:构建敏捷、高效、可观测的交付体系
运维·云原生·devops
FreeBuf_9 天前
黑客利用GitHub现成工具通过DevOps API发起加密货币挖矿攻击
运维·github·devops