[{"data":1,"prerenderedAt":1415},["ShallowReactive",2],{"doc-page:\u002Fdocs\u002Fci-cd-guide":3},{"doc":4,"prev":1382,"next":1389,"resolvedType":8,"readingMinutes":103,"audience":1391,"checklist":1395,"related":1399},{"path":5,"title":6,"description":7,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":12,"publishedAt":12,"icon":13,"body":14},"\u002Fdocs\u002Fci-cd-guide","CI\u002FCD 实践指南","持续集成与部署概念、GitHub Actions 进阶、自动化测试与部署策略","article",null,"infra-deployment","服务器与部署","2026-02-27","i-carbon-cloud",{"type":15,"value":16,"toc":1354},"minimark",[17,21,25,29,32,51,54,62,66,70,253,256,316,319,399,403,435,439,561,610,613,617,669,673,744,748,848,852,935,938,1083,1087,1195,1198,1201,1219,1222,1226,1229,1243,1246,1249,1263,1266,1269,1283,1286,1297,1300,1320,1323,1350],[18,19,6],"h1",{"id":20},"cicd-实践指南",[22,23,24],"p",{},"这页适合作为“代码提交到自动交付”的流程总览。CI\u002FCD 的重点不是把 YAML 写长，而是保证每次改动都能被稳定构建、测试、发布和回滚。",[26,27,28],"h2",{"id":28},"先定流水线目标",[22,30,31],{},"一条健康的流水线通常至少回答清楚这些问题：",[33,34,35,39,42,45,48],"ul",{},[36,37,38],"li",{},"提交后要自动检查什么",[36,40,41],{},"哪些分支允许部署",[36,43,44],{},"失败后谁会收到通知",[36,46,47],{},"生产发布能不能回滚",[36,49,50],{},"密钥和环境变量如何隔离",[26,52,53],{"id":53},"核心概念",[33,55,56,59],{},[36,57,58],{},"CI（持续集成）：代码提交后自动构建、测试",[36,60,61],{},"CD（持续部署\u002F交付）：测试通过后自动部署到生产环境",[26,63,65],{"id":64},"github-actions-进阶","GitHub Actions 进阶",[67,68,69],"h3",{"id":69},"矩阵构建",[71,72,77],"pre",{"className":73,"code":74,"language":75,"meta":76,"style":76},"language-yaml shiki shiki-themes github-light github-dark","jobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [18, 20, 22]\n        os: [ubuntu-latest, windows-latest]\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: npm ci\n      - run: npm test\n","yaml","",[78,79,80,93,101,114,122,130,157,175,183,197,209,217,228,241],"code",{"__ignoreMap":76},[81,82,85,89],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"s9eBZ","jobs",[81,90,92],{"class":91},"sVt8B",":\n",[81,94,96,99],{"class":83,"line":95},2,[81,97,98],{"class":87},"  test",[81,100,92],{"class":91},[81,102,104,107,110],{"class":83,"line":103},3,[81,105,106],{"class":87},"    runs-on",[81,108,109],{"class":91},": ",[81,111,113],{"class":112},"sZZnC","ubuntu-latest\n",[81,115,117,120],{"class":83,"line":116},4,[81,118,119],{"class":87},"    strategy",[81,121,92],{"class":91},[81,123,125,128],{"class":83,"line":124},5,[81,126,127],{"class":87},"      matrix",[81,129,92],{"class":91},[81,131,133,136,139,143,146,149,151,154],{"class":83,"line":132},6,[81,134,135],{"class":87},"        node-version",[81,137,138],{"class":91},": [",[81,140,142],{"class":141},"sj4cs","18",[81,144,145],{"class":91},", ",[81,147,148],{"class":141},"20",[81,150,145],{"class":91},[81,152,153],{"class":141},"22",[81,155,156],{"class":91},"]\n",[81,158,160,163,165,168,170,173],{"class":83,"line":159},7,[81,161,162],{"class":87},"        os",[81,164,138],{"class":91},[81,166,167],{"class":112},"ubuntu-latest",[81,169,145],{"class":91},[81,171,172],{"class":112},"windows-latest",[81,174,156],{"class":91},[81,176,178,181],{"class":83,"line":177},8,[81,179,180],{"class":87},"    steps",[81,182,92],{"class":91},[81,184,186,189,192,194],{"class":83,"line":185},9,[81,187,188],{"class":91},"      - ",[81,190,191],{"class":87},"uses",[81,193,109],{"class":91},[81,195,196],{"class":112},"actions\u002Fcheckout@v4\n",[81,198,200,202,204,206],{"class":83,"line":199},10,[81,201,188],{"class":91},[81,203,191],{"class":87},[81,205,109],{"class":91},[81,207,208],{"class":112},"actions\u002Fsetup-node@v4\n",[81,210,212,215],{"class":83,"line":211},11,[81,213,214],{"class":87},"        with",[81,216,92],{"class":91},[81,218,220,223,225],{"class":83,"line":219},12,[81,221,222],{"class":87},"          node-version",[81,224,109],{"class":91},[81,226,227],{"class":112},"${{ matrix.node-version }}\n",[81,229,231,233,236,238],{"class":83,"line":230},13,[81,232,188],{"class":91},[81,234,235],{"class":87},"run",[81,237,109],{"class":91},[81,239,240],{"class":112},"npm ci\n",[81,242,244,246,248,250],{"class":83,"line":243},14,[81,245,188],{"class":91},[81,247,235],{"class":87},[81,249,109],{"class":91},[81,251,252],{"class":112},"npm test\n",[67,254,255],{"id":255},"缓存依赖",[71,257,259],{"className":73,"code":258,"language":75,"meta":76,"style":76},"- uses: actions\u002Fcache@v4\n  with:\n    path: ~\u002F.pnpm-store\n    key: ${{ runner.os }}-pnpm-${{ hashFiles('**\u002Fpnpm-lock.yaml') }}\n    restore-keys: |\n      ${{ runner.os }}-pnpm-\n",[78,260,261,273,280,290,300,311],{"__ignoreMap":76},[81,262,263,266,268,270],{"class":83,"line":84},[81,264,265],{"class":91},"- ",[81,267,191],{"class":87},[81,269,109],{"class":91},[81,271,272],{"class":112},"actions\u002Fcache@v4\n",[81,274,275,278],{"class":83,"line":95},[81,276,277],{"class":87},"  with",[81,279,92],{"class":91},[81,281,282,285,287],{"class":83,"line":103},[81,283,284],{"class":87},"    path",[81,286,109],{"class":91},[81,288,289],{"class":112},"~\u002F.pnpm-store\n",[81,291,292,295,297],{"class":83,"line":116},[81,293,294],{"class":87},"    key",[81,296,109],{"class":91},[81,298,299],{"class":112},"${{ runner.os }}-pnpm-${{ hashFiles('**\u002Fpnpm-lock.yaml') }}\n",[81,301,302,305,307],{"class":83,"line":124},[81,303,304],{"class":87},"    restore-keys",[81,306,109],{"class":91},[81,308,310],{"class":309},"szBVR","|\n",[81,312,313],{"class":83,"line":132},[81,314,315],{"class":112},"      ${{ runner.os }}-pnpm-\n",[67,317,318],{"id":318},"条件执行",[71,320,322],{"className":73,"code":321,"language":75,"meta":76,"style":76},"steps:\n  - name: Deploy\n    if: github.ref == 'refs\u002Fheads\u002Fmain' && github.event_name == 'push'\n    run: npm run deploy\n\n  - name: Preview\n    if: github.event_name == 'pull_request'\n    run: npm run build\n",[78,323,324,331,344,354,364,370,381,390],{"__ignoreMap":76},[81,325,326,329],{"class":83,"line":84},[81,327,328],{"class":87},"steps",[81,330,92],{"class":91},[81,332,333,336,339,341],{"class":83,"line":95},[81,334,335],{"class":91},"  - ",[81,337,338],{"class":87},"name",[81,340,109],{"class":91},[81,342,343],{"class":112},"Deploy\n",[81,345,346,349,351],{"class":83,"line":103},[81,347,348],{"class":87},"    if",[81,350,109],{"class":91},[81,352,353],{"class":112},"github.ref == 'refs\u002Fheads\u002Fmain' && github.event_name == 'push'\n",[81,355,356,359,361],{"class":83,"line":116},[81,357,358],{"class":87},"    run",[81,360,109],{"class":91},[81,362,363],{"class":112},"npm run deploy\n",[81,365,366],{"class":83,"line":124},[81,367,369],{"emptyLinePlaceholder":368},true,"\n",[81,371,372,374,376,378],{"class":83,"line":132},[81,373,335],{"class":91},[81,375,338],{"class":87},[81,377,109],{"class":91},[81,379,380],{"class":112},"Preview\n",[81,382,383,385,387],{"class":83,"line":159},[81,384,348],{"class":87},[81,386,109],{"class":91},[81,388,389],{"class":112},"github.event_name == 'pull_request'\n",[81,391,392,394,396],{"class":83,"line":177},[81,393,358],{"class":87},[81,395,109],{"class":91},[81,397,398],{"class":112},"npm run build\n",[67,400,402],{"id":401},"secrets-使用","Secrets 使用",[71,404,406],{"className":73,"code":405,"language":75,"meta":76,"style":76},"env:\n  API_KEY: ${{ secrets.API_KEY }}\n  DATABASE_URL: ${{ secrets.DATABASE_URL }}\n",[78,407,408,415,425],{"__ignoreMap":76},[81,409,410,413],{"class":83,"line":84},[81,411,412],{"class":87},"env",[81,414,92],{"class":91},[81,416,417,420,422],{"class":83,"line":95},[81,418,419],{"class":87},"  API_KEY",[81,421,109],{"class":91},[81,423,424],{"class":112},"${{ secrets.API_KEY }}\n",[81,426,427,430,432],{"class":83,"line":103},[81,428,429],{"class":87},"  DATABASE_URL",[81,431,109],{"class":91},[81,433,434],{"class":112},"${{ secrets.DATABASE_URL }}\n",[67,436,438],{"id":437},"复用-workflow","复用 Workflow",[71,440,442],{"className":73,"code":441,"language":75,"meta":76,"style":76},"# .github\u002Fworkflows\u002Freusable-build.yml\non:\n  workflow_call:\n    inputs:\n      environment:\n        required: true\n        type: string\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - run: npm ci\n      - run: npm run build -- --mode ${{ inputs.environment }}\n",[78,443,444,450,457,464,471,478,488,498,502,508,515,523,529,539,549],{"__ignoreMap":76},[81,445,446],{"class":83,"line":84},[81,447,449],{"class":448},"sJ8bj","# .github\u002Fworkflows\u002Freusable-build.yml\n",[81,451,452,455],{"class":83,"line":95},[81,453,454],{"class":141},"on",[81,456,92],{"class":91},[81,458,459,462],{"class":83,"line":103},[81,460,461],{"class":87},"  workflow_call",[81,463,92],{"class":91},[81,465,466,469],{"class":83,"line":116},[81,467,468],{"class":87},"    inputs",[81,470,92],{"class":91},[81,472,473,476],{"class":83,"line":124},[81,474,475],{"class":87},"      environment",[81,477,92],{"class":91},[81,479,480,483,485],{"class":83,"line":132},[81,481,482],{"class":87},"        required",[81,484,109],{"class":91},[81,486,487],{"class":141},"true\n",[81,489,490,493,495],{"class":83,"line":159},[81,491,492],{"class":87},"        type",[81,494,109],{"class":91},[81,496,497],{"class":112},"string\n",[81,499,500],{"class":83,"line":177},[81,501,369],{"emptyLinePlaceholder":368},[81,503,504,506],{"class":83,"line":185},[81,505,88],{"class":87},[81,507,92],{"class":91},[81,509,510,513],{"class":83,"line":199},[81,511,512],{"class":87},"  build",[81,514,92],{"class":91},[81,516,517,519,521],{"class":83,"line":211},[81,518,106],{"class":87},[81,520,109],{"class":91},[81,522,113],{"class":112},[81,524,525,527],{"class":83,"line":219},[81,526,180],{"class":87},[81,528,92],{"class":91},[81,530,531,533,535,537],{"class":83,"line":230},[81,532,188],{"class":91},[81,534,191],{"class":87},[81,536,109],{"class":91},[81,538,196],{"class":112},[81,540,541,543,545,547],{"class":83,"line":243},[81,542,188],{"class":91},[81,544,235],{"class":87},[81,546,109],{"class":91},[81,548,240],{"class":112},[81,550,552,554,556,558],{"class":83,"line":551},15,[81,553,188],{"class":91},[81,555,235],{"class":87},[81,557,109],{"class":91},[81,559,560],{"class":112},"npm run build -- --mode ${{ inputs.environment }}\n",[71,562,564],{"className":73,"code":563,"language":75,"meta":76,"style":76},"# .github\u002Fworkflows\u002Fdeploy.yml\njobs:\n  deploy:\n    uses: .\u002F.github\u002Fworkflows\u002Freusable-build.yml\n    with:\n      environment: production\n",[78,565,566,571,577,584,594,601],{"__ignoreMap":76},[81,567,568],{"class":83,"line":84},[81,569,570],{"class":448},"# .github\u002Fworkflows\u002Fdeploy.yml\n",[81,572,573,575],{"class":83,"line":95},[81,574,88],{"class":87},[81,576,92],{"class":91},[81,578,579,582],{"class":83,"line":103},[81,580,581],{"class":87},"  deploy",[81,583,92],{"class":91},[81,585,586,589,591],{"class":83,"line":116},[81,587,588],{"class":87},"    uses",[81,590,109],{"class":91},[81,592,593],{"class":112},".\u002F.github\u002Fworkflows\u002Freusable-build.yml\n",[81,595,596,599],{"class":83,"line":124},[81,597,598],{"class":87},"    with",[81,600,92],{"class":91},[81,602,603,605,607],{"class":83,"line":132},[81,604,475],{"class":87},[81,606,109],{"class":91},[81,608,609],{"class":112},"production\n",[26,611,612],{"id":612},"部署策略",[67,614,616],{"id":615},"cloudflare-pages","Cloudflare Pages",[71,618,620],{"className":73,"code":619,"language":75,"meta":76,"style":76},"- name: Deploy to Cloudflare Pages\n  uses: cloudflare\u002Fwrangler-action@v3\n  with:\n    apiToken: ${{ secrets.CF_API_TOKEN }}\n    command: pages deploy dist --project-name=myproject\n",[78,621,622,633,643,649,659],{"__ignoreMap":76},[81,623,624,626,628,630],{"class":83,"line":84},[81,625,265],{"class":91},[81,627,338],{"class":87},[81,629,109],{"class":91},[81,631,632],{"class":112},"Deploy to Cloudflare Pages\n",[81,634,635,638,640],{"class":83,"line":95},[81,636,637],{"class":87},"  uses",[81,639,109],{"class":91},[81,641,642],{"class":112},"cloudflare\u002Fwrangler-action@v3\n",[81,644,645,647],{"class":83,"line":103},[81,646,277],{"class":87},[81,648,92],{"class":91},[81,650,651,654,656],{"class":83,"line":116},[81,652,653],{"class":87},"    apiToken",[81,655,109],{"class":91},[81,657,658],{"class":112},"${{ secrets.CF_API_TOKEN }}\n",[81,660,661,664,666],{"class":83,"line":124},[81,662,663],{"class":87},"    command",[81,665,109],{"class":91},[81,667,668],{"class":112},"pages deploy dist --project-name=myproject\n",[67,670,672],{"id":671},"vercel","Vercel",[71,674,676],{"className":73,"code":675,"language":75,"meta":76,"style":76},"- name: Deploy to Vercel\n  uses: amondnet\u002Fvercel-action@v25\n  with:\n    vercel-token: ${{ secrets.VERCEL_TOKEN }}\n    vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}\n    vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}\n    vercel-args: --prod\n",[78,677,678,689,698,704,714,724,734],{"__ignoreMap":76},[81,679,680,682,684,686],{"class":83,"line":84},[81,681,265],{"class":91},[81,683,338],{"class":87},[81,685,109],{"class":91},[81,687,688],{"class":112},"Deploy to Vercel\n",[81,690,691,693,695],{"class":83,"line":95},[81,692,637],{"class":87},[81,694,109],{"class":91},[81,696,697],{"class":112},"amondnet\u002Fvercel-action@v25\n",[81,699,700,702],{"class":83,"line":103},[81,701,277],{"class":87},[81,703,92],{"class":91},[81,705,706,709,711],{"class":83,"line":116},[81,707,708],{"class":87},"    vercel-token",[81,710,109],{"class":91},[81,712,713],{"class":112},"${{ secrets.VERCEL_TOKEN }}\n",[81,715,716,719,721],{"class":83,"line":124},[81,717,718],{"class":87},"    vercel-org-id",[81,720,109],{"class":91},[81,722,723],{"class":112},"${{ secrets.VERCEL_ORG_ID }}\n",[81,725,726,729,731],{"class":83,"line":132},[81,727,728],{"class":87},"    vercel-project-id",[81,730,109],{"class":91},[81,732,733],{"class":112},"${{ secrets.VERCEL_PROJECT_ID }}\n",[81,735,736,739,741],{"class":83,"line":159},[81,737,738],{"class":87},"    vercel-args",[81,740,109],{"class":91},[81,742,743],{"class":112},"--prod\n",[67,745,747],{"id":746},"docker-镜像发布","Docker 镜像发布",[71,749,751],{"className":73,"code":750,"language":75,"meta":76,"style":76},"- name: Login to Docker Hub\n  uses: docker\u002Flogin-action@v3\n  with:\n    username: ${{ secrets.DOCKER_USERNAME }}\n    password: ${{ secrets.DOCKER_TOKEN }}\n\n- name: Build and push\n  uses: docker\u002Fbuild-push-action@v5\n  with:\n    push: true\n    tags: user\u002Fapp:latest,user\u002Fapp:${{ github.sha }}\n",[78,752,753,764,773,779,789,799,803,814,823,829,838],{"__ignoreMap":76},[81,754,755,757,759,761],{"class":83,"line":84},[81,756,265],{"class":91},[81,758,338],{"class":87},[81,760,109],{"class":91},[81,762,763],{"class":112},"Login to Docker Hub\n",[81,765,766,768,770],{"class":83,"line":95},[81,767,637],{"class":87},[81,769,109],{"class":91},[81,771,772],{"class":112},"docker\u002Flogin-action@v3\n",[81,774,775,777],{"class":83,"line":103},[81,776,277],{"class":87},[81,778,92],{"class":91},[81,780,781,784,786],{"class":83,"line":116},[81,782,783],{"class":87},"    username",[81,785,109],{"class":91},[81,787,788],{"class":112},"${{ secrets.DOCKER_USERNAME }}\n",[81,790,791,794,796],{"class":83,"line":124},[81,792,793],{"class":87},"    password",[81,795,109],{"class":91},[81,797,798],{"class":112},"${{ secrets.DOCKER_TOKEN }}\n",[81,800,801],{"class":83,"line":132},[81,802,369],{"emptyLinePlaceholder":368},[81,804,805,807,809,811],{"class":83,"line":159},[81,806,265],{"class":91},[81,808,338],{"class":87},[81,810,109],{"class":91},[81,812,813],{"class":112},"Build and push\n",[81,815,816,818,820],{"class":83,"line":177},[81,817,637],{"class":87},[81,819,109],{"class":91},[81,821,822],{"class":112},"docker\u002Fbuild-push-action@v5\n",[81,824,825,827],{"class":83,"line":185},[81,826,277],{"class":87},[81,828,92],{"class":91},[81,830,831,834,836],{"class":83,"line":199},[81,832,833],{"class":87},"    push",[81,835,109],{"class":91},[81,837,487],{"class":141},[81,839,840,843,845],{"class":83,"line":211},[81,841,842],{"class":87},"    tags",[81,844,109],{"class":91},[81,846,847],{"class":112},"user\u002Fapp:latest,user\u002Fapp:${{ github.sha }}\n",[67,849,851],{"id":850},"ssh-部署","SSH 部署",[71,853,855],{"className":73,"code":854,"language":75,"meta":76,"style":76},"- name: Deploy via SSH\n  uses: appleboy\u002Fssh-action@v1\n  with:\n    host: ${{ secrets.SERVER_HOST }}\n    username: deploy\n    key: ${{ secrets.SSH_PRIVATE_KEY }}\n    script: |\n      cd \u002Fopt\u002Fapp\n      git pull\n      docker compose up -d --build\n",[78,856,857,868,877,883,893,902,911,920,925,930],{"__ignoreMap":76},[81,858,859,861,863,865],{"class":83,"line":84},[81,860,265],{"class":91},[81,862,338],{"class":87},[81,864,109],{"class":91},[81,866,867],{"class":112},"Deploy via SSH\n",[81,869,870,872,874],{"class":83,"line":95},[81,871,637],{"class":87},[81,873,109],{"class":91},[81,875,876],{"class":112},"appleboy\u002Fssh-action@v1\n",[81,878,879,881],{"class":83,"line":103},[81,880,277],{"class":87},[81,882,92],{"class":91},[81,884,885,888,890],{"class":83,"line":116},[81,886,887],{"class":87},"    host",[81,889,109],{"class":91},[81,891,892],{"class":112},"${{ secrets.SERVER_HOST }}\n",[81,894,895,897,899],{"class":83,"line":124},[81,896,783],{"class":87},[81,898,109],{"class":91},[81,900,901],{"class":112},"deploy\n",[81,903,904,906,908],{"class":83,"line":132},[81,905,294],{"class":87},[81,907,109],{"class":91},[81,909,910],{"class":112},"${{ secrets.SSH_PRIVATE_KEY }}\n",[81,912,913,916,918],{"class":83,"line":159},[81,914,915],{"class":87},"    script",[81,917,109],{"class":91},[81,919,310],{"class":309},[81,921,922],{"class":83,"line":177},[81,923,924],{"class":112},"      cd \u002Fopt\u002Fapp\n",[81,926,927],{"class":83,"line":185},[81,928,929],{"class":112},"      git pull\n",[81,931,932],{"class":83,"line":199},[81,933,934],{"class":112},"      docker compose up -d --build\n",[26,936,937],{"id":937},"自动化测试",[71,939,941],{"className":73,"code":940,"language":75,"meta":76,"style":76},"jobs:\n  test:\n    runs-on: ubuntu-latest\n    services:\n      postgres:\n        image: postgres:16\n        env:\n          POSTGRES_PASSWORD: test\n          POSTGRES_DB: testdb\n        ports:\n          - 5432:5432\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - run: npm ci\n      - run: npm test\n        env:\n          DATABASE_URL: postgres:\u002F\u002Fpostgres:test@localhost:5432\u002Ftestdb\n",[78,942,943,949,955,963,970,977,987,994,1004,1014,1021,1029,1035,1045,1055,1065,1072],{"__ignoreMap":76},[81,944,945,947],{"class":83,"line":84},[81,946,88],{"class":87},[81,948,92],{"class":91},[81,950,951,953],{"class":83,"line":95},[81,952,98],{"class":87},[81,954,92],{"class":91},[81,956,957,959,961],{"class":83,"line":103},[81,958,106],{"class":87},[81,960,109],{"class":91},[81,962,113],{"class":112},[81,964,965,968],{"class":83,"line":116},[81,966,967],{"class":87},"    services",[81,969,92],{"class":91},[81,971,972,975],{"class":83,"line":124},[81,973,974],{"class":87},"      postgres",[81,976,92],{"class":91},[81,978,979,982,984],{"class":83,"line":132},[81,980,981],{"class":87},"        image",[81,983,109],{"class":91},[81,985,986],{"class":112},"postgres:16\n",[81,988,989,992],{"class":83,"line":159},[81,990,991],{"class":87},"        env",[81,993,92],{"class":91},[81,995,996,999,1001],{"class":83,"line":177},[81,997,998],{"class":87},"          POSTGRES_PASSWORD",[81,1000,109],{"class":91},[81,1002,1003],{"class":112},"test\n",[81,1005,1006,1009,1011],{"class":83,"line":185},[81,1007,1008],{"class":87},"          POSTGRES_DB",[81,1010,109],{"class":91},[81,1012,1013],{"class":112},"testdb\n",[81,1015,1016,1019],{"class":83,"line":199},[81,1017,1018],{"class":87},"        ports",[81,1020,92],{"class":91},[81,1022,1023,1026],{"class":83,"line":211},[81,1024,1025],{"class":91},"          - ",[81,1027,1028],{"class":112},"5432:5432\n",[81,1030,1031,1033],{"class":83,"line":219},[81,1032,180],{"class":87},[81,1034,92],{"class":91},[81,1036,1037,1039,1041,1043],{"class":83,"line":230},[81,1038,188],{"class":91},[81,1040,191],{"class":87},[81,1042,109],{"class":91},[81,1044,196],{"class":112},[81,1046,1047,1049,1051,1053],{"class":83,"line":243},[81,1048,188],{"class":91},[81,1050,235],{"class":87},[81,1052,109],{"class":91},[81,1054,240],{"class":112},[81,1056,1057,1059,1061,1063],{"class":83,"line":551},[81,1058,188],{"class":91},[81,1060,235],{"class":87},[81,1062,109],{"class":91},[81,1064,252],{"class":112},[81,1066,1068,1070],{"class":83,"line":1067},16,[81,1069,991],{"class":87},[81,1071,92],{"class":91},[81,1073,1075,1078,1080],{"class":83,"line":1074},17,[81,1076,1077],{"class":87},"          DATABASE_URL",[81,1079,109],{"class":91},[81,1081,1082],{"class":112},"postgres:\u002F\u002Fpostgres:test@localhost:5432\u002Ftestdb\n",[26,1084,1086],{"id":1085},"release-自动化","Release 自动化",[71,1088,1090],{"className":73,"code":1089,"language":75,"meta":76,"style":76},"on:\n  push:\n    tags:\n      - \"v*\"\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - name: Create Release\n        uses: softprops\u002Faction-gh-release@v2\n        with:\n          generate_release_notes: true\n",[78,1091,1092,1098,1105,1111,1118,1122,1128,1135,1143,1149,1159,1170,1180,1186],{"__ignoreMap":76},[81,1093,1094,1096],{"class":83,"line":84},[81,1095,454],{"class":141},[81,1097,92],{"class":91},[81,1099,1100,1103],{"class":83,"line":95},[81,1101,1102],{"class":87},"  push",[81,1104,92],{"class":91},[81,1106,1107,1109],{"class":83,"line":103},[81,1108,842],{"class":87},[81,1110,92],{"class":91},[81,1112,1113,1115],{"class":83,"line":116},[81,1114,188],{"class":91},[81,1116,1117],{"class":112},"\"v*\"\n",[81,1119,1120],{"class":83,"line":124},[81,1121,369],{"emptyLinePlaceholder":368},[81,1123,1124,1126],{"class":83,"line":132},[81,1125,88],{"class":87},[81,1127,92],{"class":91},[81,1129,1130,1133],{"class":83,"line":159},[81,1131,1132],{"class":87},"  release",[81,1134,92],{"class":91},[81,1136,1137,1139,1141],{"class":83,"line":177},[81,1138,106],{"class":87},[81,1140,109],{"class":91},[81,1142,113],{"class":112},[81,1144,1145,1147],{"class":83,"line":185},[81,1146,180],{"class":87},[81,1148,92],{"class":91},[81,1150,1151,1153,1155,1157],{"class":83,"line":199},[81,1152,188],{"class":91},[81,1154,191],{"class":87},[81,1156,109],{"class":91},[81,1158,196],{"class":112},[81,1160,1161,1163,1165,1167],{"class":83,"line":211},[81,1162,188],{"class":91},[81,1164,338],{"class":87},[81,1166,109],{"class":91},[81,1168,1169],{"class":112},"Create Release\n",[81,1171,1172,1175,1177],{"class":83,"line":219},[81,1173,1174],{"class":87},"        uses",[81,1176,109],{"class":91},[81,1178,1179],{"class":112},"softprops\u002Faction-gh-release@v2\n",[81,1181,1182,1184],{"class":83,"line":230},[81,1183,214],{"class":87},[81,1185,92],{"class":91},[81,1187,1188,1191,1193],{"class":83,"line":243},[81,1189,1190],{"class":87},"          generate_release_notes",[81,1192,109],{"class":91},[81,1194,487],{"class":141},[26,1196,1197],{"id":1197},"推荐落地顺序",[22,1199,1200],{},"建议按这个顺序建设：",[1202,1203,1204,1207,1210,1213,1216],"ol",{},[36,1205,1206],{},"先加最小构建检查",[36,1208,1209],{},"再加测试和缓存",[36,1211,1212],{},"再加预览部署",[36,1214,1215],{},"再加生产部署和回滚",[36,1217,1218],{},"最后再补 Release、通知和审批",[26,1220,1221],{"id":1221},"常见问题",[67,1223,1225],{"id":1224},"本地能跑ci-总失败","本地能跑，CI 总失败",[22,1227,1228],{},"高频原因通常包括：",[33,1230,1231,1234,1237,1240],{},[36,1232,1233],{},"Node \u002F Python \u002F Bun 版本不一致",[36,1235,1236],{},"锁文件没同步",[36,1238,1239],{},"环境变量和 Secret 缺失",[36,1241,1242],{},"CI 环境更严格，暴露了隐藏问题",[67,1244,1245],{"id":1245},"流水线越来越慢",[22,1247,1248],{},"优先优化：",[33,1250,1251,1254,1257,1260],{},[36,1252,1253],{},"依赖缓存",[36,1255,1256],{},"Job 拆分",[36,1258,1259],{},"只在必要路径触发",[36,1261,1262],{},"把重型步骤放到更后面",[67,1264,1265],{"id":1265},"自动部署让人不安心",[22,1267,1268],{},"这时更适合先做：",[33,1270,1271,1274,1277,1280],{},[36,1272,1273],{},"Preview 部署",[36,1275,1276],{},"主分支受保护",[36,1278,1279],{},"生产环境审批",[36,1281,1282],{},"明确回滚脚本",[26,1284,1285],{"id":1285},"风险提醒",[33,1287,1288,1291,1294],{},[36,1289,1290],{},"密钥不要写死在 workflow",[36,1292,1293],{},"发布动作必须和分支策略绑定",[36,1295,1296],{},"先有回滚思路，再谈全自动上线",[26,1298,1299],{"id":1299},"延伸阅读",[33,1301,1302,1308,1314],{},[36,1303,1304],{},[1305,1306,65],"a",{"href":1307},"\u002Fdocs\u002Fgithub-actions",[36,1309,1310],{},[1305,1311,1313],{"href":1312},"\u002Fdocs\u002Fcloudflare-pages-deploy","Cloudflare Pages 部署指南",[36,1315,1316],{},[1305,1317,1319],{"href":1318},"\u002Fdocs\u002Fcloudflare-workers","Cloudflare Workers 与 Pages",[26,1321,1322],{"id":1322},"参考链接",[33,1324,1325,1334,1342],{},[36,1326,1327,1333],{},[1305,1328,1332],{"href":1329,"rel":1330},"https:\u002F\u002Fdocs.github.com\u002Fzh\u002Factions",[1331],"nofollow","GitHub Actions 文档"," — 官方文档",[36,1335,1336,1341],{},[1305,1337,1340],{"href":1338,"rel":1339},"https:\u002F\u002Fgithub.com\u002Fsdras\u002Fawesome-actions",[1331],"Awesome Actions"," — 精选 Actions",[36,1343,1344,1349],{},[1305,1345,1348],{"href":1346,"rel":1347},"https:\u002F\u002Fgithub.com\u002Fnektos\u002Fact",[1331],"act"," — 本地运行 GitHub Actions",[1351,1352,1353],"style",{},"html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":76,"searchDepth":95,"depth":95,"links":1355},[1356,1357,1358,1365,1371,1372,1373,1374,1379,1380,1381],{"id":28,"depth":95,"text":28},{"id":53,"depth":95,"text":53},{"id":64,"depth":95,"text":65,"children":1359},[1360,1361,1362,1363,1364],{"id":69,"depth":103,"text":69},{"id":255,"depth":103,"text":255},{"id":318,"depth":103,"text":318},{"id":401,"depth":103,"text":402},{"id":437,"depth":103,"text":438},{"id":612,"depth":95,"text":612,"children":1366},[1367,1368,1369,1370],{"id":615,"depth":103,"text":616},{"id":671,"depth":103,"text":672},{"id":746,"depth":103,"text":747},{"id":850,"depth":103,"text":851},{"id":937,"depth":95,"text":937},{"id":1085,"depth":95,"text":1086},{"id":1197,"depth":95,"text":1197},{"id":1221,"depth":95,"text":1221,"children":1375},[1376,1377,1378],{"id":1224,"depth":103,"text":1225},{"id":1245,"depth":103,"text":1245},{"id":1265,"depth":103,"text":1265},{"id":1285,"depth":95,"text":1285},{"id":1299,"depth":95,"text":1299},{"id":1322,"depth":95,"text":1322},{"path":1383,"title":1384,"description":1385,"docType":8,"resourceKind":9,"categoryId":1386,"categoryLabel":1387,"updatedAt":12,"publishedAt":12,"icon":1388},"\u002Fdocs\u002Fchrome-devtools","Chrome DevTools 技巧","控制台技巧、网络调试、性能分析与实用快捷键","dev-environment","开发环境","i-carbon-code",{"path":1318,"title":1319,"description":1390,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":12,"publishedAt":12,"icon":13},"Wrangler CLI、Workers 开发、Pages 部署、KV\u002FD1\u002FR2 存储服务使用指南",[1392,1393,1394],"希望把零散经验整理成长期可复用工作流的人","想先建立认知，再决定是否深入实践的人","希望阅读时顺手建立自己的操作清单或收藏体系的人",[1396,1397,1398],"先浏览标题、摘要和目录，带着问题阅读会更高效","顺手记录真正对你有用的命令、链接和注意事项，避免重复搜索","如果页面里提到相关文档，尽量一起打开对照，效果通常更完整",[1400,1403,1407,1411],{"path":1312,"title":1313,"description":1401,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":1402,"publishedAt":1402,"icon":13},"Cloudflare Pages 项目部署、自定义域名、环境变量、重定向与 Functions","2026-02-28",{"path":1404,"title":1405,"description":1406,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":1402,"publishedAt":1402,"icon":13},"\u002Fdocs\u002Fs3-storage","S3 对象存储","S3 兼容存储使用、Cloudflare R2、MinIO 自建、rclone 同步与 SDK 集成",{"path":1408,"title":1409,"description":1410,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":12,"publishedAt":12,"icon":13},"\u002Fdocs\u002Fmonitoring-logging","监控与日志","服务器监控工具、日志管理、Uptime 监控与告警配置",{"path":1412,"title":1413,"description":1414,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":12,"publishedAt":12,"icon":13},"\u002Fdocs\u002Fcontainer-orchestration","容器编排入门","Docker Swarm 与 Kubernetes 基础概念、常用命令与本地开发环境",1776215712443]