Skip to main content
Back to Blog
Web DevelopmentDevOpsCloud Computing
13 August 20268 min readUpdated 13 August 2026

Building a Node.js Application Using Docker

Introduction Docker enables developers to encapsulate and execute applications as containers. These containers are lightweight, isolated processes that operate on a shared OS, p...

Building a Node.js Application Using Docker

Introduction

Docker enables developers to encapsulate and execute applications as containers. These containers are lightweight, isolated processes that operate on a shared OS, providing a more efficient alternative to virtual machines. Containers are not a new concept; however, their advantages, such as process isolation and environment consistency, are increasingly significant for developers utilizing distributed application architectures.

When developing and scaling an application with Docker, the initial step usually involves creating an image for your application, which can then be run in a container. This image includes your application code, libraries, configuration files, environment variables, and runtime, ensuring a standardized environment that contains only the necessary elements to build and run your application.

In this guide, you'll create an application image for a static website using the Express framework and Bootstrap. You'll build a container from this image, push it to Docker Hub for future use, and then pull the stored image from your Docker Hub repository to build another container, demonstrating how to recreate and scale your application.

Key Takeaways

  • Docker offers process isolation, environment standardization, and consistent deployment across environments.
  • Multi-stage Dockerfiles help create optimized production images with a minimal attack surface.
  • Run containers as non-root users, use specific base image tags, and implement proper layer caching for security.
  • Implement health checks, proper logging, and resource limits for production deployments.
  • Scale applications with multi-container setups using Docker Compose for development and production.
  • Use Docker Scout for automated security scanning of your container images.

Illustration for: - Docker offers process isolat...

Prerequisites

To follow this tutorial, ensure you have:

  • A server running Ubuntu, with a non-root user having sudo privileges and an active firewall.
  • Docker installed on your server.
  • Node.js and npm installed.
  • A Docker Hub account.

Step 1 — Installing Application Dependencies

To create your image, first, prepare your application files, which you can then copy to your container. These files will include your application’s static content, code, and dependencies.

Create a directory for your project in your non-root user’s home directory:

mkdir node_project

Navigate to this directory:

cd node_project

This will be the root directory of the project.

Next, create a package.json file with your project’s dependencies and other identifying information. Open the file with your favorite editor:

nano package.json

Add the following information about the project:

{
  "name": "nodejs-image-demo",
  "version": "1.0.0",
  "description": "nodejs image demo",
  "author": "Your Name <your_email@example.com>",
  "license": "MIT",
  "main": "app.js",
  "keywords": [
    "nodejs",
    "bootstrap",
    "express"
  ],
  "dependencies": {
    "express": "^4.16.4"
  }
}

This file includes the project name, author, and license. The "main" field defines the entry point for the application, app.js, and the "dependencies" field lists the project dependencies, such as Express 4.16.4 or above. Save and close the file when finished.

To install your project’s dependencies, run:

npm install

Step 2 — Creating the Application Files

We will create a website offering users information about sharks. Our application will have a main entry point, app.js, and a views directory including the project’s static assets. The landing page, index.html, will offer users some preliminary information and a link to a page with more detailed shark information, sharks.html.

First, open app.js in the main project directory to define the project’s routes:

nano app.js

Add the following code:

const express = require('express');
const app = express();
const router = express.Router();

const path = __dirname + '/views/';
const port = 8080;

router.use(function (req,res,next) {
  console.log('/' + req.method);
  next();
});

router.get('/', function(req,res){
  res.sendFile(path + 'index.html');
});

router.get('/sharks', function(req,res){
  res.sendFile(path + 'sharks.html');
});

app.use(express.static(path));
app.use('/', router);

app.listen(port, function () {
  console.log('Example app listening on port 8080!')
})

Save and close the file when finished.

Next, create the views directory:

mkdir views

Open the landing page file, index.html:

nano views/index.html

Add the desired content, then save and close the file.

Repeat this process for the sharks.html file, adding specific content as needed.

Finally, create a custom CSS style sheet linked to in index.html and sharks.html by first creating a css folder in the views directory:

mkdir views/css

Open the style sheet:

nano views/css/styles.css

Add the CSS code to set the desired color and font for your pages. Save and close the file when finished.

Step 3 — Writing the Dockerfile

Your Dockerfile specifies the contents of your application container when executed. Using a Dockerfile allows you to define your container environment, ensuring consistency across various stages.

In your project’s root directory, create the Dockerfile:

nano Dockerfile

Add the following multi-stage Dockerfile:

## Build stage
FROM node:20-alpine AS builder

## Set working directory
WORKDIR /app

## Copy package files
COPY package*.json ./

## Install dependencies
RUN npm ci --only=production && npm cache clean --force

## Copy source code
COPY . .

## Production stage
FROM node:20-alpine AS production

## Create app directory
WORKDIR /app

## Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

## Copy built application from builder stage
COPY --from=builder --chown=nextjs:nodejs /app /app

## Switch to non-root user
USER nextjs

## Expose port
EXPOSE 8080

## Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:8080', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"

## Start the application
CMD ["node", "app.js"]

Save and close the file when finished.

Step 4 — Using a Repository to Work with Images

By pushing your application image to a registry like Docker Hub, you make it available for future use as you build and scale your containers.

First, log in to your Docker Hub account:

docker login -u your_dockerhub_username

When prompted, enter your Docker Hub account password.

Next, push the application image to Docker Hub using the tag you created earlier:

docker push your_dockerhub_username/nodejs-image-demo

To test the utility of the image registry, you can remove your current application container and image and rebuild them with the image from your repository.

FAQs

Why should I use Docker for Node.js apps?

Docker provides several key benefits for Node.js applications:

  • Consistent environments across development, testing, and production
  • Easy scaling using container orchestration tools like Kubernetes
  • Dependency isolation, ensuring each app runs with its own isolated dependencies
  • Simplified deployment processes
  • Resource efficiency, as containers share the host OS kernel

How do I connect my Node.js container to a database?

Use Docker Compose to orchestrate multiple services:

services:
  app:
    build: .
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db
  
  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

How can I reduce Docker image size?

Several strategies can significantly reduce your Docker image size:

  • Use multi-stage builds to separate build and runtime environments
  • Choose Alpine base images for their smaller size
  • Use .dockerignore to exclude unnecessary files
  • Optimize layer caching to maximize cache hits
  • Remove build dependencies from production images
  • Consider distroless images for minimal attack surface

Conclusion

This tutorial has covered how to build, containerize, and deploy a Node.js application using Docker. By following a multi-stage Dockerfile approach, you can produce optimized production images while enhancing security by running containers as a non-root user. Integrating Docker Compose allows for easy orchestration of multi-service application environments, while health checks and monitoring features help ensure your containers are production-ready.

Next Steps

Explore more on Docker and Node.js with related tutorials, focusing on development environments, security features, and deployment strategies for Node.js applications.