Docker for Dummies (Part-2 Http Server)
Learn how to containerise an HTTP server
Alright! We've got a bit of Docker residue lingering around, especially in areas like handling environment variables, managing those marathon HTTP servers, and wrapping our heads around concepts like Docker layers and port mapping. So, let's roll up our sleeves and tackle these Docker intricacies head-on!
The second project, a directory named http-express-server
(in starter
branch), contains three files: package.json
,.env
(although env(s) should never be pushed to github) and main.js
.
// Import required modules
const express = require('express');
require('dotenv').config()
// Create an Express application
const app = express();
const apiKey = process.env.API_KEY;
// Define a route
app.get('/', (req, res) => {
res.send(`Hello, world! ${apiKey}`);
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
This is boilerplate code required to run an http server in express.js. Now, we need to run this on docker. You can start with starter
branch and see how you go.
Steps to Containerization:
Make
Dockerfile
Write Boilerplate code for now. This config is our Dockerfile. Only thing that got changed is that we exposed port 5000
# Use the official Node.js image from the Docker Hub, based on Alpine Linux for a smaller image size FROM node:22-alpine # Set the working directory inside the container to /usr/src/app WORKDIR /usr/src/app # Copy all files from the current directory to the working directory in the container COPY . . # Install the project dependencies specified in package.json RUN npm install # Expose port 5000 so that the APIs can be accessed from outside the container EXPOSE 5000 # Define the command to run the application CMD ["npm", "start"]
(Optional but recommended) Create a
.dockerignore
file. This file helps you avoid copying certain parts of your source code into the Docker container. For example, you might want to prevent your dev.env
files ornode_modules
from being copied when theCOPY . .
command is executed.# .dockerignore node_modules .env
Build your Docker Image with
docker build -t express-backend .
Running this image is slightly different than before. We have new arguments.
docker run -p 8000:5000 -e PORT=5000 -e API_KEY=<your_api_key_here> express-backend
These arguments are-p
and-e
.Port Mapping (i.e.
-p 8000:5000
): We are writing this to tell docker engine to that open an empty port8000
and connect/map container's port5000
to our local machine's8000
. Why do we need to do this ? Because container is supposed to be as isolated as possible.Environment Variable (i.e.
-e PORT=5000
or-e API_KEY=<your_api_key_here>
) : Here are defining our environment variables which are called instead docker container.
Now, your server is running inside the container. (here I have same port inside container and outside the container).
Just a minor thing right now. Port mapping... We need to discuss what port to hit and why. So, originally, we
const PORT = process.env.PORT || 3000;
, which meant we are letting env to decide which port to open or api endpoint. In our case, we are-e PORT=5000
which means the application itself is running on port 5000 of docker container. And then we are mapping docker container's 5000 port to our local device's 8000 Port. That's why we need to hitlocalhost:8000
.
Docker Layers and Caching
FROM node:22-alpine
WORKDIR /usr/src/app
COPY . .
RUN npm install
#EXPOSED 5000 Port so that we can call apis from outside the container
EXPOSE 5000
CMD ["npm", "start"]
This section is crucial, so please pay close attention. When you build your Docker image multiple times with code changes, you might notice lines like CACHED [1/5]
, CACHED [2/5]
, etc. These indicate that certain steps (or layers) were cached because they were identical to those in the previous image build. Docker caches these intermediate build results to save time and resources, avoiding redundant tasks.
So, what are "Layer"?
To optimize your Dockerfile, you should structure it so that the steps most likely to change are placed later in the build process. For example, in our Dockerfile, the RUN npm install
command depends on a set of dependencies that don't change often, whereas the COPY . .
command runs every time we make changes to our codebase.
One analogy people often use is that Docker layers are like the layers of an onion. Each time you run the Docker build command, you make a onion inside-out. First, you add the innermost layer, which is the base image of node-alpine. Then, you copy the files and run npm install
. If we build our Docker image multiple times, npm install
will be called more often than we change package.json
.
By rearranging these commands, we can reduce the number of layers that need to be rebuilt. Specifically, if we move the RUN npm install
command above the COPY . .
command, we only need to rebuild the final step (copying the source code) when the source code changes. Here's the optimized version of the same Dockerfile:
FROM node:22-alpine
# Create app directory
WORKDIR /usr/src/app
# Copy only package.json files as npm install requires this particular file.
COPY package*.json ./
# Install dependencies in empty project with just only package.json(s)
RUN npm install
# Copy back the source code o the application.
COPY . .
CMD ["npm", "start"]
In this optimized Dockerfile, Docker only needs to recompute the last step (COPY . .
) when the source code changes, significantly speeding up the build process. This way, you only rebuild what’s necessary, improving efficiency and saving time.
Optimize your Dockerfile by placing less frequently changing steps, like RUN npm install
, above frequently changing ones, like COPY . .
. Layers are steps in a Dockerfile, and leveraging Docker's caching of these layers reduces build times.
Next Part
That's all for today, in next part we will see how to make multi-container system with docker compose also look into how to "communicate" with each other with shared networks and shared space.
Next Part: https://blogs.shashankdaima.com/docker-for-dummies-part-3-docker-compose
Bye-Bye