[DevOps Series] Part 3: Docker and the world of containerization
For me, Docker is like a “Hello, World” to the DevOps world. At first glance, it’s hard to understand because it introduces many new concepts. In this chapter, I’ll simplify Docker for you. Let’s start!
This is the reason

Yes, but not every time. Apps that work on your machine might not work on mine. Modern applications have many dependencies, libraries, and other components…

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh ./get-docker.sh
sudo docker run hello-world # Test if Docker is installed correctlyBy default, the Docker daemon runs as root, so remember to add sudo before every Docker command.
mkdir docker-nodejs-backend
cd docker-nodejs-backend
npm init -y
npm install expressindex.js fileconst express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});node index.js to test if it works.Dockerfile in the root of your project with below content. Read my comment in the file to understand what it does.FROM node:20-alpine # Base image - Alpine Linux with Node.js 20
WORKDIR /app # Working directory (like mkdir -p /app && cd /app)
COPY package.json . # Copy package.json to container
COPY index.js . # Copy index.js to container
RUN npm install # Install dependencies
EXPOSE 3000 # Expose port 3000 (optional but recommended)
CMD ["node", "index.js"] # Command to run your appsudo docker build -t docker-nodejs-backend .-t flag tags the container (think of it as the name). The . is the build context - it tells Docker to find the Dockerfile in the current directory.sudo docker run -p 3000:3000 docker-nodejs-backendhttp://localhost:3000That’s it! Now you can add “Docker Master” to your CV. :))))
My confession: I now always ask AI to write Dockerfiles for me. :))))
Dockerfile creates Docker images.-p 3000:3000 maps host port 3000 to container port 3000.docker network create my-network and connect containers with docker network connect my-network my-container.docker run: Run a container from an imagedocker build: Build an image from a Dockerfiledocker images: List all imagesdocker ps: List running containers (docker ps -a for all containers)docker stop/start/restart <container-id>: Control container lifecycledocker rm <container-id>: Remove a containerdocker rmi <image-id>: Remove an imagedocker pull/push <image-name>: Pull/push images from/to registrydocker exec <container-id> <command>: Execute command in running containerdocker logs <container-id>: View container logsdocker inspect <container-id>: Inspect container detailsdocker stats <container-id>: Show container resource usagesudo docker run -p 3000:3000 -v /path/to/host:/path/to/container docker-nodejs-backenddocker-compose.yml file:version: '3.8'
services:
redis:
image: redis:alpine
ports:
- "6379:6379"
networks:
- app-network
database:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
volumes:
- db-data:/var/lib/postgresql/data
networks:
- app-network
backend:
build: ./backend
ports:
- "3000:3000"
volumes:
- ./backend:/app
depends_on:
- redis
- database
environment:
DATABASE_URL: postgresql://user:password@database:5432/mydb
REDIS_URL: redis://redis:6379
networks:
- app-network
frontend:
build: ./frontend
ports:
- "8080:80"
depends_on:
- backend
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
db-data:docker-compose up -ddocker-compose downdocker-compose logs -fdocker-compose psdocker compose instead of docker-compose.Each Dockerfile instruction creates a cached layer. I copy package.json and run npm install before copying source code because if only source code changes (not dependencies), Docker won’t reinstall packages, saving build time.
Optimize image size by using multiple FROM instructions. For example, build a React app in one stage, then copy the built files to a smaller nginx image for production.
.dockerignore to exclude unnecessary fileslatest# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files first for layer caching
COPY package.json package-lock.json ./
RUN npm ci
# Copy source code and build
COPY . .
RUN npm run build
# Runtime stage
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]✅ What we covered:
This blog covers Docker basics and common features. Docker has many advanced features not covered here - check the Docker documentation for more. While Docker introduces many new concepts, it’s not hard once you understand the fundamentals. The best way to learn is to install Docker and practice, not just read blogs!
| ← Previous Chapter
🚀 A noob guy deploy his web app | DevOps Series
Chapter 3 of 16 | Next Chapter →
☸️ K8s in a nutshell |