[{"data":1,"prerenderedAt":1597},["ShallowReactive",2],{"page-/post/nest/docker-compose-deploy-nest":3,"surrounding-page":1588},{"id":4,"title":5,"author":6,"body":7,"date":1576,"description":72,"extension":1577,"group":6,"lastmod":1578,"meta":1579,"navigation":959,"path":1580,"rawbody":1581,"seo":1582,"showTitle":6,"stem":1583,"tags":1584,"versions":6,"__hash__":1587},"content/post/Nest/docker-compose-deploy-nest.md","使用 Docker Compose 部署 Nest 应用",null,{"type":8,"value":9,"toc":1565},"minimark",[10,14,26,29,32,36,39,47,50,53,56,63,66,458,461,464,678,681,684,687,1155,1163,1171,1178,1181,1186,1192,1195,1201,1204,1209,1223,1241,1244,1250,1253,1256,1259,1265,1272,1275,1278,1283,1286,1292,1295,1301,1312,1319,1324,1330,1333,1344,1348,1351,1357,1368,1374,1377,1380,1386,1410,1416,1419,1422,1425,1434,1437,1440,1474,1478,1481,1487,1549,1555,1561],[11,12,13],"h2",{"id":13},"引言",[15,16,17,18,25],"p",{},"最近把像素厂（",[19,20,24],"a",{"href":21,"rel":22},"https://mp.weixin.qq.com/s/_XUUL1HR60Zfu3xjKFeZ_w",[23],"nofollow","PixeledPicPro","）的前端又翻新了一遍，也把 Nest应用部署到了云服务器，下面分享一下使用 docker-compose 的部署流程，保证你可以按步骤完成 Nest 项目（小水管服务器的）部署工作。",[11,27,28],{"id":28},"配置文件",[15,30,31],{},"本地和服务器是两个环境，所以难免会涉及配置文件问题，这里一并讲了。然后按本地模拟部署和服务器两部分演示",[33,34,35],"h3",{"id":35},"本地部署",[15,37,38],{},"首先保证你的 Nest 服务在本地 dev 环境下是正常可用的。有些同学不要一上来就拿不知道哪年的项目开始练手，结果项目本身就有问题。",[15,40,41,42,46],{},"因为本地和线上是两个不同环境，mysql、redis等很多需要区分环境的属性值都需要统一配置。Nest提供了",[43,44,45],"code",{},"@nest/config","这个包，可以用来读取环境变量文件，并注入一个 service 到所有 module 里使用。",[15,48,49],{},"环境变量文件的类型有很多，可以是.env 文件，也可以是.yaml，也可以是.js, .ts。这里我使用的是.env，下面就以此为例。",[15,51,52],{},".env 文件放在根目录下，如果你是有多个微服务，这个 .env文件默认读取的也是根目录。",[15,54,55],{},"在目录中位置如下：",[15,57,58],{},[59,60],"img",{"alt":61,"src":62},"1.00","https://img.zzao.club/article/202411191445665.png",[15,64,65],{},"在app.module.ts 中配置 ConfigModule，同样的，在这个文件中把 TypeOrmModule 的配置改成 env里的配置项",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-typescript shiki shiki-themes github-light","@Module({\n    imports: [\n        ConfigModule.forRoot({\n          isGlobal: true,\n          envFilePath:\n            process.env.NODE_ENVIRONMENT === 'production'\n              ? path.join(process.cwd(), '.prod.env')\n              : path.join(process.cwd(), '.dev.env'),\n        }),\n        TypeOrmModule.forRootAsync({\n          imports: [ConfigModule],\n          useFactory(configService: ConfigService) {\n            return {\n              type: 'mysql',\n              host: configService.get('mysql_server_host'),\n              port: configService.get('mysql_server_port'),\n              username: configService.get('mysql_server_username'),\n              password: configService.get('mysql_server_password'),\n              database: configService.get('mysql_server_database'),\n              # 可以同步表结构，先开着\n              synchronize: true,\n              logging: true,\n              entities: [\n                你的 entities\n              ],\n              poolSize: 10,\n              connectorPackage: 'mysql2',\n              extra: {\n                authPlugin: 'sha256_password',\n              },\n            };\n          },\n          inject: [ConfigService],\n        }),\n    ]\n})\n \n","typescript","",[43,74,75,91,97,108,121,127,144,171,193,199,210,216,238,247,258,274,289,304,319,334,340,350,360,366,372,378,389,400,406,417,423,429,435,441,446,452],{"__ignoreMap":72},[76,77,80,84,88],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"sgsFI","@",[76,85,87],{"class":86},"s7eDp","Module",[76,89,90],{"class":82},"({\n",[76,92,94],{"class":78,"line":93},2,[76,95,96],{"class":82},"    imports: [\n",[76,98,100,103,106],{"class":78,"line":99},3,[76,101,102],{"class":82},"        ConfigModule.",[76,104,105],{"class":86},"forRoot",[76,107,90],{"class":82},[76,109,111,114,118],{"class":78,"line":110},4,[76,112,113],{"class":82},"          isGlobal: ",[76,115,117],{"class":116},"sYu0t","true",[76,119,120],{"class":82},",\n",[76,122,124],{"class":78,"line":123},5,[76,125,126],{"class":82},"          envFilePath:\n",[76,128,130,133,136,140],{"class":78,"line":129},6,[76,131,132],{"class":82},"            process.env.",[76,134,135],{"class":116},"NODE_ENVIRONMENT",[76,137,139],{"class":138},"sD7c4"," ===",[76,141,143],{"class":142},"sYBdl"," 'production'\n",[76,145,147,150,153,156,159,162,165,168],{"class":78,"line":146},7,[76,148,149],{"class":138},"              ?",[76,151,152],{"class":82}," path.",[76,154,155],{"class":86},"join",[76,157,158],{"class":82},"(process.",[76,160,161],{"class":86},"cwd",[76,163,164],{"class":82},"(), ",[76,166,167],{"class":142},"'.prod.env'",[76,169,170],{"class":82},")\n",[76,172,174,177,179,181,183,185,187,190],{"class":78,"line":173},8,[76,175,176],{"class":138},"              :",[76,178,152],{"class":82},[76,180,155],{"class":86},[76,182,158],{"class":82},[76,184,161],{"class":86},[76,186,164],{"class":82},[76,188,189],{"class":142},"'.dev.env'",[76,191,192],{"class":82},"),\n",[76,194,196],{"class":78,"line":195},9,[76,197,198],{"class":82},"        }),\n",[76,200,202,205,208],{"class":78,"line":201},10,[76,203,204],{"class":82},"        TypeOrmModule.",[76,206,207],{"class":86},"forRootAsync",[76,209,90],{"class":82},[76,211,213],{"class":78,"line":212},11,[76,214,215],{"class":82},"          imports: [ConfigModule],\n",[76,217,219,222,225,229,232,235],{"class":78,"line":218},12,[76,220,221],{"class":86},"          useFactory",[76,223,224],{"class":82},"(",[76,226,228],{"class":227},"sqxcx","configService",[76,230,231],{"class":138},":",[76,233,234],{"class":86}," ConfigService",[76,236,237],{"class":82},") {\n",[76,239,241,244],{"class":78,"line":240},13,[76,242,243],{"class":138},"            return",[76,245,246],{"class":82}," {\n",[76,248,250,253,256],{"class":78,"line":249},14,[76,251,252],{"class":82},"              type: ",[76,254,255],{"class":142},"'mysql'",[76,257,120],{"class":82},[76,259,261,264,267,269,272],{"class":78,"line":260},15,[76,262,263],{"class":82},"              host: configService.",[76,265,266],{"class":86},"get",[76,268,224],{"class":82},[76,270,271],{"class":142},"'mysql_server_host'",[76,273,192],{"class":82},[76,275,277,280,282,284,287],{"class":78,"line":276},16,[76,278,279],{"class":82},"              port: configService.",[76,281,266],{"class":86},[76,283,224],{"class":82},[76,285,286],{"class":142},"'mysql_server_port'",[76,288,192],{"class":82},[76,290,292,295,297,299,302],{"class":78,"line":291},17,[76,293,294],{"class":82},"              username: configService.",[76,296,266],{"class":86},[76,298,224],{"class":82},[76,300,301],{"class":142},"'mysql_server_username'",[76,303,192],{"class":82},[76,305,307,310,312,314,317],{"class":78,"line":306},18,[76,308,309],{"class":82},"              password: configService.",[76,311,266],{"class":86},[76,313,224],{"class":82},[76,315,316],{"class":142},"'mysql_server_password'",[76,318,192],{"class":82},[76,320,322,325,327,329,332],{"class":78,"line":321},19,[76,323,324],{"class":82},"              database: configService.",[76,326,266],{"class":86},[76,328,224],{"class":82},[76,330,331],{"class":142},"'mysql_server_database'",[76,333,192],{"class":82},[76,335,337],{"class":78,"line":336},20,[76,338,339],{"class":82},"              # 可以同步表结构，先开着\n",[76,341,343,346,348],{"class":78,"line":342},21,[76,344,345],{"class":82},"              synchronize: ",[76,347,117],{"class":116},[76,349,120],{"class":82},[76,351,353,356,358],{"class":78,"line":352},22,[76,354,355],{"class":82},"              logging: ",[76,357,117],{"class":116},[76,359,120],{"class":82},[76,361,363],{"class":78,"line":362},23,[76,364,365],{"class":82},"              entities: [\n",[76,367,369],{"class":78,"line":368},24,[76,370,371],{"class":82},"                你的 entities\n",[76,373,375],{"class":78,"line":374},25,[76,376,377],{"class":82},"              ],\n",[76,379,381,384,387],{"class":78,"line":380},26,[76,382,383],{"class":82},"              poolSize: ",[76,385,386],{"class":116},"10",[76,388,120],{"class":82},[76,390,392,395,398],{"class":78,"line":391},27,[76,393,394],{"class":82},"              connectorPackage: ",[76,396,397],{"class":142},"'mysql2'",[76,399,120],{"class":82},[76,401,403],{"class":78,"line":402},28,[76,404,405],{"class":82},"              extra: {\n",[76,407,409,412,415],{"class":78,"line":408},29,[76,410,411],{"class":82},"                authPlugin: ",[76,413,414],{"class":142},"'sha256_password'",[76,416,120],{"class":82},[76,418,420],{"class":78,"line":419},30,[76,421,422],{"class":82},"              },\n",[76,424,426],{"class":78,"line":425},31,[76,427,428],{"class":82},"            };\n",[76,430,432],{"class":78,"line":431},32,[76,433,434],{"class":82},"          },\n",[76,436,438],{"class":78,"line":437},33,[76,439,440],{"class":82},"          inject: [ConfigService],\n",[76,442,444],{"class":78,"line":443},34,[76,445,198],{"class":82},[76,447,449],{"class":78,"line":448},35,[76,450,451],{"class":82},"    ]\n",[76,453,455],{"class":78,"line":454},36,[76,456,457],{"class":82},"})\n",[15,459,460],{},"redis配置同理，也把之前写死的属性值改成 env 文件里的配置项",[15,462,463],{},"redis.module.ts",[67,465,467],{"className":69,"code":466,"language":71,"meta":72,"style":72},"@Module({\n  controllers: [RedisController],\n  providers: [\n    RedisService,\n    {\n      provide: 'REDIS_CLIENT',\n      async useFactory(configService: ConfigService) {\n        console.log(`redis 打印`, configService.get('redis_server_port'));\n        const client = createClient({\n          socket: {\n            host: configService.get('redis_server_host'),\n            port: configService.get('redis_server_port'),\n          },\n          database: configService.get('redis_server_db'),\n        });\n        await client.connect();\n        return client;\n      },\n      inject: [ConfigService],\n    },\n  ],\n  imports: [],\n  exports: [RedisService],\n})\n\n",[43,468,469,477,482,487,492,497,507,525,551,567,572,586,599,603,617,622,636,644,649,654,659,664,669,674],{"__ignoreMap":72},[76,470,471,473,475],{"class":78,"line":79},[76,472,83],{"class":82},[76,474,87],{"class":86},[76,476,90],{"class":82},[76,478,479],{"class":78,"line":93},[76,480,481],{"class":82},"  controllers: [RedisController],\n",[76,483,484],{"class":78,"line":99},[76,485,486],{"class":82},"  providers: [\n",[76,488,489],{"class":78,"line":110},[76,490,491],{"class":82},"    RedisService,\n",[76,493,494],{"class":78,"line":123},[76,495,496],{"class":82},"    {\n",[76,498,499,502,505],{"class":78,"line":129},[76,500,501],{"class":82},"      provide: ",[76,503,504],{"class":142},"'REDIS_CLIENT'",[76,506,120],{"class":82},[76,508,509,512,515,517,519,521,523],{"class":78,"line":146},[76,510,511],{"class":138},"      async",[76,513,514],{"class":86}," useFactory",[76,516,224],{"class":82},[76,518,228],{"class":227},[76,520,231],{"class":138},[76,522,234],{"class":86},[76,524,237],{"class":82},[76,526,527,530,533,535,538,541,543,545,548],{"class":78,"line":173},[76,528,529],{"class":82},"        console.",[76,531,532],{"class":86},"log",[76,534,224],{"class":82},[76,536,537],{"class":142},"`redis 打印`",[76,539,540],{"class":82},", configService.",[76,542,266],{"class":86},[76,544,224],{"class":82},[76,546,547],{"class":142},"'redis_server_port'",[76,549,550],{"class":82},"));\n",[76,552,553,556,559,562,565],{"class":78,"line":195},[76,554,555],{"class":138},"        const",[76,557,558],{"class":116}," client",[76,560,561],{"class":138}," =",[76,563,564],{"class":86}," createClient",[76,566,90],{"class":82},[76,568,569],{"class":78,"line":201},[76,570,571],{"class":82},"          socket: {\n",[76,573,574,577,579,581,584],{"class":78,"line":212},[76,575,576],{"class":82},"            host: configService.",[76,578,266],{"class":86},[76,580,224],{"class":82},[76,582,583],{"class":142},"'redis_server_host'",[76,585,192],{"class":82},[76,587,588,591,593,595,597],{"class":78,"line":218},[76,589,590],{"class":82},"            port: configService.",[76,592,266],{"class":86},[76,594,224],{"class":82},[76,596,547],{"class":142},[76,598,192],{"class":82},[76,600,601],{"class":78,"line":240},[76,602,434],{"class":82},[76,604,605,608,610,612,615],{"class":78,"line":249},[76,606,607],{"class":82},"          database: configService.",[76,609,266],{"class":86},[76,611,224],{"class":82},[76,613,614],{"class":142},"'redis_server_db'",[76,616,192],{"class":82},[76,618,619],{"class":78,"line":260},[76,620,621],{"class":82},"        });\n",[76,623,624,627,630,633],{"class":78,"line":276},[76,625,626],{"class":138},"        await",[76,628,629],{"class":82}," client.",[76,631,632],{"class":86},"connect",[76,634,635],{"class":82},"();\n",[76,637,638,641],{"class":78,"line":291},[76,639,640],{"class":138},"        return",[76,642,643],{"class":82}," client;\n",[76,645,646],{"class":78,"line":306},[76,647,648],{"class":82},"      },\n",[76,650,651],{"class":78,"line":321},[76,652,653],{"class":82},"      inject: [ConfigService],\n",[76,655,656],{"class":78,"line":336},[76,657,658],{"class":82},"    },\n",[76,660,661],{"class":78,"line":342},[76,662,663],{"class":82},"  ],\n",[76,665,666],{"class":78,"line":352},[76,667,668],{"class":82},"  imports: [],\n",[76,670,671],{"class":78,"line":362},[76,672,673],{"class":82},"  exports: [RedisService],\n",[76,675,676],{"class":78,"line":368},[76,677,457],{"class":82},[15,679,680],{},"更改完成后，先启动一下本地服务，看看能不能正常访问，如果可以再进行下一步！这一步卡住也没事，慢慢 Google 一下，基本都是常见问题。出现问题再解决才能变成自己的东西。",[15,682,683],{},"本地开发的时候，我是用Docker Desktop单独跑了两个容器：mysql、redis。他们的 data 也挂载到了我本地目录上。而上线的时候我需要用 docker-compose 编排 nest、mysql、redis 他们三个，并且 nest 依赖于 mysql 和 redis，下面直接列一下我本地部署时的 docker-compose.yml。我直接把注释写在里面，免去上下滑动对比着看了",[15,685,686],{},"有些名称、端口我故意没有起成一样的，方便形成对比",[67,688,692],{"className":689,"code":690,"language":691,"meta":72,"style":72},"language-yaml shiki shiki-themes github-light","version: '1.0'\nservices:\n # 我的nest服务名称\n  nest-master:\n      # 指定了容器名， 这个容器名会用于容器间的通信， 比如你本地 mysql 的 host 是 localhost，那线上就用 zz_master 访问。 不声明的话就是‘nest-master’\n    container_name: 'zz_master'\n    # nest项目打包时，用的是他自己目录下的 Dockerfile\n    build:\n      context: ./\n      dockerfile: ./apps/master/Dockerfile\n    # 依赖于另外两个 service，另外两个 service 会先被构建\n    depends_on:\n      - zz-mysql-7\n      - zz-redis-7\n    # 端口映射，左边是宿主机上的端口 7008，也就是你打开浏览器http://localhost:7008 可以访问到 nest 的接口，而在容器内部他是运行在 7007 上\n    ports:\n      - '7008:7007'\n    # 失败时重启，有时候 mysql 没启动起来，nest 已经完事了，就会连不上 mysql，所以一直重启，知道 mysql 启动成功\n    # 不过你的项目如果有bug，他就会无限重启，所以要自己注意了\n    restart: on-failure\n    # 声明他们在zzstudio-server 这个网络中，可以用container_name进行访问\n    # 不声明的话，也会在同一个网络中，名称默认是 项目_default， 比如我这个项目叫 zz-nest, 默认的网络名称就是 zz-nest_default\n    networks:\n      - zzstudio-server\n  # 我的 mysql 服务的名称\n  zz-mysql-7:\n    # 我指定的容器名，当 nest服务（也就是上边的 nest-master）要访问 mysql 时，mysql的 host 就配置为 zz_mysql\n    container_name: 'zz_mysql'\n    image: mysql\n    # 端口映射，当你从外部访问 3307 时会被映射到容器内部的 3306 上。\n    # 因为这里我们三个服务在同一个网络下，所以我们prod.env 使用的应该是 3306\n    ports:\n      - '3307:3306'\n    # 挂载的本地目录\n    volumes:\n      - /我的本地目录/mysql:/var/lib/mysql\n      # 可以用于初始化时执行一些 sql，我查阅的野文里，有的用来解决数据库没有被创建，或用来表结构初始化，有需要的自行尝试\n      # - ./init.sql/:/docker-entrypoint-initdb.d/init.sql\n\n    # 相关的环境变量，密码应该是必须要设置的，忘了咋回事了\n    environment:\n      # 如果你的 mysql 是 8.x 不要指定 MYSQL_USER=root，会报错\n      # 在指定了MYSQL_DATABASE后，会自动创建这个数据库！\n      MYSQL_DATABASE: zzstudio \n      MYSQL_ROOT_PASSWORD: 123456\n    # 同样，显式的声明在一个网络下\n    networks:\n      - zzstudio-server\n  # 我的redis服务名称\n  zz-redis-7:\n    # 我指定的容器名称，当 nest 应用要连接 redis 时，redis server host 直接写这个‘zz_redis’ 即可\n    container_name: 'zz_redis'\n    image: redis\n    # 端口映射，道理和 mysql 一样。可以看下上边的描述\n    ports:\n      - '6378:6379'\n    volumes:\n      - /我的本地目录/redis:/data\n    networks:\n      - zzstudio-server\n# 声明网络，和上边所有的 server 下边的 networks 相对应\nnetworks:\n  zzstudio-server:\n    driver: bridge\n\n","yaml",[43,693,694,706,714,720,727,732,742,747,754,764,774,779,786,794,801,806,813,820,825,830,840,845,850,857,864,869,876,881,890,900,905,910,916,923,928,935,942,948,954,961,967,975,981,987,1001,1012,1018,1025,1032,1038,1046,1052,1062,1072,1078,1085,1093,1100,1108,1115,1122,1128,1136,1144],{"__ignoreMap":72},[76,695,696,700,703],{"class":78,"line":79},[76,697,699],{"class":698},"shJU0","version",[76,701,702],{"class":82},": ",[76,704,705],{"class":142},"'1.0'\n",[76,707,708,711],{"class":78,"line":93},[76,709,710],{"class":698},"services",[76,712,713],{"class":82},":\n",[76,715,716],{"class":78,"line":99},[76,717,719],{"class":718},"sAwPA"," # 我的nest服务名称\n",[76,721,722,725],{"class":78,"line":110},[76,723,724],{"class":698},"  nest-master",[76,726,713],{"class":82},[76,728,729],{"class":78,"line":123},[76,730,731],{"class":718},"      # 指定了容器名， 这个容器名会用于容器间的通信， 比如你本地 mysql 的 host 是 localhost，那线上就用 zz_master 访问。 不声明的话就是‘nest-master’\n",[76,733,734,737,739],{"class":78,"line":129},[76,735,736],{"class":698},"    container_name",[76,738,702],{"class":82},[76,740,741],{"class":142},"'zz_master'\n",[76,743,744],{"class":78,"line":146},[76,745,746],{"class":718},"    # nest项目打包时，用的是他自己目录下的 Dockerfile\n",[76,748,749,752],{"class":78,"line":173},[76,750,751],{"class":698},"    build",[76,753,713],{"class":82},[76,755,756,759,761],{"class":78,"line":195},[76,757,758],{"class":698},"      context",[76,760,702],{"class":82},[76,762,763],{"class":142},"./\n",[76,765,766,769,771],{"class":78,"line":201},[76,767,768],{"class":698},"      dockerfile",[76,770,702],{"class":82},[76,772,773],{"class":142},"./apps/master/Dockerfile\n",[76,775,776],{"class":78,"line":212},[76,777,778],{"class":718},"    # 依赖于另外两个 service，另外两个 service 会先被构建\n",[76,780,781,784],{"class":78,"line":218},[76,782,783],{"class":698},"    depends_on",[76,785,713],{"class":82},[76,787,788,791],{"class":78,"line":240},[76,789,790],{"class":82},"      - ",[76,792,793],{"class":142},"zz-mysql-7\n",[76,795,796,798],{"class":78,"line":249},[76,797,790],{"class":82},[76,799,800],{"class":142},"zz-redis-7\n",[76,802,803],{"class":78,"line":260},[76,804,805],{"class":718},"    # 端口映射，左边是宿主机上的端口 7008，也就是你打开浏览器http://localhost:7008 可以访问到 nest 的接口，而在容器内部他是运行在 7007 上\n",[76,807,808,811],{"class":78,"line":276},[76,809,810],{"class":698},"    ports",[76,812,713],{"class":82},[76,814,815,817],{"class":78,"line":291},[76,816,790],{"class":82},[76,818,819],{"class":142},"'7008:7007'\n",[76,821,822],{"class":78,"line":306},[76,823,824],{"class":718},"    # 失败时重启，有时候 mysql 没启动起来，nest 已经完事了，就会连不上 mysql，所以一直重启，知道 mysql 启动成功\n",[76,826,827],{"class":78,"line":321},[76,828,829],{"class":718},"    # 不过你的项目如果有bug，他就会无限重启，所以要自己注意了\n",[76,831,832,835,837],{"class":78,"line":336},[76,833,834],{"class":698},"    restart",[76,836,702],{"class":82},[76,838,839],{"class":142},"on-failure\n",[76,841,842],{"class":78,"line":342},[76,843,844],{"class":718},"    # 声明他们在zzstudio-server 这个网络中，可以用container_name进行访问\n",[76,846,847],{"class":78,"line":352},[76,848,849],{"class":718},"    # 不声明的话，也会在同一个网络中，名称默认是 项目_default， 比如我这个项目叫 zz-nest, 默认的网络名称就是 zz-nest_default\n",[76,851,852,855],{"class":78,"line":362},[76,853,854],{"class":698},"    networks",[76,856,713],{"class":82},[76,858,859,861],{"class":78,"line":368},[76,860,790],{"class":82},[76,862,863],{"class":142},"zzstudio-server\n",[76,865,866],{"class":78,"line":374},[76,867,868],{"class":718},"  # 我的 mysql 服务的名称\n",[76,870,871,874],{"class":78,"line":380},[76,872,873],{"class":698},"  zz-mysql-7",[76,875,713],{"class":82},[76,877,878],{"class":78,"line":391},[76,879,880],{"class":718},"    # 我指定的容器名，当 nest服务（也就是上边的 nest-master）要访问 mysql 时，mysql的 host 就配置为 zz_mysql\n",[76,882,883,885,887],{"class":78,"line":402},[76,884,736],{"class":698},[76,886,702],{"class":82},[76,888,889],{"class":142},"'zz_mysql'\n",[76,891,892,895,897],{"class":78,"line":408},[76,893,894],{"class":698},"    image",[76,896,702],{"class":82},[76,898,899],{"class":142},"mysql\n",[76,901,902],{"class":78,"line":419},[76,903,904],{"class":718},"    # 端口映射，当你从外部访问 3307 时会被映射到容器内部的 3306 上。\n",[76,906,907],{"class":78,"line":425},[76,908,909],{"class":718},"    # 因为这里我们三个服务在同一个网络下，所以我们prod.env 使用的应该是 3306\n",[76,911,912,914],{"class":78,"line":431},[76,913,810],{"class":698},[76,915,713],{"class":82},[76,917,918,920],{"class":78,"line":437},[76,919,790],{"class":82},[76,921,922],{"class":142},"'3307:3306'\n",[76,924,925],{"class":78,"line":443},[76,926,927],{"class":718},"    # 挂载的本地目录\n",[76,929,930,933],{"class":78,"line":448},[76,931,932],{"class":698},"    volumes",[76,934,713],{"class":82},[76,936,937,939],{"class":78,"line":454},[76,938,790],{"class":82},[76,940,941],{"class":142},"/我的本地目录/mysql:/var/lib/mysql\n",[76,943,945],{"class":78,"line":944},37,[76,946,947],{"class":718},"      # 可以用于初始化时执行一些 sql，我查阅的野文里，有的用来解决数据库没有被创建，或用来表结构初始化，有需要的自行尝试\n",[76,949,951],{"class":78,"line":950},38,[76,952,953],{"class":718},"      # - ./init.sql/:/docker-entrypoint-initdb.d/init.sql\n",[76,955,957],{"class":78,"line":956},39,[76,958,960],{"emptyLinePlaceholder":959},true,"\n",[76,962,964],{"class":78,"line":963},40,[76,965,966],{"class":718},"    # 相关的环境变量，密码应该是必须要设置的，忘了咋回事了\n",[76,968,970,973],{"class":78,"line":969},41,[76,971,972],{"class":698},"    environment",[76,974,713],{"class":82},[76,976,978],{"class":78,"line":977},42,[76,979,980],{"class":718},"      # 如果你的 mysql 是 8.x 不要指定 MYSQL_USER=root，会报错\n",[76,982,984],{"class":78,"line":983},43,[76,985,986],{"class":718},"      # 在指定了MYSQL_DATABASE后，会自动创建这个数据库！\n",[76,988,990,993,995,998],{"class":78,"line":989},44,[76,991,992],{"class":698},"      MYSQL_DATABASE",[76,994,702],{"class":82},[76,996,997],{"class":142},"zzstudio",[76,999,1000],{"class":82}," \n",[76,1002,1004,1007,1009],{"class":78,"line":1003},45,[76,1005,1006],{"class":698},"      MYSQL_ROOT_PASSWORD",[76,1008,702],{"class":82},[76,1010,1011],{"class":116},"123456\n",[76,1013,1015],{"class":78,"line":1014},46,[76,1016,1017],{"class":718},"    # 同样，显式的声明在一个网络下\n",[76,1019,1021,1023],{"class":78,"line":1020},47,[76,1022,854],{"class":698},[76,1024,713],{"class":82},[76,1026,1028,1030],{"class":78,"line":1027},48,[76,1029,790],{"class":82},[76,1031,863],{"class":142},[76,1033,1035],{"class":78,"line":1034},49,[76,1036,1037],{"class":718},"  # 我的redis服务名称\n",[76,1039,1041,1044],{"class":78,"line":1040},50,[76,1042,1043],{"class":698},"  zz-redis-7",[76,1045,713],{"class":82},[76,1047,1049],{"class":78,"line":1048},51,[76,1050,1051],{"class":718},"    # 我指定的容器名称，当 nest 应用要连接 redis 时，redis server host 直接写这个‘zz_redis’ 即可\n",[76,1053,1055,1057,1059],{"class":78,"line":1054},52,[76,1056,736],{"class":698},[76,1058,702],{"class":82},[76,1060,1061],{"class":142},"'zz_redis'\n",[76,1063,1065,1067,1069],{"class":78,"line":1064},53,[76,1066,894],{"class":698},[76,1068,702],{"class":82},[76,1070,1071],{"class":142},"redis\n",[76,1073,1075],{"class":78,"line":1074},54,[76,1076,1077],{"class":718},"    # 端口映射，道理和 mysql 一样。可以看下上边的描述\n",[76,1079,1081,1083],{"class":78,"line":1080},55,[76,1082,810],{"class":698},[76,1084,713],{"class":82},[76,1086,1088,1090],{"class":78,"line":1087},56,[76,1089,790],{"class":82},[76,1091,1092],{"class":142},"'6378:6379'\n",[76,1094,1096,1098],{"class":78,"line":1095},57,[76,1097,932],{"class":698},[76,1099,713],{"class":82},[76,1101,1103,1105],{"class":78,"line":1102},58,[76,1104,790],{"class":82},[76,1106,1107],{"class":142},"/我的本地目录/redis:/data\n",[76,1109,1111,1113],{"class":78,"line":1110},59,[76,1112,854],{"class":698},[76,1114,713],{"class":82},[76,1116,1118,1120],{"class":78,"line":1117},60,[76,1119,790],{"class":82},[76,1121,863],{"class":142},[76,1123,1125],{"class":78,"line":1124},61,[76,1126,1127],{"class":718},"# 声明网络，和上边所有的 server 下边的 networks 相对应\n",[76,1129,1131,1134],{"class":78,"line":1130},62,[76,1132,1133],{"class":698},"networks",[76,1135,713],{"class":82},[76,1137,1139,1142],{"class":78,"line":1138},63,[76,1140,1141],{"class":698},"  zzstudio-server",[76,1143,713],{"class":82},[76,1145,1147,1150,1152],{"class":78,"line":1146},64,[76,1148,1149],{"class":698},"    driver",[76,1151,702],{"class":82},[76,1153,1154],{"class":142},"bridge\n",[15,1156,1157,1158,1162],{},"然后再对比看一下本地和线上的环境配置，我",[1159,1160,1161],"strong",{},"放在了一起演示","，可以自己体会一下",[67,1164,1169],{"className":1165,"code":1167,"language":1168},[1166],"language-text","redis_server_host=localhost #dev\nredis_server_host=zz_redis #prod 对应 docker-compose.yml 里的 redis 的 container_name\n\nredis_server_port=6379 #dev\nredis_server_port=6379 #prod  ports 配的是 6378：6379，这里用的是 6379，因为他们已经在同一个网络里\n\nredis_server_db=1\n\nmysql_server_host=localhost # dev\nmysql_server_host=zz_mysql # prod 对应 docker-compose.yml 里的 msyql 的 container_name\nmysql_server_port=3307# dev 我本地 docker单独启用 mysql 时， 使用的 ports 是 3307:3306，所以我 nest 访问 mysql是用 3307 访问，从外部访问。同理 navicat 这种软件访问也需要从 3307\nmysql_server_port=3306# prod 线上ports 配的也是 3307:3306，这里用的是 3306，因为他们已经在同一个网络里\n#mysql_server_username=用户名\n#mysql_server_password=密码\n#mysql_server_database=数据库名称\n","text",[43,1170,1167],{"__ignoreMap":72},[15,1172,1173,1174,1177],{},"nest 应用的 Dockerfile 如下，要注意的是我单独把 ",[43,1175,1176],{},"prod.env"," 复制进去了，不然不会扔进去。你也可以先自己试试，看看报错信息，思考一下是哪里出了问题，再回过头来按我这个流程排查。这样也能加深记忆。",[15,1179,1180],{},"这个 Dockfile 我也是参考了很多野文，这方面不再赘述了，能跑起来就行！",[15,1182,1183],{},[43,1184,1185],{},"要注意：master 是我的服务的文件名，要记得换成自己的",[67,1187,1190],{"className":1188,"code":1189,"language":1168},[1166],"FROM node:18.18.2-alpine3.18 as build-stage\n\nWORKDIR /app\n\nRUN npm install -g pnpm\n\nCOPY package.json .\n\nRUN pnpm install\n\nCOPY . .\n\nRUN pnpm build:master \n\nFROM node:18.18.2-alpine3.18 as production-stage \n\nCOPY --from=build-stage /app/dist/apps /app/apps\nCOPY --from=build-stage /app/.prod.env /app/.prod.env\nCOPY --from=build-stage /app/package.json /app/package.json\n\nWORKDIR /app\n\nRUN npm install -g pnpm\n\nENV NODE_ENVIRONMENT=production\n\nRUN pnpm install --production\n\nCMD [ \"node\", \"./apps/master/main.js\" ]\n\nEXPOSE 7007\n\n",[43,1191,1189],{"__ignoreMap":72},[15,1193,1194],{},"写好了Dockerfile、 docker-compose.yml 和本地 env，我们先把本地当线上跑一下，跑之前先看看自己本地的docker 运行情况，不要冲突了。",[67,1196,1199],{"className":1197,"code":1198,"language":1168},[1166],"docker-compose up\n",[43,1200,1198],{"__ignoreMap":72},[15,1202,1203],{},"跑完后，出现下图，即为成功",[15,1205,1206],{},[59,1207],{"alt":61,"src":1208},"https://img.zzao.club/article/202411191445666.png",[15,1210,1211,1212,1215,1216,1219,1222],{},"如果出现了类似以下报错，可能是",[1159,1213,1214],{},"因为没建成数据库","，也可能是你挂载的本地目录",[43,1217,1218],{},"/你的目录/mysql",[1159,1220,1221],{},"不为空，导致初始化失败","。",[15,1224,1225,1226,1229,1230,1233,1234,1233,1237,1240],{},"也可能会出现 178.x.x.x：端口号 访问不到的报错，一般是你配置文件里 ",[43,1227,1228],{},"port"," 配错了，应该配成容器的 ",[43,1231,1232],{},"network"," 的 ",[43,1235,1236],{},"bridge",[43,1238,1239],{},"Gateway"," 地址",[15,1242,1243],{},"可以往上滑一下日志，看看具体报错信息，如有不知名报错，也可以在公众号：早早集市 找到我，我再尝试复现一下。",[67,1245,1248],{"className":1246,"code":1247,"language":1168},[1166],"zz_master  | Error: getaddrinfo EAI_AGAIN zz_mysql\n",[43,1249,1247],{"__ignoreMap":72},[15,1251,1252],{},"然后我们拿浏览器测试一下 Get接口即可，能返回数据，终端里有输出日志就行啦",[15,1254,1255],{},"然后开始部署到云服务器！",[33,1257,1258],{"id":1258},"服务器部署",[15,1260,1261,1262],{},"往服务器部署前还需要改一下 ",[43,1263,1264],{},"docker-compose.yml",[15,1266,1267,1268,1271],{},"mysql、redis的 ",[43,1269,1270],{},"volumes"," 改为服务器上的地址，没有的话先新建一下",[15,1273,1274],{},"然后代码传到服务器上，可以用各种方式，如 git、ftp、jenkins等。",[15,1276,1277],{},"然后我们通过 ssh 连一下云服务器，然后进入到项目的根目录下， 运行",[67,1279,1281],{"className":1280,"code":1198,"language":1168},[1166],[43,1282,1198],{"__ignoreMap":72},[15,1284,1285],{},"如果你的服务器上只安装了 docker 没有 docker-compose，可以使用以下命令先安装一下",[67,1287,1290],{"className":1288,"code":1289,"language":1168},[1166],"sudo curl -L \"https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/local/bin/docker-compose\n\nsudo chmod +x /usr/local/bin/docker-compose\n\ndocker-compose --version\n",[43,1291,1289],{"__ignoreMap":72},[15,1293,1294],{},"在经过几次 retry之后，可以看到服务成功启动!",[67,1296,1299],{"className":1297,"code":1298,"language":1168},[1166],"zz_master  | [Nest] 1  - 12/24/2023, 12:32:59 PM   ERROR [TypeOrmModule] Unable to connect to the database. Retrying (4)...\n",[43,1300,1298],{"__ignoreMap":72},[15,1302,1303,1304,1307,1308,1311],{},"使用 ",[43,1305,1306],{},"docker-compose down"," 停止，然后使用",[43,1309,1310],{},"docker-compose up -d","，在后台启动即可",[15,1313,1314,1315,1318],{},"然后使用",[43,1316,1317],{},"docker ps"," ，查看已经在运行的容器～",[15,1320,1321,1323],{},[43,1322,1306],{}," 运行后也可以看到，启动的是三个 Container，一个 Network，Network 就是上边指定的网络",[67,1325,1328],{"className":1326,"code":1327,"language":1168},[1166],"[+] Running 4/4\n ⠿ Container zz_master             Removed                                                                                                              10.4s\n ⠿ Container zz_redis              Removed                                                                                                                 0.4s\n ⠿ Container zz_mysql              Removed                                                                                                                 1.1s\n ⠿ Network server_zzstudio-server  Removed    \n",[43,1329,1327],{"__ignoreMap":72},[15,1331,1332],{},"ok，服务已经启动，接下来我用 Ngxin 做代理，把接口代理到 nest 服务上（容器的对外端口上）",[15,1334,1335,1336,1339,1340,1343],{},"nginx 也是一个 docker 单独启动的容器，目前用来挂载一个前端应用的目录，靠",[43,1337,1338],{},"Termius","的 ",[43,1341,1342],{},"SFTP"," 部署前端，因为Jenkins 太吃内存，后面我再调研一下有没有平替的方案。运维方面也不着急优化。",[11,1345,1347],{"id":1346},"nginx-转发到-nest","Nginx 转发到 Nest",[15,1349,1350],{},"一开始我是这样配置的",[67,1352,1355],{"className":1353,"code":1354,"language":1168},[1166]," location ^~ /api/ {\n            proxy_set_header  X-Real-IP  $remote_addr;\n            proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;\n            proxy_pass http://localhost:7007/; \n        }\n",[43,1356,1354],{"__ignoreMap":72},[15,1358,1359,1360,1363,1364,1367],{},"在服务器里使用 ",[43,1361,1362],{},"curl"," 是可以调通Nest的接口的，但是用 ",[43,1365,1366],{},"Apifox"," 调不同，会报 502，查看错误日志",[67,1369,1372],{"className":1370,"code":1371,"language":1168},[1166],"2023/12/24 08:04:24 [warn] 24#24: *4 upstream server temporarily disabled while connecting to upstream, client: xxxx, server: zzstudio.cn, request: \"GET /api/user/aaa HTTP/1.1\", upstream: \"http://127.0.0.1:7007/user/aaa\", host: \"zzstudio.cn\"\n2023/12/24 08:04:25 [error] 24#24: *4 no live upstreams while connecting to upstream, client: xxx, server: zzstudio.cn, request: \"GET /api/user/aaa HTTP/1.1\", upstream: \"http://localhost/user/aaa\", host: \"zzstudio.cn\"\n",[43,1373,1371],{"__ignoreMap":72},[15,1375,1376],{},"然后把配置里的 localhost 改成自己服务器的公网 ip，发现还是不行",[15,1378,1379],{},"然后又查看了一下 nginx 容器里的 ip",[67,1381,1384],{"className":1382,"code":1383,"language":1168},[1166],"# 我的nginx容器名称叫nginx\ndocker inspect nginx\n",[43,1385,1383],{"__ignoreMap":72},[15,1387,1388,1389,1392,1393,1233,1395,1397,1398,1401,1402,1405,1406,1409],{},"拉到最后，看到 ",[43,1390,1391],{},"Networks","里，",[43,1394,1236],{},[43,1396,1239],{}," 地址，把上边 ",[43,1399,1400],{},"location"," 里的 ",[43,1403,1404],{},"proxy_pass","  改成 ",[43,1407,1408],{},"http://Gateway 地址: 端口号/","， 再去 Apifox 尝试下自己的接口",[67,1411,1414],{"className":1412,"code":1413,"language":1168},[1166]," location ^~ /api/ {\n            proxy_set_header  X-Real-IP  $remote_addr;\n            proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;\n            proxy_pass http://Gateway 地址:7007/; \n        }\n",[43,1415,1413],{"__ignoreMap":72},[15,1417,1418],{},"成功了👏",[11,1420,1421],{"id":1421},"小结",[15,1423,1424],{},"以上就是我使用 docker-compose 部署 Nest 应用的全部过程，如果对你有帮助的话，还望点个关注、点赞支持一下～",[15,1426,1427,1428,1433],{},"也欢迎关注公众号：",[19,1429,1432],{"href":1430,"rel":1431},"https://mp.weixin.qq.com/s/A8wHxE5Q2jl6Su_7QA6f-A",[23],"早早集市","，第一时间围观我继续产出其他产品和文章～",[15,1435,1436],{},"感谢你的阅读，我是枣把儿~",[11,1438,1439],{"id":1439},"相关文章推荐",[1441,1442,1443,1451,1458,1466],"ul",{},[1444,1445,1446,1447],"li",{},"产品构思： ",[19,1448,1450],{"href":1430,"rel":1449},[23],"当一个程序员突然想做一款产品",[1444,1452,1453,1454],{},"前端搭建： ",[19,1455,1457],{"href":21,"rel":1456},[23],"Vue3项目实战：像素风LOGO编辑器 Pixeled Pic Pro",[1444,1459,1460,1461],{},"前端迭代（一）：",[19,1462,1465],{"href":1463,"rel":1464},"https://mp.weixin.qq.com/s/n8E_clCeQl8Zu2nLg0lHbQ",[23],"iKun集合！Pixeled Pic Pro 前端迭代篇（一）",[1444,1467,1468,1469],{},"后端搭建：",[19,1470,1473],{"href":1471,"rel":1472},"https://mp.weixin.qq.com/s/GnYy_Ym3Z7jlVRfsjGLMUA",[23],"一个产品要有一个“好底子”：Nest项目搭建",[11,1475,1477],{"id":1476},"后记-2023年12月26日175916","后记 2023年12月26日17:59:16",[15,1479,1480],{},"nest项目通过docker-compose更新",[67,1482,1485],{"className":1483,"code":1484,"language":1168},[1166],"# 先停止\ndocker-compose down \n# 然后后台启动的时候重新build，mysql和redis自动越过了，重新打包了nest服务\ndocker-compose up --build -d\n",[43,1486,1484],{"__ignoreMap":72},[67,1488,1492],{"className":1489,"code":1490,"language":1491,"meta":72,"style":72},"language-shell shiki shiki-themes github-light","sudo tee /etc/docker/daemon.json \u003C\u003C-EOF { \"registry-mirrors\": [ \"https://1js6gccw.mirror.aliyuncs.com\", \"https://dockerproxy.com\", \"https://mirror.baidubce.com\", \"https://docker.m.daocloud.io\", \"https://docker.nju.edu.cn\", \"https://docker.mirrors.sjtug.sjtu.edu.cn\" ] } EOF\n","shell",[43,1493,1494],{"__ignoreMap":72},[76,1495,1496,1499,1502,1505,1508,1511,1514,1517,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546],{"class":78,"line":79},[76,1497,1498],{"class":86},"sudo",[76,1500,1501],{"class":142}," tee",[76,1503,1504],{"class":142}," /etc/docker/daemon.json",[76,1506,1507],{"class":138}," \u003C\u003C-",[76,1509,1510],{"class":142},"EOF",[76,1512,1513],{"class":82}," { ",[76,1515,1516],{"class":86},"\"registry-mirrors\"",[76,1518,231],{"class":116},[76,1520,1521],{"class":82}," [ ",[76,1523,1524],{"class":142},"\"https://1js6gccw.mirror.aliyuncs.com\",",[76,1526,1527],{"class":142}," \"https://dockerproxy.com\",",[76,1529,1530],{"class":142}," \"https://mirror.baidubce.com\",",[76,1532,1533],{"class":142}," \"https://docker.m.daocloud.io\",",[76,1535,1536],{"class":142}," \"https://docker.nju.edu.cn\",",[76,1538,1539],{"class":142}," \"https://docker.mirrors.sjtug.sjtu.edu.cn\"",[76,1541,1542],{"class":142}," ]",[76,1544,1545],{"class":142}," }",[76,1547,1548],{"class":142}," EOF\n",[67,1550,1553],{"className":1551,"code":1552,"language":1168},[1166],"sudo mkdir -p /etc/docker \nsudo tee /etc/docker/daemon.json \u003C\u003C-EOF \n{ \"registry-mirrors\": [ \n    \"https://1js6gccw.mirror.aliyuncs.com\",\n    \"https://dockerproxy.com\", \n    \"https://mirror.baidubce.com\", \n    \"https://docker.m.daocloud.io\", \n    \"https://docker.nju.edu.cn\",\n    \"https://docker.mirrors.sjtug.sjtu.edu.cn\" \n    ] \n} \nEOF\n\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n",[43,1554,1552],{"__ignoreMap":72},[67,1556,1559],{"className":1557,"code":1558,"language":1168},[1166],"docker run -d \\\n--init \\\n--name memoz \\\n--publish 5230:5230 \\\n--volume /home/memoz/:/var/opt/memos \\\nneosmemo/memos:0.22.3\n",[43,1560,1558],{"__ignoreMap":72},[1562,1563,1564],"style",{},"html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}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 pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":72,"searchDepth":93,"depth":93,"links":1566},[1567,1568,1572,1573,1574,1575],{"id":13,"depth":93,"text":13},{"id":28,"depth":93,"text":28,"children":1569},[1570,1571],{"id":35,"depth":99,"text":35},{"id":1258,"depth":99,"text":1258},{"id":1346,"depth":93,"text":1347},{"id":1421,"depth":93,"text":1421},{"id":1439,"depth":93,"text":1439},{"id":1476,"depth":93,"text":1477},"2023-12-24T00:00:00.000Z","md","2026-02-05T05:59:55.857Z",{},"/post/nest/docker-compose-deploy-nest","---\ntitle: 使用 Docker Compose 部署 Nest 应用\ndate: 2023-12-24\nlastmod: \"2026-02-05T05:59:55.857Z\"\ntags: [\"Nest\", \"Docker\"]\n---\n\n## 引言\n\n最近把像素厂（[PixeledPicPro](https://mp.weixin.qq.com/s/_XUUL1HR60Zfu3xjKFeZ_w)）的前端又翻新了一遍，也把 Nest应用部署到了云服务器，下面分享一下使用 docker-compose 的部署流程，保证你可以按步骤完成 Nest 项目（小水管服务器的）部署工作。\n\n## 配置文件\n\n本地和服务器是两个环境，所以难免会涉及配置文件问题，这里一并讲了。然后按本地模拟部署和服务器两部分演示\n\n### 本地部署\n\n首先保证你的 Nest 服务在本地 dev 环境下是正常可用的。有些同学不要一上来就拿不知道哪年的项目开始练手，结果项目本身就有问题。\n\n因为本地和线上是两个不同环境，mysql、redis等很多需要区分环境的属性值都需要统一配置。Nest提供了`@nest/config`这个包，可以用来读取环境变量文件，并注入一个 service 到所有 module 里使用。\n\n环境变量文件的类型有很多，可以是.env 文件，也可以是.yaml，也可以是.js, .ts。这里我使用的是.env，下面就以此为例。\n\n.env 文件放在根目录下，如果你是有多个微服务，这个 .env文件默认读取的也是根目录。\n\n在目录中位置如下：\n\n![1.00](https://img.zzao.club/article/202411191445665.png)\n\n在app.module.ts 中配置 ConfigModule，同样的，在这个文件中把 TypeOrmModule 的配置改成 env里的配置项\n\n```typescript\n@Module({\n\timports: [\n\t\tConfigModule.forRoot({\n\t      isGlobal: true,\n\t      envFilePath:\n\t        process.env.NODE_ENVIRONMENT === 'production'\n\t          ? path.join(process.cwd(), '.prod.env')\n\t          : path.join(process.cwd(), '.dev.env'),\n\t    }),\n\t\tTypeOrmModule.forRootAsync({\n\t      imports: [ConfigModule],\n\t      useFactory(configService: ConfigService) {\n\t        return {\n\t          type: 'mysql',\n\t          host: configService.get('mysql_server_host'),\n\t          port: configService.get('mysql_server_port'),\n\t          username: configService.get('mysql_server_username'),\n\t          password: configService.get('mysql_server_password'),\n\t          database: configService.get('mysql_server_database'),\n\t          # 可以同步表结构，先开着\n\t          synchronize: true,\n\t          logging: true,\n\t          entities: [\n\t            你的 entities\n\t          ],\n\t          poolSize: 10,\n\t          connectorPackage: 'mysql2',\n\t          extra: {\n\t            authPlugin: 'sha256_password',\n\t          },\n\t        };\n\t      },\n\t      inject: [ConfigService],\n\t    }),\n\t]\n})\n \n```\n\nredis配置同理，也把之前写死的属性值改成 env 文件里的配置项\n\nredis.module.ts\n\n```typescript\n@Module({\n  controllers: [RedisController],\n  providers: [\n    RedisService,\n    {\n      provide: 'REDIS_CLIENT',\n      async useFactory(configService: ConfigService) {\n        console.log(`redis 打印`, configService.get('redis_server_port'));\n        const client = createClient({\n          socket: {\n            host: configService.get('redis_server_host'),\n            port: configService.get('redis_server_port'),\n          },\n          database: configService.get('redis_server_db'),\n        });\n        await client.connect();\n        return client;\n      },\n      inject: [ConfigService],\n    },\n  ],\n  imports: [],\n  exports: [RedisService],\n})\n\n```\n\n更改完成后，先启动一下本地服务，看看能不能正常访问，如果可以再进行下一步！这一步卡住也没事，慢慢 Google 一下，基本都是常见问题。出现问题再解决才能变成自己的东西。\n\n本地开发的时候，我是用Docker Desktop单独跑了两个容器：mysql、redis。他们的 data 也挂载到了我本地目录上。而上线的时候我需要用 docker-compose 编排 nest、mysql、redis 他们三个，并且 nest 依赖于 mysql 和 redis，下面直接列一下我本地部署时的 docker-compose.yml。我直接把注释写在里面，免去上下滑动对比着看了\n\n有些名称、端口我故意没有起成一样的，方便形成对比\n\n```yaml\nversion: '1.0'\nservices:\n # 我的nest服务名称\n  nest-master:\n\t  # 指定了容器名， 这个容器名会用于容器间的通信， 比如你本地 mysql 的 host 是 localhost，那线上就用 zz_master 访问。 不声明的话就是‘nest-master’\n    container_name: 'zz_master'\n    # nest项目打包时，用的是他自己目录下的 Dockerfile\n    build:\n      context: ./\n      dockerfile: ./apps/master/Dockerfile\n    # 依赖于另外两个 service，另外两个 service 会先被构建\n    depends_on:\n      - zz-mysql-7\n      - zz-redis-7\n    # 端口映射，左边是宿主机上的端口 7008，也就是你打开浏览器http://localhost:7008 可以访问到 nest 的接口，而在容器内部他是运行在 7007 上\n    ports:\n      - '7008:7007'\n    # 失败时重启，有时候 mysql 没启动起来，nest 已经完事了，就会连不上 mysql，所以一直重启，知道 mysql 启动成功\n    # 不过你的项目如果有bug，他就会无限重启，所以要自己注意了\n    restart: on-failure\n    # 声明他们在zzstudio-server 这个网络中，可以用container_name进行访问\n    # 不声明的话，也会在同一个网络中，名称默认是 项目_default， 比如我这个项目叫 zz-nest, 默认的网络名称就是 zz-nest_default\n    networks:\n      - zzstudio-server\n  # 我的 mysql 服务的名称\n  zz-mysql-7:\n    # 我指定的容器名，当 nest服务（也就是上边的 nest-master）要访问 mysql 时，mysql的 host 就配置为 zz_mysql\n    container_name: 'zz_mysql'\n    image: mysql\n    # 端口映射，当你从外部访问 3307 时会被映射到容器内部的 3306 上。\n    # 因为这里我们三个服务在同一个网络下，所以我们prod.env 使用的应该是 3306\n    ports:\n      - '3307:3306'\n    # 挂载的本地目录\n    volumes:\n      - /我的本地目录/mysql:/var/lib/mysql\n      # 可以用于初始化时执行一些 sql，我查阅的野文里，有的用来解决数据库没有被创建，或用来表结构初始化，有需要的自行尝试\n      # - ./init.sql/:/docker-entrypoint-initdb.d/init.sql\n\n    # 相关的环境变量，密码应该是必须要设置的，忘了咋回事了\n    environment:\n      # 如果你的 mysql 是 8.x 不要指定 MYSQL_USER=root，会报错\n      # 在指定了MYSQL_DATABASE后，会自动创建这个数据库！\n      MYSQL_DATABASE: zzstudio \n      MYSQL_ROOT_PASSWORD: 123456\n    # 同样，显式的声明在一个网络下\n    networks:\n      - zzstudio-server\n  # 我的redis服务名称\n  zz-redis-7:\n    # 我指定的容器名称，当 nest 应用要连接 redis 时，redis server host 直接写这个‘zz_redis’ 即可\n    container_name: 'zz_redis'\n    image: redis\n    # 端口映射，道理和 mysql 一样。可以看下上边的描述\n    ports:\n      - '6378:6379'\n    volumes:\n      - /我的本地目录/redis:/data\n    networks:\n      - zzstudio-server\n# 声明网络，和上边所有的 server 下边的 networks 相对应\nnetworks:\n  zzstudio-server:\n    driver: bridge\n\n```\n\n然后再对比看一下本地和线上的环境配置，我**放在了一起演示**，可以自己体会一下\n\n```\nredis_server_host=localhost #dev\nredis_server_host=zz_redis #prod 对应 docker-compose.yml 里的 redis 的 container_name\n\nredis_server_port=6379 #dev\nredis_server_port=6379 #prod  ports 配的是 6378：6379，这里用的是 6379，因为他们已经在同一个网络里\n\nredis_server_db=1\n\nmysql_server_host=localhost # dev\nmysql_server_host=zz_mysql # prod 对应 docker-compose.yml 里的 msyql 的 container_name\nmysql_server_port=3307# dev 我本地 docker单独启用 mysql 时， 使用的 ports 是 3307:3306，所以我 nest 访问 mysql是用 3307 访问，从外部访问。同理 navicat 这种软件访问也需要从 3307\nmysql_server_port=3306# prod 线上ports 配的也是 3307:3306，这里用的是 3306，因为他们已经在同一个网络里\n#mysql_server_username=用户名\n#mysql_server_password=密码\n#mysql_server_database=数据库名称\n```\n\nnest 应用的 Dockerfile 如下，要注意的是我单独把 `prod.env` 复制进去了，不然不会扔进去。你也可以先自己试试，看看报错信息，思考一下是哪里出了问题，再回过头来按我这个流程排查。这样也能加深记忆。\n\n这个 Dockfile 我也是参考了很多野文，这方面不再赘述了，能跑起来就行！\n\n`要注意：master 是我的服务的文件名，要记得换成自己的`\n\n```\nFROM node:18.18.2-alpine3.18 as build-stage\n\nWORKDIR /app\n\nRUN npm install -g pnpm\n\nCOPY package.json .\n\nRUN pnpm install\n\nCOPY . .\n\nRUN pnpm build:master \n\nFROM node:18.18.2-alpine3.18 as production-stage \n\nCOPY --from=build-stage /app/dist/apps /app/apps\nCOPY --from=build-stage /app/.prod.env /app/.prod.env\nCOPY --from=build-stage /app/package.json /app/package.json\n\nWORKDIR /app\n\nRUN npm install -g pnpm\n\nENV NODE_ENVIRONMENT=production\n\nRUN pnpm install --production\n\nCMD [ \"node\", \"./apps/master/main.js\" ]\n\nEXPOSE 7007\n\n```\n\n写好了Dockerfile、 docker-compose.yml 和本地 env，我们先把本地当线上跑一下，跑之前先看看自己本地的docker 运行情况，不要冲突了。\n\n```\ndocker-compose up\n```\n\n跑完后，出现下图，即为成功\n\n![1.00](https://img.zzao.club/article/202411191445666.png)\n\n如果出现了类似以下报错，可能是**因为没建成数据库**，也可能是你挂载的本地目录`/你的目录/mysql`**不为空，导致初始化失败**。\n\n也可能会出现 178.x.x.x：端口号 访问不到的报错，一般是你配置文件里 `port` 配错了，应该配成容器的 `network` 的 `bridge` 的 `Gateway` 地址\n\n可以往上滑一下日志，看看具体报错信息，如有不知名报错，也可以在公众号：早早集市 找到我，我再尝试复现一下。\n\n```\nzz_master  | Error: getaddrinfo EAI_AGAIN zz_mysql\n```\n\n然后我们拿浏览器测试一下 Get接口即可，能返回数据，终端里有输出日志就行啦\n\n然后开始部署到云服务器！\n\n### 服务器部署\n\n往服务器部署前还需要改一下 `docker-compose.yml`\n\nmysql、redis的 `volumes` 改为服务器上的地址，没有的话先新建一下\n\n然后代码传到服务器上，可以用各种方式，如 git、ftp、jenkins等。\n\n然后我们通过 ssh 连一下云服务器，然后进入到项目的根目录下， 运行\n\n```\ndocker-compose up\n```\n\n如果你的服务器上只安装了 docker 没有 docker-compose，可以使用以下命令先安装一下\n\n```\nsudo curl -L \"https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/local/bin/docker-compose\n\nsudo chmod +x /usr/local/bin/docker-compose\n\ndocker-compose --version\n```\n\n在经过几次 retry之后，可以看到服务成功启动!\n\n```\nzz_master  | [Nest] 1  - 12/24/2023, 12:32:59 PM   ERROR [TypeOrmModule] Unable to connect to the database. Retrying (4)...\n```\n\n使用 `docker-compose down` 停止，然后使用`docker-compose up -d`，在后台启动即可\n\n然后使用`docker ps` ，查看已经在运行的容器～\n\n`docker-compose down` 运行后也可以看到，启动的是三个 Container，一个 Network，Network 就是上边指定的网络\n\n```\n[+] Running 4/4\n ⠿ Container zz_master             Removed                                                                                                              10.4s\n ⠿ Container zz_redis              Removed                                                                                                                 0.4s\n ⠿ Container zz_mysql              Removed                                                                                                                 1.1s\n ⠿ Network server_zzstudio-server  Removed    \n```\n\nok，服务已经启动，接下来我用 Ngxin 做代理，把接口代理到 nest 服务上（容器的对外端口上）\n\nnginx 也是一个 docker 单独启动的容器，目前用来挂载一个前端应用的目录，靠`Termius`的 `SFTP` 部署前端，因为Jenkins 太吃内存，后面我再调研一下有没有平替的方案。运维方面也不着急优化。\n\n## Nginx 转发到 Nest\n\n一开始我是这样配置的\n\n```\n location ^~ /api/ {\n            proxy_set_header  X-Real-IP  $remote_addr;\n            proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;\n            proxy_pass http://localhost:7007/; \n        }\n```\n\n在服务器里使用 `curl` 是可以调通Nest的接口的，但是用 `Apifox` 调不同，会报 502，查看错误日志\n\n```\n2023/12/24 08:04:24 [warn] 24#24: *4 upstream server temporarily disabled while connecting to upstream, client: xxxx, server: zzstudio.cn, request: \"GET /api/user/aaa HTTP/1.1\", upstream: \"http://127.0.0.1:7007/user/aaa\", host: \"zzstudio.cn\"\n2023/12/24 08:04:25 [error] 24#24: *4 no live upstreams while connecting to upstream, client: xxx, server: zzstudio.cn, request: \"GET /api/user/aaa HTTP/1.1\", upstream: \"http://localhost/user/aaa\", host: \"zzstudio.cn\"\n```\n\n然后把配置里的 localhost 改成自己服务器的公网 ip，发现还是不行\n\n然后又查看了一下 nginx 容器里的 ip\n\n```\n# 我的nginx容器名称叫nginx\ndocker inspect nginx\n```\n\n拉到最后，看到 `Networks`里，`bridge` 的 `Gateway` 地址，把上边 `location` 里的 `proxy_pass`  改成 `http://Gateway 地址: 端口号/`， 再去 Apifox 尝试下自己的接口\n\n```\n location ^~ /api/ {\n            proxy_set_header  X-Real-IP  $remote_addr;\n            proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;\n            proxy_pass http://Gateway 地址:7007/; \n        }\n```\n\n成功了👏\n\n## 小结\n\n以上就是我使用 docker-compose 部署 Nest 应用的全部过程，如果对你有帮助的话，还望点个关注、点赞支持一下～\n\n也欢迎关注公众号：[早早集市](https://mp.weixin.qq.com/s/A8wHxE5Q2jl6Su_7QA6f-A)，第一时间围观我继续产出其他产品和文章～\n\n感谢你的阅读，我是枣把儿\\~\n\n## 相关文章推荐\n\n* 产品构思： [当一个程序员突然想做一款产品](https://mp.weixin.qq.com/s/A8wHxE5Q2jl6Su_7QA6f-A)\n\n* 前端搭建： [Vue3项目实战：像素风LOGO编辑器 Pixeled Pic Pro](https://mp.weixin.qq.com/s/_XUUL1HR60Zfu3xjKFeZ_w)\n\n* 前端迭代（一）：[iKun集合！Pixeled Pic Pro 前端迭代篇（一）](https://mp.weixin.qq.com/s/n8E_clCeQl8Zu2nLg0lHbQ)\n\n* 后端搭建：[一个产品要有一个“好底子”：Nest项目搭建](https://mp.weixin.qq.com/s/GnYy_Ym3Z7jlVRfsjGLMUA)\n\n## 后记 2023年12月26日17:59:16\n\nnest项目通过docker-compose更新\n\n```\n# 先停止\ndocker-compose down \n# 然后后台启动的时候重新build，mysql和redis自动越过了，重新打包了nest服务\ndocker-compose up --build -d\n```\n\n```shell\nsudo tee /etc/docker/daemon.json \u003C\u003C-EOF { \"registry-mirrors\": [ \"https://1js6gccw.mirror.aliyuncs.com\", \"https://dockerproxy.com\", \"https://mirror.baidubce.com\", \"https://docker.m.daocloud.io\", \"https://docker.nju.edu.cn\", \"https://docker.mirrors.sjtug.sjtu.edu.cn\" ] } EOF\n```\n\n```\nsudo mkdir -p /etc/docker \nsudo tee /etc/docker/daemon.json \u003C\u003C-EOF \n{ \"registry-mirrors\": [ \n\t\"https://1js6gccw.mirror.aliyuncs.com\",\n\t\"https://dockerproxy.com\", \n\t\"https://mirror.baidubce.com\", \n\t\"https://docker.m.daocloud.io\", \n\t\"https://docker.nju.edu.cn\",\n\t\"https://docker.mirrors.sjtug.sjtu.edu.cn\" \n\t] \n} \nEOF\n\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n```\n\n```\ndocker run -d \\\n--init \\\n--name memoz \\\n--publish 5230:5230 \\\n--volume /home/memoz/:/var/opt/memos \\\nneosmemo/memos:0.22.3\n```\n\n",{"title":5,"description":72},"post/Nest/docker-compose-deploy-nest",[1585,1586],"Nest","Docker","wRDzqXOb50tAeMwuz70kjIoVQch02NRbzDZZWzQRNQs",[1589,1593],{"title":1590,"path":1591,"stem":1592},"OpenClaw 安装入门（Windows）","/post/zzao/openclaw/openclaw-install-windows","post/zzao/openclaw/openclaw-install-windows",{"title":1594,"path":1595,"stem":1596},"假设你是AI，你的Skill应该是什么样的","/post/zzao/ai-skill-structure","post/zzao/ai-skill-structure",1779005087059]