I'm creating a website using NextJS and Docker so that I can easily deploy it. I used npx-create-next-app to initialize it and used this Dockerfile (slightly modified) to containerize it. Since I wanted to use SSL with my server without going through the hassle of setting up a proxy, I followed this article, and setup the custom server.
This worked fine when I ran it outside of a docker container, and performed as expected, serving over HTTPS. However when I containerized it, and tried to open the webpage over HTTPS, I came up with SSL_ERROR_RX_RECORD_TOO_LONG, but I could open the page using just HTTP (which I could not do when running outside of a container). Some googling led me to this question, from which I concluded that when running outside of a docker container, the custom server runs the server over HTTPS, as expected, however when I containerize it, it starts running HTTP, even though no code has been changed.
I'd expect the behavior to be the same when running locally or containerized.
At first I assumed this was due to invalid key and cert values in httpsOptions however I wasn't able to find anything that would make them invalid, and I don't see how that would cause this strange behavior. I tried changing the Docker run environment from node:alpine-16 to just node:latest to see if it had something to do with the parent image, but that was fruitless.
One other minor issue I had is that console.log does not seem to output to the container's log for some reason, I tried googling this but didn't find much of anything pertaining to it. This has made debugging much harder as I can't really output any debug data. The only log I get when running inside of a container is Listening on port 3000 url: http://localhost:3000, which I assume is output by some library/package as it isn't anywhere in my code.
Here is my custom server code in case it would be helpful:
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}`)
})
})
Link to a reproducible example here.
The thing is, based on your sample repo, that your server.js file that is in the root of your repo gets overwritten in the image because of this line in the Dockerfile:
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
So the actual server.js that is running in the container is the server.js that is created by the yarn build command and it looks like this (you can exec into the container and see it for yourself):
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
)
})
And as you see it starts a http server not a https. Also this is why the console.log("lksdfjls"); in your own server.js will not get executed.
What I would suggest is to leave node as it is, running on http://localhost:3000 and set up a reverse proxy that would forward incoming requests to this node backend that is accessible only from the reverse proxy. And of course reverse proxy would handle TLS termination. A docker compose setup would be more convenient for this so you could put the reverse proxy container (nginx for example) in the compose project too and map a directory from the docker host where your cert files are stored into the reverse proxy container at runtime - DO NOT BAKE CERTS OR ANY OTHER SECRETS INTO ANY IMAGE, not even if it is an internally used image only because it could leak out accidentally any time.
Also you could just manually run the two container with docker run but compose would make life easier it has a lot of capabilities for example you could scale compose services up and down so your backend service would run not in one but many containers. But if this would be a high load and/or business critical production stuff then you are better off with a better (real) container orchestrator like kubernetes, docker swarm, nomad etc but today as I see it the de facto container orchestrator is kubernetes.
I have builded an app using Nuxt and I have created simply server middleware for handling email sending. Everything is working on dev but on production I have 404 error from that endpoint. Does anybody know how to include server-middleware into build files or any other way?
server-middleware:
const bodyParser = require('body-parser')
const app = require('express')()
app.use(bodyParser.json())
app.post('/', (req, res) => {
// Some code here
})
module.exports = app
Nuxt.config.js
serverMiddleware: [
{ path: '/contact/send', handler: '~/server-middleware/email.js' }
],
Response here:
I found an answer on github issue nuxt repo:
This is correct - server middleware aren't compiled or part of your
webpack build and so you should make sure to copy them separately into
production. (As well as making sure that any dependencies they have
are installed in production.)
(Note that this will change with Nuxt 3 - you'll be able to have a
single built server that includes your server middleware.)
Issue here:
https://github.com/nuxt/nuxt.js/issues/9158#issuecomment-820676790
Here is an in-depth answer regarding the whole setup of a serverMiddleware.
This solutions seems to work: https://github.com/nuxt/nuxt.js/issues/1486#issuecomment-325181524
// nuxt.config.js
serverMiddleware: [
'~/api/index.js',
]
// api/index.js
const app = require('express')()
module.exports = { path: '/api', handler: app }
app.get('/say/:word', (req, res) => {
res.json(req.params)
})
I'm trying to add Log4js-Node to a Node.js server running on Apache. Here's my code:
const path = require("path");
const express = require("express");
const log4js = require('log4js');
const app = express();
const logger = log4js.getLogger();
logger.level = "debug";
const port = 443;
log4js.configure({
appenders: { everything: { type: 'file', filename: 'logs.log', flags: 'w' } },
categories: { default: { appenders: ['everything'], level: 'ALL' } }
});
const server = app.listen(port, () => {
logger.debug("listening to requests on port " + port);
});
app.get("/log", (req, res) => {
res.sendFile(path.join(__dirname + "/logs.log"));
});
When I run the script on Node.js on my computer and navigate to localhost:443/log I see what I expect, which is this:
[2020-03-17T22:50:43.145] [DEBUG] default - listening to requests on port 443
But when I run the code on a remote server it crashes and I get this in the error page (with part of the path replaced by me with "[removed]"):
App 25925 output: at Server. ([removed]/index.js:27:9)
App 25925 output: at Logger. [as debug] ([removed]/12/lib/node_modules/log4js/lib/logger.js:124:10)
App 25925 output: at Logger.log ([removed]/12/lib/node_modules/log4js/lib/logger.js:73:12)
App 25925 output: at Logger._log ([removed]/12/lib/node_modules/log4js/lib/logger.js:90:16)
App 25925 output: at Object.send ([removed]/12/lib/node_modules/log4js/lib/clustering.js:97:15)
App 25925 output: [removed]/12/lib/node_modules/log4js/lib/clustering.js:97
App 25925 output: at Object. ([removed]/12/lib/node_modules/log4js/lib/clustering.js:8:13)
I'm using A2 Hosting which uses Apache 2.4.41. I opted for Node.js 12.9.0, and Log4js 6.1.2. The package.json should be the same on both my computer and the server, and I've run npm install on both.
Is this just an issue with Log4js and the server, or have I missed something somewhere?
This was actually a relatively simple fix. The path referenced by the last error in the stack trace is a Log4js module that implements clustering support through Node's "cluster" module. The line "8" referenced is cluster = require("cluster"). It's wrapped in a try/catch block like this:
try {
cluster = require("cluster"); //eslint-disable-line
} catch (e) {
debug("cluster module not present");
disabled = true;
}
The installation of Node.js on my computer came with the "cluster" module, however as far as I can tell, the server I'm using doesn't support it. Also, the version of Node I'm using on my computer is newer than what I'm using on the server (so I've now installed 12.9 on my machine). I believe the older version of Node doesn't bother trying to catch the exception and tries to load the cluster module, fails, and then throws the error.
So the simple fix was to comment out most of the "try/catch" block, leaving just the contents of "catch" like this:
// try {
// cluster = require("cluster"); //eslint-disable-line
// } catch (e) {
debug("cluster module not present");
disabled = true;
// }
If someone has a better fix, I'm open to suggestions.
The same response of #skittleswrapper,thx, it work for me.
I use Node.js 14.18.1 with log4js 6.3.0.
But i wondering what'is the necessary of this module 'cluster' and if we can
add it to our app in other way.
I am building a blog using Node js and Express and hosting it on firebase. When I serve the website locally everything works just fine and the html is served as expected. But, when I deploy the server the routes no longer work and the html files can't be found. I'm sure it has to do with how firebase deploy hold the html files.
I'm not really sure where to go from here. I can't really find great guidance on how to set up something like this on the firebase docs.
const functions = require("firebase-functions")
const cors = require("cors")
const express = require("express")
const path = require("path")
/* Express with CORS */
const app = express()
app.use(cors({ origin: true }))
app.get("/", (request, response) => {
response.send("Hello from Express on Firebase with CORS!")
})
//File path consts
const publicDir = "/Users/wilson/wildman-talks-fb/public";
const blogDir = "/Users/wilson/wildman-talks-fb/public/blogs";
app.get("/about/", (req, res) =>{
res.sendFile(path.join(publicDir, "/about.html"));
});
app.get("/contact/", (req, res) =>{
res.sendFile(path.join(publicDir, "/contact.html"));
});
app.get("/tools/", (req, res) =>{
res.sendFile(path.join(publicDir, "/tools.html"));
});
app.get("/five-steps-july-20/", (req, res) =>{
//res.send(path.join(publicDir, "/five-steps-july-20.html"));
res.sendFile(path.join(publicDir, "/five-steps-july-20.html"));
})
exports.app = functions.https.onRequest(app)
So what is happening is when I deploy the site locally all of the links in my webpage work to other html webpages for my site. When I deploy it on firebase I get 404 errors. I was able to use path.join(__dirname, "../public") and print out all of the files contained there. When i did that these were the files that were there on my local host: [".DS_Store","404.html","about.html","blogs","contact.html","css","five-steps-july-20.html","img","index.html","js","mail","tools.html","vendor"]. After deploying it just returns me a 500 error so I guess that won't help.
Your directories contain absolute paths to your filesystem. Try to use dynamic absolute paths.
Change the paths from
const publicDir = "/Users/wilson/wildman-talks-fb/public";
const blogDir = "/Users/wilson/wildman-talks-fb/public/blogs";
To
const path = require("path");
const publicDir = path.join(__dirname, "/public";)
const blogDir = path.join( __dirname, "/public/blogs");
I'm using React BrowserHistory for navigation. I want the same files to be served from my Node server regardless of the URL path.
My server code:
const http = require('http');
const path = require('path');
const express = require('express');
const app = express();
const hostname = 'localhost';
app.use(express.static('../public'));
app.get('*', (req, res)=>{
res.sendFile(path.join(__dirname, '../public/index.html'));
});
const port = process.env.PORT || 8007;
app.listen(port, ()=>{
console.log('Production Express server running at localhost:' + port)
});
http://localhost:8007 => works fine
http://localhost:8007/help => works fine
http://localhost:8007/help/faq => Fails
I want all URLs to return the same resources, served from ../public. However, it seems that the express.static doesn't work when the resource isn't requested from the root. So, for instance, when the browser asks for <script src="scripts/main.js"></script>, it thinks I want ../public/help/scripts/main.js, whereas I actually want ../public/scripts/main.js/. So, because express doesn't find such file, it moves on to the app.get('*'..., which returns the ../public/index.html file when the script is requested.
So, the desired behavior is:
return the same index.html for any path (with any number of sub-folders), and let React figure out what to show
return the resources always relative to the path index.html is served from, not relative to the path in the URL
It works when I use absolute paths in my resource requests (I.E. <script src="http://localhost:8007/scripts/main.js"></script>), however writing it like that obviously isn't desirable, because it needs to be changed when it's hosted elsewhere.
What should I do?
you can use that condition for your routing:
app.get('*/**', (req, res) => {
res.sendFile(path.join(__dirname, '../public/index.html'));
});
It's work find for me.
Be carefull with this method, your browser will reload the entire page for each call to a route.