diff --git a/react-express-mysql/backend/Dockerfile b/react-express-mysql/backend/Dockerfile index 758c4e8..38bc1b6 100755 --- a/react-express-mysql/backend/Dockerfile +++ b/react-express-mysql/backend/Dockerfile @@ -1,36 +1,31 @@ # if you're doing anything beyond your local machine, please pin this to a specific version at https://hub.docker.com/_/node/ -FROM node:10 - -RUN mkdir -p /opt/app +FROM node:lts # set our node environment, either development or production # defaults to production, compose overrides this to development on build and run ARG NODE_ENV=production ENV NODE_ENV $NODE_ENV +WORKDIR /code + # default to port 80 for node, and 9229 and 9230 (tests) for debug ARG PORT=80 ENV PORT $PORT EXPOSE $PORT 9229 9230 -# you'll likely want the latest npm, reguardless of node version, for speed and fixes -RUN npm i npm@latest -g - -# install dependencies first, in a different location for easier app bind mounting for local development -WORKDIR /opt -COPY package.json package-lock.json* ./ -RUN npm install && npm cache clean --force -ENV PATH /opt/node_modules/.bin:$PATH +COPY package.json /code/package.json +COPY package-lock.json /code/package-lock.json +RUN npm ci && npm cache clean --force # check every 30s to ensure this service returns HTTP 200 -HEALTHCHECK --interval=30s CMD node healthcheck.js +HEALTHCHECK --interval=30s \ + CMD node healthcheck.js # copy in our source code last, as it changes the most -WORKDIR /opt/app -COPY . /opt/app +COPY . /code # if you want to use npm start instead, then use `docker run --init in production` # so that signals are passed properly. Note the code in index.js is needed to catch Docker signals # using node here is still more graceful stopping then npm with --init afaik # I still can't come up with a good production way to run with npm and graceful shutdown -CMD [ "node", "index.js" ] +CMD [ "node", "src/index.js" ] diff --git a/react-express-mysql/backend/healthcheck.js b/react-express-mysql/backend/healthcheck.js index b5ca141..9a4e51a 100755 --- a/react-express-mysql/backend/healthcheck.js +++ b/react-express-mysql/backend/healthcheck.js @@ -1,20 +1,20 @@ -var http = require("http"); +const http = require("http"); -var options = { +const options = { timeout: 2000, - host: 'localhost', + host: "localhost", port: process.env.PORT || 8080, - path: '/healthz' // must be the same as HEALTHCHECK in Dockerfile + path: "/healthz" // must be the same as HEALTHCHECK in Dockerfile }; -var request = http.request(options, (res) => { - console.info('STATUS: ' + res.statusCode); - process.exitCode = (res.statusCode === 200) ? 0 : 1; +const request = http.request(options, res => { + console.info("STATUS: " + res.statusCode); + process.exitCode = res.statusCode === 200 ? 0 : 1; process.exit(); }); -request.on('error', function(err) { - console.error('ERROR', err); +request.on("error", function(err) { + console.error("ERROR", err); process.exit(1); }); diff --git a/react-express-mysql/backend/index.js b/react-express-mysql/backend/index.js deleted file mode 100755 index 56b75ba..0000000 --- a/react-express-mysql/backend/index.js +++ /dev/null @@ -1,78 +0,0 @@ -// simple node web server that displays hello world -// optimized for Docker image - -var express = require('express'); -// this example uses express web framework so we know what longer build times -// do and how Dockerfile layer ordering matters. If you mess up Dockerfile ordering -// you'll see long build times on every code change + build. If done correctly, -// code changes should be only a few seconds to build locally due to build cache. - -var morgan = require('morgan'); -// morgan provides easy logging for express, and by default it logs to stdout -// which is a best practice in Docker. Friends don't let friends code their apps to -// do app logging to files in containers. - -// Constants -const PORT = process.env.PORT || 8080; -// if you're not using docker-compose for local development, this will default to 8080 -// to prevent non-root permission problems with 80. Dockerfile is set to make this 80 -// because containers don't have that issue :) - -// Appi -var app = express(); - -app.use(morgan('common')); - -app.get('/', function (req, res) { - res.json({ message: 'Hello Docker World!' }); -}); - -app.get('/healthz', function (req, res) { - // do app logic here to determine if app is truly healthy - // you should return 200 if healthy, and anything else will fail - // if you want, you should be able to restrict this to localhost (include ipv4 and ipv6) - res.send('I am happy and healthy\n'); -}); - -var server = app.listen(PORT, function () { - console.log('Webserver is ready'); -}); - - -// -// need this in docker container to properly exit since node doesn't handle SIGINT/SIGTERM -// this also won't work on using npm start since: -// https://github.com/npm/npm/issues/4603 -// https://github.com/npm/npm/pull/10868 -// https://github.com/RisingStack/kubernetes-graceful-shutdown-example/blob/master/src/index.js -// if you want to use npm then start with `docker run --init` to help, but I still don't think it's -// a graceful shutdown of node process -// - -// quit on ctrl-c when running docker in terminal -process.on('SIGINT', function onSigint () { - console.info('Got SIGINT (aka ctrl-c in docker). Graceful shutdown ', new Date().toISOString()); - shutdown(); -}); - -// quit properly on docker stop -process.on('SIGTERM', function onSigterm () { - console.info('Got SIGTERM (docker container stop). Graceful shutdown ', new Date().toISOString()); - shutdown(); -}) - -// shut down server -function shutdown() { - server.close(function onServerClosed (err) { - if (err) { - console.error(err); - process.exitCode = 1; - } - process.exit(); - }) -} -// -// need above in docker container to properly exit -// - -module.exports = app; diff --git a/react-express-mysql/backend/src/config.js b/react-express-mysql/backend/src/config.js new file mode 100644 index 0000000..b5d759c --- /dev/null +++ b/react-express-mysql/backend/src/config.js @@ -0,0 +1,7 @@ +// Constants +module.exports = { + port: process.env.PORT || 8080 + // if you're not using docker-compose for local development, this will default to 8080 + // to prevent non-root permission problems with 80. Dockerfile is set to make this 80 + // because containers don't have that issue :) +}; diff --git a/react-express-mysql/backend/src/index.js b/react-express-mysql/backend/src/index.js new file mode 100755 index 0000000..c792933 --- /dev/null +++ b/react-express-mysql/backend/src/index.js @@ -0,0 +1,48 @@ +const app = require("./server"); +const { port } = require("./config"); + +const server = app.listen(port, function() { + console.log("Webserver is ready"); +}); + +// +// need this in docker container to properly exit since node doesn't handle SIGINT/SIGTERM +// this also won't work on using npm start since: +// https://github.com/npm/npm/issues/4603 +// https://github.com/npm/npm/pull/10868 +// https://github.com/RisingStack/kubernetes-graceful-shutdown-example/blob/master/src/index.js +// if you want to use npm then start with `docker run --init` to help, but I still don't think it's +// a graceful shutdown of node process +// + +// quit on ctrl-c when running docker in terminal +process.on("SIGINT", function onSigint() { + console.info( + "Got SIGINT (aka ctrl-c in docker). Graceful shutdown ", + new Date().toISOString() + ); + shutdown(); +}); + +// quit properly on docker stop +process.on("SIGTERM", function onSigterm() { + console.info( + "Got SIGTERM (docker container stop). Graceful shutdown ", + new Date().toISOString() + ); + shutdown(); +}); + +// shut down server +function shutdown() { + server.close(function onServerClosed(err) { + if (err) { + console.error(err); + process.exit(1); + } + process.exit(0); + }); +} +// +// need above in docker container to properly exit +// diff --git a/react-express-mysql/backend/src/server.js b/react-express-mysql/backend/src/server.js new file mode 100644 index 0000000..aab4fbb --- /dev/null +++ b/react-express-mysql/backend/src/server.js @@ -0,0 +1,31 @@ +// simple node web server that displays hello world +// optimized for Docker image + +const express = require("express"); +// this example uses express web framework so we know what longer build times +// do and how Dockerfile layer ordering matters. If you mess up Dockerfile ordering +// you'll see long build times on every code change + build. If done correctly, +// code changes should be only a few seconds to build locally due to build cache. + +const morgan = require("morgan"); +// morgan provides easy logging for express, and by default it logs to stdout +// which is a best practice in Docker. Friends don't let friends code their apps to +// do app logging to files in containers. + +// Appi +const app = express(); + +app.use(morgan("common")); + +app.get("/", function(req, res) { + res.json({ message: "Hello Docker World!" }); +}); + +app.get("/healthz", function(req, res) { + // do app logic here to determine if app is truly healthy + // you should return 200 if healthy, and anything else will fail + // if you want, you should be able to restrict this to localhost (include ipv4 and ipv6) + res.send("I am happy and healthy\n"); +}); + +module.exports = app; diff --git a/react-express-mysql/backend/test/sample.js b/react-express-mysql/backend/test/sample.js index 35948bb..628a64a 100755 --- a/react-express-mysql/backend/test/sample.js +++ b/react-express-mysql/backend/test/sample.js @@ -1,32 +1,33 @@ -const app = require('../index'); +const chai = require("chai"); +const chaiHttp = require("chai-http"); -const chai = require('chai'); -const chaiHttp = require('chai-http'); +const app = require("../src/server"); chai.use(chaiHttp); chai.should(); -describe('API /healthz', () => { - it('it should return 200', (done) => { - chai.request(app) - .get('/healthz') - .end((err, res) => { - res.should.have.status(200); - done(); - }); - }); +describe("API /healthz", () => { + it("it should return 200", done => { + chai + .request(app) + .get("/healthz") + .end((err, res) => { + res.should.have.status(200); + done(); + }); + }); }); -describe('API /', () => { - it('it should return Welcome message', (done) => { - chai.request(app) - .get('/') - .end((err, res) => { - res.should.have.status(200); - res.should.to.be.html; - res.text.should.be.equal("Hello Docker World\n"); - done(); - }); - }); +describe("API /", () => { + it("it should return Welcome message", done => { + chai + .request(app) + .get("/") + .end((err, res) => { + res.should.have.status(200); + res.should.to.be.html; + res.text.should.be.equal("Hello Docker World\n"); + done(); + }); + }); }); -