V2EX 前天 16:01
[分享创造] cloudflare + vercel + supubase 迁移至腾讯云
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文分享了作者将其自动化播客项目从Vercel和Supabase迁移到腾讯云的实践过程。由于Vercel和Supabase的免费额度限制,作者选择了腾讯云轻量服务器,并详细介绍了如何部署Next.js应用和PostgreSQL数据库,涵盖了数据库迁移、Next.js应用的Docker配置、Nginx配置以及Cloudflare的DNS设置等关键步骤,最终实现了全程加密的流量传输。

🐳 **数据库迁移**: 作者详细介绍了如何将数据从Supabase迁移到自建的PostgreSQL数据库,包括使用pg_dump导出数据、配置数据库连接信息,以及使用psql导入数据。

📦 **Next.js 应用部署**: 提供了Next.js应用的Dockerfile配置,展示了如何构建镜像、安装依赖、配置环境变量,并最终运行Next.js应用。此外,还介绍了next.config.js的配置,用于支持standalone模式。

🌐 **Nginx 配置**: 作者给出了Nginx的配置文件,用于实现HTTPS访问,配置了SSL证书、HTTPS强制跳转、反向代理等,确保流量安全地转发到Next.js应用。

☁️ **Cloudflare DNS 配置**: 详细说明了在Cloudflare中配置DNS记录和SSL/TLS加密模式的步骤,确保域名解析指向腾讯云服务器,并启用“完全-严格”的SSL/TLS加密,保障数据传输安全。

原因

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

supubase 迁移至自建 postgres

    生成配置文件(可以用 https://pgtune.leopard.in.ua/ 生成配置文件),配置文件见下文, 然后启动容器。(注意:请务必使用云服务商的防火墙,仅对你的应用服务器 IP 开放 5432 端口,避免数据库暴露在公网)
    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:
# 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'
    迁移数据
    #!/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 应用

    nextjs 容器镜像 (Dockerfile)
    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"]
    next.config.js 配置
    module.exports = {      output: "standalone",    };
    Cloudflare 生成 Origin Certificate 并下载, 然后配置 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;        }    }}
    容器编排配置 (docker-compose.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-networknetworks:  app-network:    driver: bridge

修改 Cloudflare DNS

    登录你的 Cloudflare 账户,进入 domain.com 域名的 DNS 设置页面。添加或修改 A 记录,名称为 domain.com(或 @),内容为你部署 Next.js 应用的腾讯云服务器公网 IP 地址确保“代理状态 (Proxy status)”为“已代理 (Proxied)”,即云朵图标为橙色。导航到 "SSL/TLS" -> "概述 (Overview)" 页面,将 SSL/TLS 加密模式设置为 "Full (Strict)"(完全-严格)。这是最安全的模式,因为它能确保从浏览器到 Cloudflare 以及从 Cloudflare 到你的服务器的全程流量都经过加密验证。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Next.js PostgreSQL Cloudflare 腾讯云 Docker
相关文章