Docker and Docker Compose for Node.js: A Production Guide
Learn how to containerize a Node.js application with Docker, orchestrate multi-service stacks with Docker Compose, and deploy production-ready containers with health checks, volumes, and networking.
DL
Shantanu Kumar
Chief Solutions Architect
March 13, 2026
21 min read
Updated March 2026
Docker transforms "it works on my machine" into "it works everywhere."
Docker has become the standard for packaging and deploying Node.js applications. Instead of configuring servers manually and hoping the production environment matches development, you ship a container that includes your application, its dependencies, and its runtime — identical everywhere it runs.
This guide covers the Docker workflow we use at Dude Lemon for production client deployments: writing optimized Dockerfiles, building multi-service stacks with Docker Compose, handling environment variables securely, implementing health checks, and deploying containers to AWS EC2 and other cloud platforms.
Containers do not replace good architecture. They enforce it.
1) Writing a Production Dockerfile
Most Node.js Dockerfiles you find online are single-stage builds that include development dependencies, lack proper user permissions, and produce images 3-4x larger than necessary. A production Dockerfile should use multi-stage builds, run as a non-root user, and include only what the application needs to run.
Multi-stage builds — separate dependency installation, build step, and runtime into distinct stages. The final image only contains production code.
Alpine base — node:20-alpine is ~50MB vs ~350MB for the full image. Smaller images deploy faster and have fewer vulnerabilities.
npm ci — installs exact versions from package-lock.json. Never use npm install in Docker builds.
Non-root user — if an attacker exploits your application, they cannot escalate to root privileges inside the container.
HEALTHCHECK — Docker and orchestrators use this to know when your container is ready to receive traffic.
2) The .dockerignore File
Without a .dockerignore file, Docker copies everything in your project directory into the build context — including node_modules, .git, environment files, and test fixtures. This makes builds slower and can leak secrets into your image.
bash.dockerignore
1node_modules
2npm-debug.log
3.git
4.gitignore
5.env
6.env.*
7*.md
8tests
9coverage
10.nyc_output
11.vscode
12.idea
13docker-compose*.yml
14Dockerfile*
Docker Compose orchestrates your entire application stack — API, database, cache, and reverse proxy.
3) Docker Compose for Multi-Service Applications
Production Node.js applications rarely run in isolation. A typical stack includes the API server, a PostgreSQL database, a Redis cache, and an Nginx reverse proxy. Docker Compose defines this entire stack in a single YAML file and launches all services with one command.
Key patterns in this configuration: depends_on with condition: service_healthy ensures your API does not start until PostgreSQL and Redis are actually ready — not just running. Named volumes (postgres-data, redis-data) persist data across container restarts.
4) Environment Variables in Docker
Never bake secrets into your Docker image. Environment variables should be injected at runtime through env_file in Docker Compose or through your cloud provider's secrets management. For secure secrets handling patterns, see our guide on securing Node.js applications in production.
Notice the database host is postgres, not localhost. Inside a Docker network, services reference each other by their service name defined in docker-compose.yml. This is one of the most common mistakes when moving from local development to Docker.
5) Nginx Reverse Proxy Configuration
Nginx sits in front of your Node.js API, handling SSL termination, static file serving, request buffering, and load balancing. This is the same pattern we covered in our EC2 deployment guide, containerized for Docker.
Docker, Nginx, and load balancers all need to know if your application is healthy. A proper health check endpoint verifies not just that the process is running, but that it can reach its dependencies — database, cache, and external services.
Return a 503 status code when any dependency is down. This tells load balancers and Docker to stop routing traffic to this instance and potentially restart it based on the HEALTHCHECK configuration.
7) Docker Networking Deep Dive
Docker Compose creates an isolated network for your services. Understanding how container networking works prevents the most common Docker debugging headaches.
Service discovery — containers reference each other by service name (postgres, redis, api), not by IP address
Port mapping — 3000:3000 maps host port to container port. Only expose ports you need accessible from outside Docker
Internal communication — services on the same Docker network communicate directly without port mapping
Bridge network — the default driver isolates your stack from other containers on the same host
DNS resolution — Docker provides built-in DNS so service names resolve automatically within the network
8) Volume Management for Persistent Data
Containers are ephemeral — when they stop, their filesystem is destroyed. Volumes persist data independently of the container lifecycle. For databases and caches, volumes are critical.
Docker containers deploy identically across development, staging, and production environments worldwide.
9) Production Deployment Workflow
Here is the deployment workflow we use for Docker-based applications at Dude Lemon. This integrates with GitHub Actions for CI/CD and deploys to AWS EC2 instances.
bashscripts/deploy.sh
1#!/bin/bash
2set-euopipefail
3
4echo"🚀Startingdeployment..."
5
6# Pull latest code
7cd/var/www/myapp
8gitpulloriginmain
9
10# Build new images
11dockercomposebuild--no-cacheapi
12
13# Run database migrations
14dockercomposeexecapinpmrunmigrate
15
16# Rolling restart — zero downtime
17dockercomposeup-d--no-depsapi
18
19# Wait for health check
20echo"Waitingforhealthcheck..."
21foriin{1..30};do
22ifcurl-sfhttp://localhost:3000/health > /dev/null 2>&1; then
23echo"Healthcheckpassed"
24break
25fi
26sleep2
27done
28
29# Clean up old images
30dockerimageprune-f
31
32echo"Deploymentcomplete"
The --no-deps flag restarts only the API container without touching PostgreSQL, Redis, or Nginx. Combined with the health check wait loop, this gives you zero-downtime deployments for your application updates.
10) Docker Security Best Practices
Always run as a non-root user inside containers
Use specific image tags (node:20-alpine), never :latest
Scan images for vulnerabilities with docker scout or Trivy
Use read-only filesystem where possible: read_only: true
Limit container resources with mem_limit and cpus
Never store secrets in Dockerfiles or image layers
Conclusion: Containers Are Production Infrastructure
Docker is not just a development convenience — it is production infrastructure. The patterns in this guide ensure your Node.js applications run reliably, securely, and consistently across every environment. Combined with PM2 cluster mode for process management and proper security hardening, Docker gives you a complete production deployment system.
At Dude Lemon, we containerize every production application we build. Whether you are deploying a single API or a multi-service platform with databases, caches, and background workers, Docker Compose gives you a reproducible, versionable, and deployable architecture. If you need help containerizing your application or building a CI/CD pipeline for Docker deployments, get in touch with our DevOps team.
Ship containers, not instructions. If your deployment requires a wiki page, you are not done yet.
Need help building this?
Let our team build it for you.
Dude Lemon builds production-grade web apps, APIs, and cloud infrastructure. Get a free consultation and project proposal within 48 hours.