我正在使用NextJS和Docker创建一个网站,这样我就可以轻松地部署它.我使用npx-create-next-app对其进行初始化,并使用this Dockerfile(稍作修改)对其进行容器处理.因为我想在我的服务器上使用SSL,而不需要费力地设置代理,所以我按照this article的要求设置了定制服务器.

当我在 docker 容器外运行它时,它运行得很好,并且在HTTPS上运行时,性能达到了预期.然而,当我将其打包,并try 通过HTTPS打开网页时,我想到了SSL_ERROR_RX_RECORD_TOO_LONG,但我可以仅使用HTTP打开页面(当我在容器外运行时,我不能这样做).我用谷歌搜索了this question,从中我得出结论,当在停靠容器之外运行时,定制服务器如预期的那样通过HTTPS运行服务器,然而当我将其集装化时,它开始运行HTTP,即使代码没有改变.

我希望在本地运行或集装箱化运行时的行为是相同的.

起初,我认为这是由于httpsOptions中无效的keycert值造成的,但我找不到任何会使它们无效的东西,我也不明白这会如何导致这种奇怪的行为.我try 将Docker运行环境从node:alpine-16更改为node:latest,以查看这是否与父映像有关,但徒劳无功.

我遇到的另一个小问题是,出于某种原因,console.log似乎没有输出到容器的日志(log)中,我try 用谷歌搜索这一点,但没有找到太多与它相关的东西.这使得调试变得更加困难,因为我无法真正输出任何调试数据.在容器中运行时,我得到的唯一日志(log)是Listening on port 3000 url: http://localhost:3000,我认为它是由某个库/包输出的,因为它不在我的代码中.

以下是我的定制服务器代码,以防万一:

const https = require('https');
const fs = require('fs');
const { parse } = require('url');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const hostname = "127.0.0.1";
const port = process.env.PORT || 3000
const app = next({ dev, hostname, port })
const handle = app.getRequestHandler()

const httpsOptions = {
    key: fs.readFileSync('./cert/privkey.pem'),
    cert: fs.readFileSync('./cert/fullchain.pem')
};


app.prepare().then(() => {
    https.createServer(httpsOptions, async (req, res) => { // When running on docker this creates an HTTP server instead of HTTPS
        const parsedUrl = parse(req.url, true)
        const { pathname, query } = parsedUrl

        await handle(req, res, parsedUrl)
    }).listen(port, (err) => {
        if(err) throw err
        console.log(`Ready on https://localhost:${port}`)
    })
})

链接到可重现的示例here.

推荐答案

问题是,根据您的sample repo,您的repo根目录中的server.js文件将在映像中被覆盖,因为Dockerfile中的这一行:

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./

因此,在容器中运行的实际server.js是由yarn build命令创建的server.js,如下所示(您可以执行容器并亲自查看它):

const NextServer = require('next/dist/server/next-server').default
const http = require('http')
const path = require('path')
process.env.NODE_ENV = 'production'
process.chdir(__dirname)

// Make sure commands gracefully respect termination signals (e.g. from Docker)
// Allow the graceful termination to be manually configurable
if (!process.env.NEXT_MANUAL_SIG_HANDLE) {
  process.on('SIGTERM', () => process.exit(0))
  process.on('SIGINT', () => process.exit(0))
}

let handler

const server = http.createServer(async (req, res) => {
  try {
    await handler(req, res)
  } catch (err) {
    console.error(err);
    res.statusCode = 500
    res.end('internal server error')
  }
})
const currentPort = parseInt(process.env.PORT, 10) || 3000

server.listen(currentPort, (err) => {
  if (err) {
    console.error("Failed to start server", err)
    process.exit(1)
  }
  const nextServer = new NextServer({
    hostname: 'localhost',
    port: currentPort,
    dir: path.join(__dirname),
    dev: false,
    customServer: false,
    conf: {"env":{},"webpack":null,"webpackDevMiddleware":null,"eslint":{"ignoreDuringBuilds":false},"typescript":{"ignoreBuildErrors":false,"tsconfigPath":"tsconfig.json"},"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","configOrigin":"next.config.js","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"target":"server","poweredByHeader":true,"compress":true,"analyticsId":"","images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[16,32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":60,"formats":["image/webp"],"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","remotePatterns":[],"unoptimized":false},"devIndicators":{"buildActivity":true,"buildActivityPosition":"bottom-right"},"onDemandEntries":{"maxInactiveAge":15000,"pagesBufferLength":2},"amp":{"canonicalBase":""},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":{"locales":["en"],"defaultLocale":"en"},"productionBrowserSourceMaps":false,"optimizeFonts":true,"excludeDefaultMomentLocales":true,"serverRuntimeConfig":{},"publicRuntimeConfig":{},"reactStrictMode":true,"httpAgentOptions":{"keepAlive":true},"outputFileTracing":true,"staticPageGenerationTimeout":60,"swcMinify":true,"output":"standalone","experimental":{"middlewarePrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"legacyBrowsers":false,"newNextLinkBehavior":true,"cpus":7,"sharedPool":true,"profiling":false,"isrFlushToDisk":true,"workerThreads":false,"pageEnv":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"swcFileReading":true,"craCompat":false,"esmExternals":true,"appDir":false,"isrMemoryCacheSize":52428800,"fullySpecified":false,"outputFileTracingRoot":"","swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"enableUndici":false,"adjustFontFallbacks":false,"adjustFontFallbacksWithSizeAdjust":false,"trustHostHeader":false},"configFileName":"next.config.js"},
  })
  handler = nextServer.getRequestHandler()

  console.log(
    'Listening on port',
    currentPort,
    'url: http://localhost:' + currentPort
  )
})

如您所见,它启动的是http服务器,而不是HTTPS.这也是您自己的server.js中的console.log("lksdfjls");不会被执行的原因.

我建议让 node 保持原样,在http://localhost:3000上运行,并设置一个反向代理,该代理将传入的请求转发到只能从反向代理访问的 node 后端.当然,反向代理将处理TLS终止.Docker Compose设置会更方便,这样您也可以将反向代理容器(例如nginx)放在Compose项目中,并在Runtime-DO NOT BAKE CERTS OR ANY OTHER SECRETS INTO ANY IMAGE中将存储证书文件的Docker主机的目录映射到反向代理容器中,即使它是内部使用的图像,因为它随时可能意外泄漏.

此外,您也可以手动运行docker run个容器中的两个容器,但Compose会让事情变得更简单,它有很多功能,例如,您可以上下扩展组合服务,这样您的后端服务将不是在一个容器中运行,而是在多个容器中运行.但如果这是一个高负载和/或业务关键型的产品,那么您最好使用一个更好的(真正的)容器协调器,如Kubernetes、 docker 群、游牧者等,但今天在我看来,事实上的容器协调器是Kubernetes.

Javascript相关问答推荐

是什么原因导致此Angular 16电影应用程序中因类型错误而不存在属性?

如何为GrapesJS模板编辑器创建自定义撤销/重复按钮?

是什么原因导致此Angular 16应用程序中类型错误时属性结果不存在?

我无法在NightWatch.js测试中获取完整的Chrome浏览器控制台日志(log)

materialized - activeIndex返回-1

zoom svg以适应圆

格式值未保存在redux持久切片中

将2D数组转换为图形

阿波罗返回的数据错误,但在网络判断器中是正确的

cypress中e2e测试上的Click()事件在Switch Element Plus组件上使用时不起作用

JS—删除对象数组中对象的子对象

当使用';字母而不是与';var#39;一起使用时,访问窗口为什么返回未定义的?

使用getBorbingClientRect()更改绝对元素位置

如何将zoom 变换应用到我的d3力有向图?

搜索功能不是在分页的每一页上进行搜索

构建器模式与参数对象输入

限制数组中每个元素的长度,

我如何才能让p5.js在不使用实例模式的情况下工作?

相对于具有选定类的不同SVG组放置自定义工具提示

使用重新 Select 和对象理解 Select 器备忘