"But it works on my machine!" If you are a developer, you have likely said this exact phrase. Your code runs perfectly on your laptop, but the moment you hand it to a coworker or try to deploy it to a production server, it crashes. The Node version is wrong, a dependency is missing, or the environment variables are mismatched.
This is exactly the problem Docker solves.
Docker allows you to package your application and all of its dependencies into a single, standardized unit. Today, I am going to show you how to containerize a simple Node.js application in 5 easy steps so it runs perfectly anywhere, every single time.
Before we write the code, let's clear up the biggest point of confusion for beginners.
The Core Concept: Image vs. Container
People often use these terms interchangeably, but they are completely different things:
- The Docker Image: This is the recipe. It is a static, read-only file that contains your source code, libraries, dependencies, and tools. An image does not "run."
- The Docker Container: This is the cake. It is the running, active instance of your Docker Image. You can start, stop, and delete a container.
You build an Image once, and you can spin up a hundred Containers from it.
Prerequisites
- Docker Desktop installed on your machine.
- A basic understanding of the terminal.
- Node.js installed locally (just for our initial setup).
Step 1: Create a Simple Node.js App
We need something to containerize. Let's build a dead-simple Express server. Open your terminal, create a new folder, and initialize a project:
mkdir my-docker-app
cd my-docker-app
npm init -y
npm install express
Next, create a file named server.js and paste in this code:
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Hello from inside a Docker Container! 🐳');
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Finally, open your package.json file and add a start script:
"scripts": {
"start": "node server.js"
}
Step 2: Write the Dockerfile
The Dockerfile is a text document that contains all the commands needed to assemble your Image. In the root of your project folder, create a new file named exactly Dockerfile (no file extension).
Add the following instructions:
# 1. Specify the base image
FROM node:18-alpine
# 2. Set the working directory inside the container
WORKDIR /usr/src/app
# 3. Copy package.json and package-lock.json first
COPY package*.json ./
# 4. Install dependencies
RUN npm install
# 5. Copy the rest of your application code
COPY . .
# 6. Expose the port your app runs on
EXPOSE 3000
# 7. Define the command to run your app
CMD ["npm", "start"]
Why do we copy the package.json file before the rest of the code? Docker builds images in layers and caches them. If you change a single line in server.js, Docker doesn't need to re-install all your npm packages. It uses the cached layer for the dependencies, making your builds incredibly fast.
Step 3: Add a .dockerignore File
You wouldn't push your node_modules folder to GitHub, and you shouldn't copy it into your Docker image either. Your container should install its own clean dependencies.
Create a file named .dockerignore in your root directory:
node_modules
npm-debug.log
.git
Step 4: Build the Docker Image
Now it is time to bake the recipe. Open your terminal in the root of your project and run this command:
docker build -t my-node-app .
The
-tflag "tags" your image with a readable name (my-node-app).The
.at the very end tells Docker to look for the Dockerfile in the current directory. Do not forget the period!
Step 5: Run the Docker Container
Your image is built. Now, let's bring it to life by spinning up a container.
docker run -p 3000:3000 -d my-node-app
- The
-p 3000:3000flag maps port 3000 on your physical laptop to port 3000 inside the isolated container. - The
-dflag runs the container in "detached" mode so it runs in the background, keeping your terminal usable.
Open your web browser and navigate to http://localhost:3000.
The Takeaway
Congratulations! You just built and ran your first Dockerized application.
Because the environment is bundled directly into the image, you could send this exact my-node-app image to a colleague running Windows, a friend on a Mac, or a Linux server on AWS, and it will run flawlessly. No configuration required.
Next week, we are going to take this a step further and combine it with the CI/CD pipeline we built last time. I will show you how to automatically build this Docker image using GitHub Actions every time you push code.
Make sure to subscribe to the blog so you don't miss it!
If this tutorial helped you finally understand Docker, you can ☕ buy me a coffee here to support my work!

















