Building a Node.js App
Table of Contents
- Create Express App
- Dockerfile &
.dockerignore - Build Image
- Run Container
- Test API
- Common Mistakes to Avoid
Now that we understand Dockerfiles and core image commands, let's put theory into practice by containerizing a lightweight Node.js Express application from scratch.
1. Create Express App
Start by initializing a basic Express web application.
Create package.json:
{
"name": "docker-node-express",
"version": "1.0.0",
"description": "Express app containerized with Docker",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.19.2"
}
}
Create index.js:
const express = require("express");
const app = express();
const PORT = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.json({
status: "success",
message: "Hello from inside the Docker container!",
timestamp: new Date().toISOString(),
});
});
app.get("/health", (req, res) => {
res.status(200).json({ status: "UP" });
});
app.listen(PORT, "0.0.0.0", () => {
console.log(`Server running on http://0.0.0.0:${PORT}`);
});
2. Dockerfile & .dockerignore
Create a .dockerignore file in the project root to prevent copying local node modules and build artifacts into the build context:
Create .dockerignore:
node_modules
npm-debug.log
.git
.gitignore
README.md
Create Dockerfile:
# 1. Use lightweight LTS base image
FROM node:20-alpine
# 2. Build-time argument and runtime environment
ARG BUILD_DATE
LABEL org.opencontainers.image.created=$BUILD_DATE
ENV NODE_ENV=production
# 3. Set working directory
WORKDIR /app
# 4. Copy package definitions first to utilize layer caching
COPY package*.json ./
# 5. Install production dependencies
RUN npm ci --only=production
# 6. Copy remaining application code
COPY . .
# 7. Expose port 3000
EXPOSE 3000
# 8. Define entry point command
CMD ["npm", "start"]
3. Build Image
Build your Docker image and tag it as express-app:v1:
docker build -t express-app:v1 .
To verify the image was created:
docker images express-app:v1
4. Run Container
Run the newly built container in detached mode (-d), mapping port 3000 on your host to port 3000 inside the container:
docker run -d \
--name my-express-container \
-p 3000:3000 \
express-app:v1
Verify it is running:
docker ps -f name=my-express-container
5. Test API
You can test the containerized endpoint using curl or your browser:
# Test primary endpoint
curl http://localhost:3000
# Expected Response:
# {"status":"success","message":"Hello from inside the Docker container!","timestamp":"..."}
# Test health check endpoint
curl http://localhost:3000/health
# Expected Response:
# {"status":"UP"}
6. Common Mistakes to Avoid
-
Binding Express to
127.0.0.1(localhost) instead of0.0.0.0: Inside a container,localhostrefers only to the container's internal loopback interface. If Express listens on127.0.0.1, external traffic forwarded by Docker won't reach it. Always bind to0.0.0.0. -
Forgetting
.dockerignore: Omitting.dockerignorecauses your localnode_modules/folder to overwrite container dependencies, which can lead to platform mismatch bugs (e.g., native binaries compiled on macOS failing on Linux Alpine). -
Copying code before running
npm install: CopyingCOPY . .beforeRUN npm installinvalidates the layer cache on every single code edit, forcingnpm installto re-run on every build. -
Running as root user: By default, Docker containers run as
root. For production environments, switch to a non-root user (e.g., addingUSER nodebeforeCMDin your Dockerfile) to improve security.
Buy Me a Coffee
Found this tutorial helpful? Consider buying me a coffee to support more free content!