In the era of cloud-native systems, the famous developer phrase “But it works on my machine!” has been rendered obsolete. Modern application deployment relies on consistency, predictability, and horizontal scalability.

This is where Docker and Kubernetes step in. Docker provides a standardized packaging format for our applications (containerization), while Kubernetes manages how these containers run, communicate, and scale across clusters of servers (orchestration). Let’s review the fundamental pipeline to containerize and orchestrate a microservice from scratch.


1. Packaging with Docker: The Dockerfile

A Docker container is a running instance of a static Docker Image. We declare how our image should be built using a text file named Dockerfile.

Here is an optimized, multi-stage build configuration for a Node.js application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Stage 1: Build dependency environment
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production runtime env
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist

EXPOSE 3000
CMD ["node", "dist/main.js"]

Multi-stage builds allow us to compile the codebase in a rich build environment, and then copy only the compiled static binaries into a tiny, secure production runtime base image, keeping our deployment images incredibly lightweight.


2. Orchestration with Kubernetes: Deployment Configs

Once our image is built and pushed to a container registry, we need Kubernetes to manage its lifecycle. We communicate with Kubernetes using declarative YAML configurations.

Below is a standard Deployment configuration that spins up 3 replicated Pods of our container:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-service-deployment
labels:
app: node-service
spec:
replicas: 3 # Running 3 identical containers for high availability
selector:
matchLabels:
app: node-service
template:
metadata:
labels:
app: node-service
spec:
containers:
- name: node-service-container
image: custom-registry/node-service:v1.0.0
ports:
- containerPort: 3000
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "250m"
memory: "256Mi"

Applying this configuration with kubectl apply -f deployment.yaml instructs Kubernetes to dynamically allocate the containers across healthy servers, perform health checks, and automatically restart container instances if they crash.