Running Node.js with Docker

You don’t have to install Node and manage different Node versions on your system. Everything can be done with Docker, including packaging your Node.js app into a distributable Docker image.

This assumes that you have Docker installed, you can verify it works with docker -v or anything similar, and you understand the basics behind images, containers, volumes, and such.

Node is available as an official Docker image at, in multiple versions and flavors (based on Debian, Alpine etc.).

Running simple Node CLI tasks

Suppose your project is a dummy script named index.js that does something important, outputs to the screen and exits out (like a true CLI tool). To make it simple, I only put this to index.js:

console.log('It works!');

It can’t become simpler than this. Now, I’m going to run the script with something like this:

docker run --rm -it --name nodetest -v "$PWD":/app -w /app node:12-alpine node index.js

This will run the Node 12 image based on Alpine in the interactive mode, name the container “nodetest”, remove the container after exiting, mount the current folder to /app in the container, set the working folder to the same folder /app, and finally run our script with node index.js.

docker run --rm -it --name nodetest -v "$PWD":/app -w /app node:12-alpine node index.js
It works!

You can confirm that there is no trash left by running docker ps -a:

docker ps -a              

No containers are present, which means it was successfully removed after it finished running our script.

Running continuous scripts (i.e. a web server) with Docker

Now that we know how to run Node scripts with Docker, we will do the same for running a simple Express server. With the exception of one thing – we need to make sure our process will be able to stop when we hit Ctrl+C. We need to hook into process events.

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/html'
    res.write('It works!');

const server = app.listen(port, () => console.log(`App listening on port ${port}!`))

process.on('SIGINT', () => {
    console.log('SIGINT received, stopping server..');
    server.close(() => {
        console.log('Server stopped, exiting now.');

process.on('SIGTERM', () => {
    console.log('SIGTERM received');
    server.close(() => {
        console.log('Server stopped, exiting now.');

In this code block, you will notice that we added process.on() calls and are watching for SIGINT and SIGTERM events. Then, we call server‘s close method. This is done before process.exit() to ensure our server finishes all outstanding operations.

Now, we can run our server with almost the same command. This time, I’m going to remove --rm as I want to be able to restart the container after I stop it.

docker run -it --name nodetest -v "$PWD":/app -w /app -p 3000:3000 node:12-alpine node index.js

Open your browser at localhost:3000 and you will be able to see It works!. Now in your terminal, hit Ctrl+c and the container will stop. Since we removed --rm, we are now able to restart our server at any given point in time:

% docker restart nodetest

The terminal responded with a container name, which means it was successful. Run docker ps and see if your server is running fine.

% docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                    NAMES
f8a3cab02fc8   node:12-alpine   "docker-entrypoint.s…"   8 minutes ago   Up 3 seconds>3000/tcp   nodetest

Wait, but we don’t see any console.log anymore? That’s right, now we need to attach to it.

docker attach nodetest

Now we will continue receiving all container output, and we will be able to stop it with Ctrl+c:

% docker attach nodetest
^CSIGINT received, stopping server..
Server stopped, exiting now.

Did you know that I made an in-browser image converter, where you can convert your images without uploading them anywhere? Try it out and let me know what you think. It's free.

Leave a Comment

Your email address will not be published. Required fields are marked *