Building Graceful Node Applications in Docker

Request lifecycle with graceful exiting (in-flight requests will still be aborted, but processing requests will complete)

Graceless exit

// package.json
{
"name": "simple_node_app",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.13.3"
}
}
// server.js
'use strict';
const express = require('express');const PORT = process.env.port || 8080;const app = express();app.get('/', function (req, res) {
res.send('Hello world\n');
});
app.get('/wait', function (req, res) {
const timeout = 5;
console.log(`received request, waiting ${timeout} seconds`);
const delayedResponse = () => {
res.send('Hello belated world\n');
};
setTimeout(delayedResponse, timeout * 1000);
});
app.listen(PORT);
# Start the application
$ npm install && npm start
> start simple_node_app
> node server.js
$ curl http://localhost:8080/wait
# find the PID of the npm process
$ ps -falx | grep npm | grep -v grep
UID PID PPID CMD
502 68044 31496 npm
# send a SIGTERM (-15) signal to that process
$ kill -15 68044
$ npm start
> node server.js
Running on http://localhost:8080
received request, waiting 5 seconds
Terminated: 15
$ curl http://localhost:8080/wait
curl: (52) Empty reply from server

Handle all the signals

const server = app.listen(PORT);// The signals we want to handle
// NOTE: although it is tempting, the SIGKILL signal (9) cannot be intercepted and handled
var signals = {
'SIGHUP': 1,
'SIGINT': 2,
'SIGTERM': 15
};
// Do any necessary shutdown logic for our application here
const shutdown = (signal, value) => {
console.log("shutdown!");
server.close(() => {
console.log(`server stopped by ${signal} with value ${value}`);
process.exit(128 + value);
});
};
// Create a listener for each of the signals that we want to handle
Object.keys(signals).forEach((signal) => {
process.on(signal, () => {
console.log(`process received a ${signal} signal`);
shutdown(signal, signals[signal]);
});
});
$ npm start> node server.jsRunning on http://localhost:8080
received request, waiting 5 seconds
process received a SIGTERM signal
shutdown!
sending response!
server stopped by SIGTERM with value 15
$ curl http://localhost:8080/wait
Hello belated world
npm ERR! simple_node_app@1.0.0 start: `node server.js`
npm ERR! Exit status 143

Dockerize all the things

# Dockerfile
FROM node:boron
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install --production --quiet
# Bundle app source
COPY . /usr/src/app
EXPOSE 8080CMD ["npm", "start"]
$ docker build -q -t grace . && docker run -p 1234:8080 --rm --name=grace grace> node server.js
$ curl http://localhost:1234/wait
curl: (52) Empty reply from server

NPM guts

$ ps -falx | grep "node\|npm" | grep -v grep
UID PID PPID CMD
502 65378 31800 npm
502 65379 65378 node server.js
$ ps xao uid,pid,ppid,pgid,comm | grep 65378
UID PID PPID PGID CMD
502 65378 31800 65378 npm
502 65379 65378 65378 node
$ ps falx
UID PID PPID COMMAND
0 1 0 npm
0 16 1 sh -c node server.js
0 17 16 \_ node server.js
# Add an extra port mapping to our container so that we can run two node servers
$ docker run -p 1234:8080 -p 5678:5000 --rm --name=grace grace
# SSH into the container in another terminal and check the currently-running processes
$ docker exec -it grace /bin/sh
$ ps falx
UID PID PPID COMMAND
0 1 0 npm
0 15 1 sh -c node server.js
0 16 15 \_ node server.js
# Start up a second node server on a different port
$ port=5000 npm start
> node server.js
Running on http://localhost:5000
$ docker exec -it grace /bin/sh
$ ps falx
UID PID PPID COMMAND
0 22 0 /bin/sh
0 46 22 \_ npm
0 56 46 \_ sh -c node server.js
0 57 56 \_ node server.js
0 1 0 npm
0 15 1 sh -c node server.js
0 16 15 \_ node server.js

The great signal hand-off

# Dockerfile
EXPOSE 8080
CMD ["node", "server.js"]
$ docker build -q --no-cache -t grace . && docker run -p 1234:8080 --rm --name=grace grace
Running on http://localhost:8080
received request, waiting 5 seconds
process received a SIGTERM signal
shutdown!
sending response!
server stopped by SIGTERM with value 15
$ curl http://localhost:1234/wait
Hello belated world

Really really long requests

app.get('/wait', function (req, res) {
// increase the timeout
const timeout = 15;
console.log(`received request, waiting ${timeout} seconds`);
const delayedResponse = () => {
console.log("sending response!");
res.send('Hello belated world\n');
};
setTimeout(delayedResponse, timeout * 1000);
});
$ docker build -q --no-cache -t grace . && docker run -p 1234:8080 --rm --name=grace --init grace
Running on http://localhost:8080
received request, waiting 15 seconds
process received a SIGTERM signal
shutdown!
$ curl http://localhost:1234/wait
curl: (52) Empty reply from server

A graceful conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store