
Docker: 내 컴퓨터에서는 되는데 왜 서버에서는 안 될까? (완벽 가이드)
컨테이너 기술의 바이블. 리눅스 커널 심층 분석(Namespaces, Cgroups)부터 프로덕션 배포를 위한 보안 하드닝, 트러블슈팅, 그리고 용어 사전까지.

컨테이너 기술의 바이블. 리눅스 커널 심층 분석(Namespaces, Cgroups)부터 프로덕션 배포를 위한 보안 하드닝, 트러블슈팅, 그리고 용어 사전까지.
운영체제라는게 사실 프로그램들의 집합이라면, 그 중에서도 가장 핵심이 되는 녀석은 누구일까요? 항상 메모리에 상주하는 커널의 정체.

서버를 끄지 않고 배포하는 법. 롤링, 카나리, 블루-그린의 차이점과 실제 구축 전략. DB 마이그레이션의 난제(팽창-수축 패턴)와 AWS CodeDeploy 활용법까지 심층 분석합니다.

CPU는 하나인데 프로그램은 수십 개 실행됩니다. 운영체제가 0.01초 단위로 프로세스를 교체하며 '동시 실행'의 환상을 만드는 마술. 선점형 vs 비선점형, 기아 현상(Starvation), 그리고 현대적 해법인 MLFQ를 파헤칩니다.

새벽엔 낭비하고 점심엔 터지는 서버 문제 해결기. '택시 배차'와 '피자 배달' 비유로 알아보는 오토 스케일링과 서버리스의 차이, 그리고 Spot Instance를 활용한 비용 절감 꿀팁.

도커를 배우게 된 계기는 단순했다. 로컬에서는 완벽하게 돌아가던 Node.js 앱이 서버에 올리자마자 에러를 뿜는 경험이었다. "ModuleNotFoundError: No module named 'bcrypt'"
문제는 간단했다. 내 맥북에서는 Python 3.9였고, 서버는 3.7이었다. OpenSSL 버전도 달랐고, Node.js 버전도 미묘하게 달랐다. 서버 환경을 맞추느라 apt-get install을 수십 번 쳤고, 그제야 "의존성 지옥(Dependency Hell)"이 무엇인지 이해했다.
그 후 도커를 만났다. 솔로몬 하익스(Solomon Hykes)가 2013년 PyCon US에서 "The Future of Linux Containers"를 발표했을 때, 나는 그 영상을 5번 돌려봤다. 이 사람이 풀려는 문제가 정확히 내 문제였기 때문이다. "내 컴퓨터에서는 되는데"라는 핑계가 더 이상 통하지 않는 세상. 그게 도커가 약속한 미래였다.
이 글은 그때부터 지금까지 도커를 파고들며 정리해본 나만의 학습 노트다.
처음엔 단순하게 생각했다. "환경을 통째로 복사하면 되잖아? VirtualBox로 VM 이미지 만들어서 배포하면 되는 거 아니야?" 그런데 현실은 달랐다.
VM의 한계를 받아들였다:그러다 컨테이너 개념을 접했을 때 와닿았다. "아, 커널은 공유하고 프로세스만 격리하는 거구나." 가볍고, 빠르고, 효율적이었다. 도커는 이 아이디어를 개발자들이 쓸 수 있는 형태로 포장한 것이었다.
가장 큰 오해를 풀었던 순간은 도커 공식 문서의 이 문장이었다:
"A container is just a process on the host machine."
컨테이너는 그냥 프로세스다. 특별한 격리 기술로 감싼 프로세스. 이 사실을 이해하고 나니 모든 게 명확해졌다. 도커가 사용하는 기술 3가지를 정리해본다.
PID Namespace가 가장 먼저 이해됐다. 컨테이너 안에서 ps aux를 치면 내 프로세스만 보인다. 심지어 그 프로세스가 PID 1이다. 호스트에서는 같은 프로세스가 PID 5432일지도 모르는데 말이다.
NET Namespace는 네트워크 디버깅하면서 깨달았다. 컨테이너마다 eth0 인터페이스를 가진다. IP도 따로다. 하나의 물리 머신에서 수백 개의 가상 네트워크 인터페이스가 돌아가는 구조였다.
MNT, IPC, UTS, USER Namespace까지 총 6가지. 이걸 다 조합하면 완벽한 격리 환경이 만들어진다. 리눅스 커널 개발자들의 천재성이 느껴지는 순간이었다.
실제로 컨테이너 하나가 서버를 다운시키는 걸 본 적 있다. 메모리 릭이 있는 Node.js 앱이 호스트 메모리 16GB를 다 먹어치웠고, 결국 OOM Killer가 작동해서 전체 서버가 먹통이 됐다.
그 사건 이후 Cgroups의 중요성을 뼈저리게 이해했다. CPU 쿼터, 메모리 리미트, 디스크 I/O 제한. 이게 없으면 컨테이너는 그냥 위험한 폭탄이다.
# 이제 항상 이렇게 띄운다
docker run -d --memory="512m" --cpus="0.5" my-app
100개의 컨테이너를 띄워도 디스크를 거의 안 먹는 비밀이 바로 OverlayFS였다. Copy-on-Write 전략. 베이스 이미지 레이어는 읽기 전용으로 공유하고, 각 컨테이너가 변경하는 부분만 별도 레이어에 기록한다.
이 구조를 이해하고 나니 Dockerfile 최적화 전략도 명확해졌다. 자주 바뀌는 레이어(소스 코드)는 가장 아래로, 거의 안 바뀌는 레이어(베이스 이미지, 패키지)는 위로. 캐시 히트율이 올라가면 빌드 속도가 크게 개선된다고 한다.
도커가 단일 바이너리인 줄 알았다. 그런데 실제로는 여러 컴포넌트의 오케스트라였다.
docker run nginx를 치면 내부적으로 이런 일이 벌어진다는 걸 결국 이거였다 수준으로 이해했다:
docker CLI): 사용자 명령을 받아서 REST API로 변환한다.dockerd): API 요청을 받아서 이미지가 로컬에 있는지 확인한다. 없으면 Docker Hub에서 pull한다.이 구조를 알고 나니 쿠버네티스가 왜 Docker Shim을 제거하고 containerd를 직접 쓰기로 했는지도 이해됐다. Docker Daemon은 개발자 경험(DX)을 위한 래퍼였고, 프로덕션 오케스트레이션에는 불필요한 레이어였던 것이다.
이론은 알았다. 이제 실제이다. 처음 짠 Dockerfile은 형편없었다.
# 나쁜 예시 (초보 시절의 나)
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y nodejs
RUN apt-get install -y npm
COPY . /app
WORKDIR /app
RUN npm install
CMD node server.js
문제가 한두 가지가 아니었다:
ubuntu:latest: 300MB나 되는 비대한 이미지RUN을 3번 나눔: 레이어가 불필요하게 많아짐COPY . /app을 먼저 함: 소스 코드만 바뀌어도 npm install이 다시 돌아감이걸 몇 달에 걸쳐 개선했다. 현재 내가 쓰는 템플릿은 이렇다:
# 1단계 - 빌드 (멀티 스테이지)
FROM node:18-alpine AS builder
# 작업 디렉토리
WORKDIR /app
# 의존성 파일만 먼저 복사 (캐시 최적화)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 소스 코드 복사
COPY . .
# 2단계 - 프로덕션 (Distroless 같은 최소 이미지)
FROM node:18-alpine
# 비root 유저 생성
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# 빌더 스테이지에서 결과물만 복사
COPY --from=builder /app .
# 유저 전환 (보안)
USER appuser
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
# Exec form 사용 (PID 1 문제 방지)
CMD ["node", "server.js"]
이 Dockerfile의 빌드 시간은 첫 번째 버전 대비 70% 줄었고, 이미지 크기는 300MB에서 80MB로 감소했다.
도커 네트워크는 처음에 정말 헷갈렸다. "왜 localhost가 안 되지?"라는 의문으로 하루를 날린 적도 있다.
컨테이너 안에서 curl localhost:3000을 치면 컨테이너 자기 자신의 localhost를 본다. 호스트의 localhost가 아니다. 이 개념을 받아들이는 데 시간이 좀 걸렸다.
해결책은 커스텀 브리지 네트워크였다:
# 네트워크 생성
docker network create backend-net
# Redis 띄우기
docker run -d --name redis --net backend-net redis:alpine
# App 띄우기 (Redis에 연결)
docker run -d --name app --net backend-net \
-e REDIS_URL=redis://redis:6379 \
my-node-app
같은 네트워크에 있으면 컨테이너 이름으로 DNS 해석이 된다. redis:6379로 바로 접근 가능. 이게 프로덕션에서 마이크로서비스 간 통신의 기본이다.
개발 중이던 PostgreSQL 컨테이너를 실수로 docker rm -f db로 날렸다. 며칠치 테스트 데이터가 순식간에 증발했다. 그때 배운 게 Volume이다.
# Named Volume 생성
docker volume create pgdata
# DB 실행 (볼륨 마운트)
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:14
# 컨테이너를 지워도...
docker rm -f db
# 볼륨은 남아있다
docker volume ls # pgdata 존재
# 다시 같은 볼륨으로 띄우면 데이터 복구
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:14
컨테이너는 임시적이고, Volume은 영속적이다. 이 원칙을 머리가 아니라 몸으로 배웠다.
이론으로는 알 수 없는 것들. 실제로 배포하고, 장애 겪고, 새벽에 깨어나서 고치면서 배운 것들을 정리한다.
| 상황 | 증상 | 삽질 끝에 찾은 원인 | 해결책 |
|---|---|---|---|
| 빌드 중 의존성 에러 | ERROR: Unable to locate package python3 | Alpine 리눅스는 apt-get이 아니라 apk 사용 | RUN apk add --no-cache python3 |
| 컨테이너 시작 후 즉시 종료 | docker ps에 안 보임 | CMD가 백그라운드 프로세스(&)로 실행됨 | Foreground로 실행되도록 수정 |
| localhost 연결 실패 | ECONNREFUSED 127.0.0.1:5432 | 컨테이너의 localhost는 호스트와 다름 | 맥에서는 host.docker.internal 사용 |
| 디스크 풀 | No space left on device | 댕글링 이미지가 50GB 차지 | docker system prune -a --volumes |
| SIGTERM 무시 | 컨테이너가 graceful shutdown 안 됨 | PID 1이 쉘 스크립트라 시그널 전파 안 됨 | exec 사용 또는 tini init 프로세스 추가 |
특히 마지막 PID 1 문제는 프로덕션에서 롤링 업데이트 시 커넥션이 끊기는 원인이었다. CMD ["node", "server.js"] 형태의 exec form을 쓰면 node가 직접 PID 1이 되어서 SIGTERM을 제대로 받는다.
프로덕션에 처음 올렸을 때 보안팀에서 컨테이너를 스캔하고 나서 벌건 리포트를 들이밀었다. "Critical 취약점 23개"라고 적혀 있었다. 부끄러웠다.
CIS Docker Benchmark를 하나하나 적용하면서 배운 원칙들:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
docker run -d \
--memory="1g" \
--cpus="1.0" \
--pids-limit=100 \
my-app
docker run -d --read-only \
--tmpfs /tmp \
my-app
해커가 침투해도 파일을 수정 못 한다.
docker run -d \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
my-app
# Trivy로 스캔
trivy image my-app:latest
이걸 CI/CD 파이프라인에 넣어서 Critical CVE가 있으면 배포를 자동으로 막는다.
기술을 깊이 이해하려면 역사를 알아야 한다고 믿는다. 도커의 타임라인을 정리하며 많은 걸 배웠다.
이 과정에서 느낀 건 "표준의 힘"이다. OCI 덕분에 Podman, Buildah 같은 대안들이 나올 수 있었고, 쿠버네티스는 도커 없이도 돌아갈 수 있게 됐다.
컨테이너는 커널을 공유하고, VM은 커널을 각자 띄운다. 그래서 컨테이너가 가볍다. 하지만 완전한 격리는 VM이 더 강하다.
Writable Layer는 임시적이다. 중요한 데이터는 반드시 Volume이나 Bind Mount로 호스트에 저장해야 한다.
Alpine 베이스 이미지(5MB), Multi-stage build, .dockerignore 파일 활용, 레이어 최적화가 핵심이다.
도커는 벽돌(컨테이너)을 만드는 도구, 쿠버네티스는 그 벽돌로 빌딩(클러스터)을 짓는 도구다. 이제 쿠버네티스는 도커 없이도 돌아간다(containerd).
컨테이너의 네트워크 Namespace가 격리돼 있어서다. 맥/윈도우에서는 host.docker.internal을 쓰고, 리눅스에서는 --network host 옵션을 쓰면 된다.
COPY는 로컬 파일 복사만 한다. ADD는 URL 다운로드와 tar 자동 압축 해제까지 한다. 명확성을 위해 COPY 사용을 권장한다.
쉘 스크립트가 PID 1이 되면 SIGTERM을 자식 프로세스에게 전파하지 않는다. exec 키워드를 쓰거나 tini 같은 init 프로세스를 쓰면 해결된다.
Docker Engine은 오픈소스이고 무료다. Docker Desktop은 대기업에게는 유료지만 개인, 교육 기관, 중소기업에게는 무료다.
같은 네트워크에 있으면 IP로 통신 가능하다. 커스텀 브리지 네트워크를 만들면 컨테이너 이름으로 DNS 해석이 되어 더 편하다.
기본 설정은 안전하지 않다. Non-root 유저 사용, Capability 제한, Read-only 파일시스템, 리소스 제한, 이미지 스캔 등의 보안 하드닝이 필수다.
손에 익혀야 하는 것들:
| 명령어 | 설명 |
|---|---|
docker ps -a | 모든 컨테이너 목록 (실행 중 + 중지됨) |
docker logs -f <id> | 컨테이너 로그 실시간 추적 |
docker exec -it <id> /bin/sh | 실행 중인 컨테이너에 쉘 접속 |
docker build -t <tag> . | 현재 디렉토리의 Dockerfile로 이미지 빌드 |
docker system prune -a | 사용하지 않는 이미지, 컨테이너, 네트워크, 볼륨 전부 삭제 |
docker inspect <id> | 컨테이너/이미지 상세 정보 JSON 출력 |
docker stats | 실시간 CPU/메모리 사용량 모니터링 |
docker-compose up -d | docker-compose.yml로 스택 백그라운드 실행 |
docker volume ls | 볼륨 목록 확인 |
docker network inspect bridge | 네트워크 상세 정보 (IP 할당 등) 확인 |
latest, v1.2.3).dockerd).docker 명령어).FROM을 써서 이미지 크기를 줄이는 기법.회사에서 보안팀이 "도커 데몬이 root 권한으로 돌아가는 게 위험하다"며 Podman 도입을 검토했다.
Docker:
dockerd가 항상 떠 있어야 함)Podman (RedHat):
alias docker=podman)결론은 "개발 환경은 Docker Desktop, 프로덕션 K8s는 containerd, 보안이 극도로 중요한 환경은 Podman" 이렇게 정리했다.
도커를 배우면서 가장 중요하게 이해한 것은 "도커 자체"가 아니라 "컨테이너 사고방식"이었다. 애플리케이션을 격리하고, 불변 인프라로 다루고, 선언적으로 정의하는 철학.
이제 "내 컴퓨터에서는 되는데"라는 핑계는 통하지 않는다. Dockerfile과 docker-compose.yml만 있으면 누구나 동일한 환경을 재현할 수 있다.
앞으로는 WebAssembly(Wasm)가 특정 워크로드에서 컨테이너를 대체할지도 모른다. 하지만 "환경을 코드로 정의한다"는 핵심 아이디어는 절대 사라지지 않을 것이다.
이 글이 누군가의 새벽 3시 배포 실패를 막아주길 바란다.
My motivation for learning Docker was simple. A Node.js app that ran flawlessly on my local machine started throwing errors the moment it hit the server — "ModuleNotFoundError: No module named 'bcrypt'".
The problem was straightforward. My MacBook had Python 3.9; the server had 3.7. Different OpenSSL versions. Slightly mismatched Node.js versions. I spent hours installing and reinstalling dependencies, manually compiling native modules. That was when I truly understood what "Dependency Hell" meant.
The following week, I discovered Docker. I watched Solomon Hykes' 2013 PyCon talk "The Future of Linux Containers" five times in a row. This man was solving my exact problem. A world where "It works on my machine" would no longer be an excuse. That was Docker's promise.
This article is my personal learning journal from that moment to now.
My first instinct was simple: "Just copy the entire environment. Package a VirtualBox VM and ship that." But reality had other plans.
I learned VM limitations the hard way:When I encountered the concept of containers, it clicked instantly. "Oh, you share the kernel but isolate the processes." Lightweight, fast, efficient. Docker packaged this idea into something developers could actually use.
The biggest misconception I cleared up came from this sentence in the Docker docs:
"A container is just a process on the host machine."
Containers are just processes. Special processes wrapped in isolation technology. Once I internalized this, everything became crystal clear. Here are the three core Linux technologies Docker uses.
PID Namespace was the easiest to grasp. When you run ps aux inside a container, you only see your own processes. Your main process is even PID 1. On the host, that same process might be PID 5432.
NET Namespace became obvious while debugging networking. Each container gets its own eth0 interface. Its own IP. A single physical machine can run hundreds of virtual network interfaces.
There are six total: PID, NET, MNT, IPC, UTS, USER. Combined, they create perfect isolation. I was in awe of the Linux kernel developers' genius.
I once witnessed a single container crash an entire server. A Node.js app with a memory leak consumed all 16GB of host RAM. The OOM Killer eventually kicked in and took down everything.
After that incident, I deeply understood the importance of Cgroups. CPU quotas, memory limits, disk I/O throttling. Without these, containers are dangerous.
# Now I always run containers like this
docker run -d --memory="512m" --cpus="0.5" my-app
Running 100 containers barely consumes disk space. The secret? OverlayFS with Copy-on-Write. Base image layers are read-only and shared. Each container only writes its changes to a separate layer.
Once I understood this structure, Dockerfile optimization strategies became obvious. Put frequently changing layers (source code) at the bottom. Put rarely changing layers (base images, packages) at the top. Cache hit rates go up significantly, and build times are said to improve dramatically as a result.
I initially thought Docker was a single binary. Turns out, it's an orchestra of components working together.
When you type docker run nginx, here's what happens under the hood:
docker CLI): Converts your command into a REST API call.dockerd): Receives the API request, checks if the image exists locally. If not, pulls from Docker Hub.Understanding this architecture clarified why Kubernetes removed Docker Shim and now talks directly to containerd. Docker Daemon was a wrapper for developer experience (DX), an unnecessary layer for production orchestration.
Theory is good. Practice is everything. My first Dockerfile was terrible.
# Bad Example (Beginner Me)
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y nodejs
RUN apt-get install -y npm
COPY . /app
WORKDIR /app
RUN npm install
CMD node server.js
Problems everywhere:
ubuntu:latest: 300MB bloated imageRUN commands: unnecessary layersCOPY . /app too early: any source code change invalidates the npm install cacheI spent months refining this. Here's my current production template:
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
# Copy dependency files first (cache optimization)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Stage 2: Production
FROM node:18-alpine
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copy artifacts from builder
COPY --from=builder /app .
# Switch to non-root user
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
# Exec form (prevents PID 1 issues)
CMD ["node", "server.js"]
This Dockerfile reduced build time by 70% and image size from 300MB to 80MB.
Docker networking confused me initially. I burned an entire day wondering "Why doesn't localhost work?"
Inside a container, curl localhost:3000 refers to the container's own localhost, not the host's. This concept took time to internalize.
The solution: custom bridge networks.
# Create network
docker network create backend-net
# Run Redis
docker run -d --name redis --net backend-net redis:alpine
# Run app (connects to Redis)
docker run -d --name app --net backend-net \
-e REDIS_URL=redis://redis:6379 \
my-node-app
Containers on the same network resolve each other by name via DNS. redis:6379 just works. This is the foundation of microservice communication in production.
I accidentally ran docker rm -f db and deleted my PostgreSQL container mid-development. Days of test data evaporated instantly. That's when I learned about Volumes.
# Create named volume
docker volume create pgdata
# Run DB with volume mount
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:14
# Even if you delete the container...
docker rm -f db
# The volume survives
docker volume ls # pgdata exists
# Restart with same volume, data restored
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:14
Containers are ephemeral. Volumes are persistent. I learned this principle not with my head, but with my hands.
Theory doesn't teach you these. You learn them by deploying, failing, and fixing at 3 AM.
| Situation | Symptoms | Root Cause After Hours of Debugging | Solution |
|---|---|---|---|
| Build Dependency Error | ERROR: Unable to locate package python3 | Alpine uses apk, not apt-get | RUN apk add --no-cache python3 |
| Container Exits Immediately | Doesn't show in docker ps | CMD runs process in background (&) | Run process in foreground |
| Localhost Connection Refused | ECONNREFUSED 127.0.0.1:5432 | Container's localhost ≠ host's localhost | Use host.docker.internal on Mac/Windows |
| Disk Full | No space left on device | Dangling images consuming 50GB | docker system prune -a --volumes |
| SIGTERM Ignored | Container doesn't gracefully shutdown | PID 1 is shell script, doesn't propagate signals | Use exec or add tini init process |
The last PID 1 problem caused connection drops during rolling updates in production. Using exec form CMD ["node", "server.js"] makes node PID 1, properly receiving SIGTERM.
When I first deployed to production, the security team scanned my containers and handed me a blood-red report: "23 Critical Vulnerabilities." I was mortified.
I went through the CIS Docker Benchmark line by line. Here's what I learned:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
docker run -d \
--memory="1g" \
--cpus="1.0" \
--pids-limit=100 \
my-app
docker run -d --read-only \
--tmpfs /tmp \
my-app
If attackers breach, they can't modify files.
docker run -d \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
my-app
# Scan with Trivy
trivy image my-app:latest
We integrated this into CI/CD to automatically block deployment if Critical CVEs are found.
To deeply understand technology, you must know its history. Here's Docker's timeline and what I learned from it:
The lesson: the power of standards. OCI enabled alternatives like Podman and Buildah. Kubernetes could run without Docker thanks to standardization.
Containers share the kernel; VMs each run their own kernel. That's why containers are lighter. But VMs provide stronger isolation.
The writable layer is ephemeral. Critical data must be stored in Volumes or Bind Mounts on the host.
Alpine base images (5MB), multi-stage builds, .dockerignore files, and layer optimization.
Docker builds the bricks (containers). Kubernetes builds the building (cluster). Kubernetes now runs without Docker (using containerd).
Network namespaces are isolated. On Mac/Windows, use host.docker.internal. On Linux, use --network host.
COPY only copies local files. ADD can fetch URLs and auto-extract tars. Use COPY for clarity.
If a shell script becomes PID 1, it doesn't forward SIGTERM to child processes. Use exec keyword or an init process like tini.
Docker Engine is open source and free. Docker Desktop requires paid subscription for large enterprises but is free for individuals, education, and small businesses.
If on the same network, they can communicate by IP. Custom bridge networks enable DNS resolution by container name.
Default settings are not secure. You must harden: non-root users, capability restrictions, read-only filesystems, resource limits, and image scanning.
Commands you need muscle memory for:
| Command | Description |
|---|---|
docker ps -a | List all containers (running + stopped) |
docker logs -f <id> | Tail container logs in real-time |
docker exec -it <id> /bin/sh | Shell into a running container |
docker build -t <tag> . | Build image from Dockerfile in current directory |
docker system prune -a | Delete unused images, containers, networks, volumes |
docker inspect <id> | View detailed JSON metadata (IP, mounts, env vars) |
docker stats | Live CPU/RAM usage monitoring |
docker-compose up -d | Start stack in background via docker-compose.yml |
docker volume ls | List persistent volumes |
docker network inspect bridge | Debug networking, see IP allocations |
latest, v1.2.3).dockerd).docker command).FROM instructions in one Dockerfile to reduce size.Our security team raised concerns about the Docker daemon running with root privileges and proposed evaluating Podman.
Docker:
dockerd must always run)Podman (RedHat):
alias docker=podman)Our conclusion: "Docker Desktop for dev environments, containerd for production K8s, Podman for extremely security-sensitive environments."