Practical introduction to Docker/Kubernetes

Practical introduction to Docker/Kubernetes

The sweet trap of saying “Docker will work”. My record of going through hell when migrating to Kubernetes

"If you containerize it, it will work anywhere. That's infrastructure justice."

A few months after adopting Docker with such bright ideals, I found myself on the brink of despair.
"It worked locally, but for some reason I can't connect to the DB in the staging environment." "The image size is so huge that it takes 10 minutes to build." "The moment I considered migrating to Kubernetes (K8s), my brain almost melted due to the complexity of the network specifications."

This time, I will write down what I learned as I was exposed to the turbulent waters of container technology and orchestration, ``the real world where things don't go according to textbooks''. This is a collection of lessons learned from a roundabout experience for those who are about to try Docker or K8s.


1. Docker pitfalls: Why just “working” is not enough

The first mistake many people make is thinking, "I just need to write a Dockerfile, right?" The container does indeed work. However, when it comes to operating practical applications, the design philosophy of ``how to create a clean and efficient image'' is called into question.

#### The trap of bloated images and layers
Initially, when I Dockerized a Node.js app, I simply wrote a Dockerfile that just executed `npm install`. As a result, the image size exceeded 1GB, and the cache was not working every time a build was made, resulting in a wait of several minutes.

The cause was that I did not fully understand the mechanism of ``Layer''.
Docker creates a new layer each time it executes each instruction (RUN, COPY, ADD, etc.). If you simply run `COPY . This is the cause of increased build time and bloated images.

Multi-stage Build is essential to solving this problem.
By separating the build environment (which includes build tools and compilers) from the lightweight execution environment (such as Alpine or Distroless), you can eliminate unnecessary elements and improve security.

Below is a comparison example of a Dockerfile that I improved from a "just working" configuration to a "production-ready" configuration.

# --- Bad example (configuration that beginners tend to do) ---
FROM node:18
WORKDIR /app
COPY.
RUN npm install
EXPOSE 3000
CMD ["node", "src/index.js"]

# --- Good example (using Multi-stage Build) ---
# Build Stage: An environment that provides all the tools necessary for building
FROM node:18-alpine AS builder
WORKDIR /app
# Copy only dependencies first. This allows caching to work as long as package.json does not change.
COPY package*.json ./
RUN npm install
COPY.
RUN npm run build

# Production Stage: An environment that leaves only the minimum elements necessary for execution
FROM node:18-alpine
WORKDIR /app
# Copy only the necessary artifacts from the builder stage
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

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

In this way, just being aware of ``what to leave in that layer'' can dramatically change the speed of CI/CD and the stability of deployment. Also, it is almost good manners to set `.dockerignore` appropriately so that local `node_modules` and `.git` directories are not included in the image.


2. Migration to Kubernetes: This is where the real hell begins

When migrating an app that was running on Docker Compose to Kubernetes, many developers make the mistake of thinking that they are just writing more YAML. But K8s is more than just a container management tool. It is a huge abstraction layer for controlling distributed systems.

The three things that particularly bothered me were:

#### ① Network abstraction (Service, Ingress)
With Docker Compose, it was enough to communicate simply by specifying "localhost" or "service name," but with K8s, it is necessary to secure a stable endpoint through the concept of Service.
Furthermore, when incorporating Ingress settings for external publication (Nginx Ingress Controller, etc.), everything that would normally have been done with web server settings, such as path rewriting and SSL certificate handling, is incorporated into the K8s resource definition. If you proceed without understanding this layer of abstraction, you will be lost in a debugging labyrinth without being able to identify where communication is being interrupted.

#### ② State management (StatefulSet vs Deployment)
The adage "don't put your DB in a container" becomes a more serious problem in the world of K8s.
Pods are Ephemeral, and their contents disappear when the node is restarted due to failure or update. If you do not know this and try to run a database without properly setting Persistent Volume (PV) or Persistent Volume Claim (PVC), you will experience the fear of data loss every time you reboot. It is essential to appropriately use mechanisms such as StatefulSet to run stateful applications.

#### ③ Separation of settings (ConfigMap, Secret)
The basic idea is to use `ConfigMap` and `Secret` rather than directly pouring values into environment variables, but I am often confused by the different behavior from Docker Compose, such as the handling of "types" (especially whether or not to use Base64 encoding) and the timing of reflection when updating.


3. Practical comparison: Why do we need Kubernetes?

Here, I summarize in a table the differences in operational philosophy between the development environment and the production environment, which I have sorted out while thinking about it. Understanding this difference is the first step to a smooth transition to K8s.

ItemDocker / Docker Compose (Local/Dev)Kubernetes (Production/Scale)
ScalingManually or simply specify the number of replicasAutoscaling with HPA (Horizontal Pod Autoscaler)
Self-healingOnly restarts the container when it stopsAutomatically regenerates and replaces Pods by detecting anomaly
Service DiscoveryName resolution (DNS) is performed within a simple networkAdvanced name resolution and LoadBalancer integration via CoreDNS
StorageDirect persistence via host mountDynamic volume allocation using PV/PVC, StorageClass
Configuration ManagementOften written directly in YAML filesSeparation of ConfigMap/Secret and dynamic injection of Config

4. Finally: Advice for overcoming setbacks

The world of Kubernetes is huge in the broad sense of the word, and trying to understand everything perfectly from the beginning will inevitably lead to frustration. The biggest reason I failed was because I tried to ``make everything all at once.''

If you are planning to try K8s, I highly recommend following the steps below.

1. First, thoroughly solidify the basics of Docker.
(Multi-stage Build, .dockerignore, selecting a lightweight base image, etc.)
2. Using Docker Compose in the local environment.
Get a feel for how the concepts of networks and volumes work with a single container.
3. Build a small K8s cluster using Minikube or Kind.
Rather than jumping into a cloud managed service (EKS/GKE, etc.) from the beginning, manually define "Service", "Ingress", and "ConfigMap" locally one by one and do some trial and error.

If you proceed while understanding the intention behind the technology, ``Why is this mechanism necessary?'', K8s should become a very powerful weapon.

At first, you'll be disappointed by the confusing error messages. However, the real pleasure of learning about this technology stack is the feeling of deepening your understanding of the infrastructure structure each time you resolve each error.

Beyond "moving," pursue "design for continued stable movement." That's the essence of containers and orchestration.


🤖 Developer blog/public content

The developer's personal blog is also available, so please take a look. muttyan.com blog

Amazon great free trial/benefits summary