BackendDockerNode.jsDevOpsContainerizationExpress

Docker for Node.js Developers: Containerize Your App

Learn how to containerize a Node.js Express API with Docker — Dockerfile best practices, multi-stage builds, and Docker Compose for local development.

Abdur Razzak

Abdur Razzak

Full-Stack Web Developer

April 4, 2025 10 min read

Why Docker for Node.js?

Docker packages your Node.js application and all its dependencies into a container that runs identically in development, CI, and production. The classic 'it works on my machine' problem disappears. Containers also make deployment simpler — deploy the same Docker image to any cloud platform that supports containers: AWS ECS, Google Cloud Run, DigitalOcean App Platform, or Railway.

Writing Your Dockerfile

Start your Dockerfile with FROM node:20-alpine for a small base image. Copy package.json and package-lock.json first, then run npm ci (not npm install) for reproducible installs. Copy your source files after installing dependencies — this leverages Docker's layer caching so a code change doesn't reinstall all packages. Set CMD ['node', 'dist/server.js'] as the startup command.

Multi-Stage Builds

Use multi-stage builds to separate the build environment from the production image. Stage 1 (builder): install all dependencies including devDependencies and compile TypeScript. Stage 2 (production): start from a fresh node:20-alpine image, copy only the compiled JavaScript and production node_modules from the builder stage. The final image is much smaller — no TypeScript compiler, no test libraries, no build tools.

Docker Compose for Local Development

Create a docker-compose.yml that defines your Node.js service, MongoDB service, and Redis service. Use named volumes for MongoDB data persistence. Set environment variables in a .env file referenced by docker-compose. Mount your source code as a volume with hot-reload using nodemon inside the container. Run everything with docker-compose up — one command starts your entire development environment.

.dockerignore and Security

Create a .dockerignore file (similar to .gitignore) to exclude node_modules, .git, .env files, and test files from the build context. Never include .env files with secrets in your Docker image. Pass secrets to containers via environment variables at runtime, not build time. Run your Node.js process as a non-root user: USER node in your Dockerfile to limit the impact of a container escape vulnerability.

Pushing to a Container Registry

Push your Docker image to a container registry: Docker Hub (public), GitHub Container Registry, or AWS ECR. Tag your images with semantic versions and the git commit SHA for traceability. Build and push in your CI/CD pipeline (GitHub Actions) automatically on every merge to main. Pull the specific tagged image in your production deployment to ensure you are running exactly what was tested.

Share this article

All posts
#Docker#Node.js#DevOps#Containerization#Express
Abdur Razzak — Full Stack Web Developer
⭐ Top Rated

Upwork Top Rated Developer

Work With a Developer Clients Trust