Skip to content

Docker Interview Questions

Docker Interview Questions & Answers (1–60)

Section titled “Docker Interview Questions & Answers (1–60)”

Q1: What is Docker and how does it differ from a Virtual Machine?

Section titled “Q1: What is Docker and how does it differ from a Virtual Machine?”

Answer: Docker is an open-source platform that packages applications and their dependencies into lightweight, portable containers.

AspectDocker (Container)Virtual Machine
OSShares host OS kernelFull guest OS
SizeMBsGBs
StartupSecondsMinutes
IsolationProcess-level (namespaces)Hardware-level (hypervisor)
PerformanceNear-native5-15% overhead
Portability✅ Run anywhere Docker runs❌ Hypervisor-dependent
Use caseMicroservices, CI/CDFull OS isolation, legacy apps
VM Architecture: Container Architecture:
┌─────────────┐ ┌────────┬────────┐
│ App A │ │ App A │ App B │
│ Libs │ │ Libs │ Libs │
│ Guest OS │ ├────────┴────────┤
├─────────────┤ │ Docker Engine │
│ Hypervisor │ ├─────────────────┤
├─────────────┤ │ Host OS │
│ Host OS │ └─────────────────┘
└─────────────┘

Key Points:

  • Containers share the host OS kernel — no guest OS needed
  • Docker uses Linux namespaces (isolation) and cgroups (resource limits)
  • Containers start in seconds; VMs take minutes

Interview tip: “Docker is not a full VM — it’s a process on your host with isolated filesystem, network, and PID namespace. The host kernel is shared.”


Q2: What are Docker images and containers?

Section titled “Q2: What are Docker images and containers?”

Answer:

Docker ImageDocker Container
WhatRead-only blueprint/templateRunning instance of an image
StateStatic (immutable)Has mutable read-write layer
Created bydocker builddocker run
StoredRegistry or local diskLocal only
AnalogyClass (OOP)Object/Instance (OOP)
Terminal window
# Image lifecycle
docker pull nginx:alpine # Download from registry
docker images # List local images
docker rmi nginx:alpine # Remove image
docker image prune # Remove unused images
# Container lifecycle
docker run -d --name my-nginx -p 80:80 nginx:alpine # Create + start
docker stop my-nginx # Stop (sends SIGTERM)
docker start my-nginx # Restart stopped container
docker rm my-nginx # Delete stopped container
docker rm -f my-nginx # Force delete running container
# Inspect
docker ps # Running containers
docker ps -a # All containers (including stopped)
docker inspect my-nginx # Full details (IP, mounts, env)

Common mistake: docker rmi removes the IMAGE but not containers. docker rm removes CONTAINERS. Use docker system prune to clean everything.


Q3: What is a Dockerfile and what are common instructions?

Section titled “Q3: What is a Dockerfile and what are common instructions?”

Answer: A Dockerfile is a text file with instructions to build a Docker image. Each instruction creates a new layer.

# ---- Production-ready Dockerfile example ----
# Base image (always pin a specific version)
FROM node:18-alpine
# Set working directory (creates dir if not exists)
WORKDIR /app
# Copy package files FIRST (layer cache optimization)
COPY package*.json ./
# Install dependencies
RUN npm ci --omit=dev
# Copy source code AFTER install (changes more often)
COPY . .
# Build-time argument (not available at runtime)
ARG GIT_COMMIT=unknown
LABEL git-commit=$GIT_COMMIT
# Runtime environment variable
ENV NODE_ENV=production
# Create non-root user (security best practice)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# Document the port (does NOT publish it)
EXPOSE 3000
# Define volume mount point
VOLUME ["/app/data"]
# Health check
HEALTHCHECK --interval=30s --timeout=5s \
CMD wget -qO- http://localhost:3000/health || exit 1
# Default command when container starts
CMD ["node", "server.js"]

Common Instructions:

InstructionRuns atPurposeCreates layer?
FROMBuildBase image✅ Yes
RUNBuildExecute shell command✅ Yes
COPYBuildCopy files from host✅ Yes
ADDBuildLike COPY + URL + tar extract✅ Yes
WORKDIRBuildSet working directory✅ Yes
ENVBuild + RuntimeSet environment variable✅ Yes
ARGBuild onlyBuild-time variable❌ No
EXPOSEDocumentationDocument port (doesn’t publish)❌ No
CMDRuntimeDefault command (overrideable)❌ No
ENTRYPOINTRuntimeFixed executable❌ No
USERBuild + RuntimeSwitch to this user❌ No
VOLUMERuntimeMount point❌ No
HEALTHCHECKRuntimeContainer health check❌ No
LABELBuildMetadata❌ No

Interview tip: EXPOSE does NOT open a port on the host — it’s just documentation. You still need -p 3000:3000 when running.


Q4: What is the difference between CMD and ENTRYPOINT?

Section titled “Q4: What is the difference between CMD and ENTRYPOINT?”

Answer:

FeatureCMDENTRYPOINT
Overridedocker run myimage <new-cmd>--entrypoint flag only
PurposeDefault argumentsFixed executable
CombinedCMD becomes args to ENTRYPOINTENTRYPOINT is always run
# CMD only — entire command is replaceable
CMD ["nginx", "-g", "daemon off;"]
# docker run myimage ls -la ← completely replaces CMD
# ENTRYPOINT only — always runs nginx
ENTRYPOINT ["nginx"]
# docker run myimage -g "daemon off;" ← -g becomes arg to nginx
# ENTRYPOINT + CMD (best pattern)
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"] ← default args, easily replaced
# docker run myimage -c /etc/nginx/custom.conf ← replaces CMD part only
Terminal window
# Examples at runtime:
docker run myimage # → runs ENTRYPOINT + CMD defaults
docker run myimage --debug # → runs ENTRYPOINT + ["--debug"]
docker run --entrypoint /bin/sh myimage # → overrides ENTRYPOINT (for debugging)

Shell form vs Exec form:

# Shell form (BAD for ENTRYPOINT — wraps in /bin/sh, can't receive signals)
CMD node server.js
# Exec form (GOOD — direct exec, receives SIGTERM properly)
CMD ["node", "server.js"]

Interview tip: Always use exec form ["cmd", "arg"] for ENTRYPOINT and CMD. Shell form wraps in /bin/sh -c, which means PID 1 is the shell, not your app — signals like SIGTERM won’t reach your app, causing slow shutdowns.


Q5: How do you build and tag a Docker image?

Section titled “Q5: How do you build and tag a Docker image?”

Answer:

Terminal window
# Basic build
docker build -t myapp:1.0 .
# Build with multiple tags in one command
docker build -t myapp:1.0 -t myapp:latest .
# Use specific Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
# Pass build-time variables (available only during build)
docker build --build-arg NODE_ENV=production --build-arg GIT_SHA=$(git rev-parse --short HEAD) -t myapp:1.0 .
# Build from different context directory
docker build -t myapp:1.0 -f ./docker/Dockerfile ./src
# No cache (useful in CI to get fresh dependencies)
docker build --no-cache -t myapp:1.0 .
# View layer sizes and commands
docker history myapp:1.0
docker history --no-trunc myapp:1.0 # Full commands

Interview tip: In CI/CD, tag with git SHA for immutability: myapp:abc1234. Also tag myapp:latest for convenience. Never deploy :latest to production — it’s not reproducible.


Q6: How do you push an image to Docker Hub?

Section titled “Q6: How do you push an image to Docker Hub?”

Answer:

Terminal window
# Docker Hub
docker login # Prompts username/password
docker login -u $USERNAME -p $PASSWORD # Non-interactive (CI)
docker tag myapp:1.0 username/myapp:1.0
docker push username/myapp:1.0
# AWS ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS \
--password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
docker tag myapp:1.0 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
# Azure ACR
az acr login --name myregistry
docker tag myapp:1.0 myregistry.azurecr.io/myapp:1.0
docker push myregistry.azurecr.io/myapp:1.0
# Google GCR
gcloud auth configure-docker
docker push gcr.io/my-project/myapp:1.0

Common mistake: Never use docker login -p with your actual password in shell scripts — it shows up in shell history. Use --password-stdin instead.


Answer:

NetworkDescriptionDNS by nameMulti-hostUse Case
bridge (default)Containers on docker0❌ No❌ NoQuick testing only
bridge (user-defined)Custom bridge✅ Yes❌ NoSingle-host apps
hostUses host network directlyN/AN/AMaximum performance
noneNo network at allN/AN/ASecurity-sensitive tasks
overlayMulti-host virtual network✅ Yes✅ YesDocker Swarm
macvlanContainer gets own MAC/IP✅ Yes✅ YesLegacy apps needing real IPs
Terminal window
# List networks
docker network ls
# Create custom bridge network (recommended for dev)
docker network create --driver bridge my-app-net
# Run container on network
docker run -d --network my-app-net --name api myapp:1.0
docker run -d --network my-app-net --name db postgres:15
# API can reach DB by name: http://db:5432
# DB can reach API by name: http://api:3000
# Inspect network and see connected containers
docker network inspect my-app-net
# Connect existing running container to another network
docker network connect my-app-net my-container
# Disconnect
docker network disconnect my-app-net my-container
# Remove unused networks
docker network prune

Interview tip: Always use user-defined bridge networks instead of the default bridge. The default bridge doesn’t support DNS resolution by container name — you’d have to use IPs which change on restart.


Q8: How do containers communicate with each other?

Section titled “Q8: How do containers communicate with each other?”

Answer:

Terminal window
# 1. Using user-defined bridge network (most common)
docker network create app-net
docker run -d --name postgres --network app-net \
-e POSTGRES_PASSWORD=secret postgres:15
docker run -d --name api --network app-net \
-e DATABASE_URL=postgresql://postgres:secret@postgres:5432/mydb \
myapp:1.0
# 'api' container can reach 'postgres' by container name
# Test from inside container:
docker exec api ping postgres # DNS works by name!
docker exec api nc -zv postgres 5432 # Check port open
# 2. Using Docker Compose (auto-creates network)
# All services in same compose file can reach each other by service name
# 3. Using container IP (NOT recommended - IPs change)
docker inspect postgres --format '{{.NetworkSettings.Networks.app-net.IPAddress}}'
# → 172.20.0.2 ← unreliable, use name instead

How Docker DNS works:

Docker embedded DNS server runs at 127.0.0.11
When container does: curl http://db:5432
→ Container's /etc/resolv.conf points to 127.0.0.11
→ Docker DNS resolves 'db' → 172.20.0.3
→ Request reaches db container

Common mistake: This only works on user-defined networks. The default docker0 bridge does NOT have embedded DNS.


Q9: How do you expose and publish container ports?

Section titled “Q9: How do you expose and publish container ports?”

Answer:

Terminal window
# Syntax: -p <host_port>:<container_port>
docker run -p 8080:80 nginx
# ↑ ↑
# host port container port
# Access via: http://localhost:8080
# Bind to specific host IP (security — only localhost)
docker run -p 127.0.0.1:8080:80 nginx
# Only accessible from same machine, NOT from network
# Multiple ports
docker run -p 80:80 -p 443:443 nginx
# Random host port (Docker picks)
docker run -P nginx # Publishes all EXPOSE'd ports to random ports
# Check what ports are mapped
docker port my-container
docker ps # Shows port mappings in output
# UDP ports
docker run -p 53:53/udp dns-server

EXPOSE vs -p:

# EXPOSE in Dockerfile — just documentation, does NOT publish
EXPOSE 3000
Terminal window
# -p flag actually publishes (opens host port)
docker run -p 3000:3000 myapp # ← This actually opens the port

Interview tip: EXPOSE is just metadata — it documents which port the container listens on. You still need -p to make it accessible from the host. Think of EXPOSE as the contract for what port to use with -p.


Q10: What are Docker volumes and when to use them?

Section titled “Q10: What are Docker volumes and when to use them?”

Answer: Docker provides three storage options:

TypeHowWhere storedBest For
Named Volume-v myvolume:/container/path/var/lib/docker/volumes/Production databases, persistent data
Bind Mount-v /host/path:/container/pathAnywhere on hostDev with live code reload
tmpfs--tmpfs /container/pathRAM only (not disk)Secrets, temp data, performance
Terminal window
# Named Volume (production — Docker manages it)
docker volume create db-data
docker run -d \
--name postgres \
-v db-data:/var/lib/postgresql/data \
postgres:15
# Data persists even after container is deleted!
# Bind Mount (development — see changes instantly)
docker run -d \
-v $(pwd)/src:/app/src \
-v $(pwd)/public:/app/public \
-p 3000:3000 \
node:18
# Edit files locally → instantly reflected in container
# tmpfs (in-memory, fast, gone on stop)
docker run --tmpfs /tmp:rw,noexec,nosuid,size=100m myapp
# Read-only bind mount (config files, never write)
docker run -v $(pwd)/config.json:/app/config.json:ro myapp
# Inspect and manage volumes
docker volume ls
docker volume inspect db-data
docker volume prune # Remove volumes not used by any container

Common mistake: Bind mounts depend on the host path existing. Named volumes are portable and can be backed up with docker volume. On production, always use named volumes for databases — never bind mounts.


Q11: How do you back up and restore Docker volumes?

Section titled “Q11: How do you back up and restore Docker volumes?”

Answer:

Terminal window
# Backup volume to tar archive
docker run --rm \
-v db-data:/data:ro \
-v $(pwd)/backups:/backup \
alpine \
tar czf /backup/db-backup-$(date +%Y%m%d).tar.gz -C /data .
# Restore volume from backup
docker run --rm \
-v db-data:/data \
-v $(pwd)/backups:/backup \
alpine \
tar xzf /backup/db-backup-20240115.tar.gz -C /data
# Copy files between host and container (quick method)
docker cp my-container:/app/config.json ./config.json # container → host
docker cp ./config.json my-container:/app/config.json # host → container
# Postgres-specific backup (recommended over volume backup)
docker exec postgres-container \
pg_dump -U postgres mydb > backup.sql
# Restore postgres
cat backup.sql | docker exec -i postgres-container \
psql -U postgres mydb
# Move volume between hosts
# 1. Export
docker run --rm -v myvolume:/data alpine tar czf - /data | ssh user@newhost 'docker run --rm -v myvolume:/data -i alpine tar xzf - -C /'

Interview tip: For database backups, use the database’s native tools (pg_dump, mysqldump) rather than backing up the raw volume. Raw volume backups can be corrupted if the DB was writing during backup.


Q12: What is Docker Compose and how does it work?

Section titled “Q12: What is Docker Compose and how does it work?”

Answer: Docker Compose defines and runs multi-container applications using a YAML file.

docker-compose.yml
version: '3.9'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=db
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres-data:
networks:
default:
driver: bridge
Terminal window
# Start all services
docker compose up -d
# View logs
docker compose logs -f
# Scale a service
docker compose up -d --scale web=3
# Stop and remove containers/networks
docker compose down
# Also remove volumes
docker compose down -v

Q13: How do you use environment variables in Docker Compose?

Section titled “Q13: How do you use environment variables in Docker Compose?”

Answer:

Terminal window
# .env file (auto-loaded by Compose)
DB_PASSWORD=supersecret
APP_PORT=3000
IMAGE_TAG=1.2.0
docker-compose.yml
services:
app:
image: myapp:${IMAGE_TAG}
ports:
- "${APP_PORT}:3000"
environment:
- DB_PASSWORD=${DB_PASSWORD}
env_file:
- .env.production
Terminal window
# Override with inline env
DB_PASSWORD=test docker compose up
# Use alternate env file
docker compose --env-file .env.staging up

Section 5: Multi-stage Builds & Optimization

Section titled “Section 5: Multi-stage Builds & Optimization”

Q14: What are multi-stage builds and why use them?

Section titled “Q14: What are multi-stage builds and why use them?”

Answer: Multi-stage builds use multiple FROM statements to produce a smaller final image by separating build-time dependencies from runtime.

# Stage 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production image (much smaller)
FROM node:18-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
Terminal window
# Build specific stage
docker build --target builder -t myapp:build .
docker build --target production -t myapp:prod .

Result: Build image may be 800MB; production image ~120MB.


Q15: How do you optimize Docker image size?

Section titled “Q15: How do you optimize Docker image size?”

Answer:

# 1. Use minimal base images
FROM alpine:3.18 # ~5MB vs ubuntu ~77MB
FROM node:18-alpine # ~170MB vs node:18 ~1GB
# 2. Combine RUN commands to reduce layers
RUN apt-get update && \
apt-get install -y curl wget && \
rm -rf /var/lib/apt/lists/*
# 3. Use .dockerignore
# .dockerignore
node_modules
.git
*.log
dist
.env
# 4. Order layers by change frequency (cache optimization)
COPY package*.json ./ # Changes rarely
RUN npm install # Cached if package.json unchanged
COPY . . # Changes often
Terminal window
# Inspect image layers and sizes
docker history myapp:1.0
# Use dive tool for layer analysis
dive myapp:1.0

Q16: What are Docker security best practices?

Section titled “Q16: What are Docker security best practices?”

Answer:

1. Run as non-root user

# Create and use non-root user (most important security step)
FROM node:18-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser # All subsequent commands and the container run as appuser

2. Read-only filesystem + writable tmpfs

Terminal window
docker run \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid \
myapp
# Root FS is immutable; only /tmp is writable

3. Drop all Linux capabilities

Terminal window
docker run \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
myapp
# Container can't gain new privileges (prevents setuid exploits)

4. Pin image versions

# BAD — :latest changes silently
FROM node:latest
# GOOD — exact version
FROM node:18.19.1-alpine3.19
# BEST — pin by digest (immutable)
FROM node@sha256:abc123...def456

5. Scan for vulnerabilities

Terminal window
# Trivy (most popular)
trivy image --exit-code 1 --severity CRITICAL,HIGH myapp:1.0
# Docker Scout (built-in)
docker scout cves myapp:1.0
docker scout recommendations myapp:1.0 # Suggests safer base images
# Grype
grype myapp:1.0

6. Resource limits (prevent DoS)

Terminal window
docker run \
--memory=512m \
--memory-swap=512m \
--cpus=1.0 \
--pids-limit=100 \
--ulimit nofile=1024:1024 \
myapp

Interview tip: The #1 security issue in Docker is running containers as root. Even if your app doesn’t need root, a vulnerability could be exploited to escape to the host. Always add USER in Dockerfile.


Answer:

MethodSecurityVisible in inspect?Use Case
ENV in Dockerfile❌ Worst✅ YesNever use for secrets
-e flag at runtime⚠️ Medium✅ YesOnly for non-sensitive values
.env file⚠️ Medium✅ YesDev only
Docker Swarm secrets✅ Good❌ NoSwarm deployments
External vault (Vault/SSM)✅ Best❌ NoProduction
Terminal window
# Docker Swarm secrets (stored encrypted in Swarm Raft)
echo "supersecret" | docker secret create db_password -
docker secret ls
# Use in service
docker service create \
--name db \
--secret db_password \
-e POSTGRES_PASSWORD_FILE=/run/secrets/db_password \
postgres:15
# Secret file appears at /run/secrets/db_password inside container
# docker-compose with Swarm secrets
version: '3.9'
services:
db:
image: postgres:15
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
external: true # Already created with docker secret create
Terminal window
# Without Swarm — best practice: inject at runtime from external source
# Using AWS Secrets Manager:
docker run \
-e DB_PASSWORD=$(aws secretsmanager get-secret-value \
--secret-id /prod/db-password --query SecretString --output text | \
jq -r '.password') \
myapp
# Using HashiCorp Vault:
DB_PASS=$(vault kv get -field=password secret/myapp/db)
docker run -e DB_PASSWORD=$DB_PASS myapp

Common mistake: Putting secrets in Dockerfile ENV means they’re baked into every image layer and visible to anyone with docker inspect or docker history. Always inject secrets at runtime.


Q18: How do you debug a failing container?

Section titled “Q18: How do you debug a failing container?”

Answer:

Terminal window
# Step 1: Check what happened
docker ps -a # See all containers (including stopped ones)
docker logs container_name # View stdout/stderr
docker logs --tail 50 -f container_name # Stream last 50 lines
docker logs --since 10m container_name # Last 10 minutes only
# Step 2: Check exit code and state
docker inspect container_name --format='{{.State.ExitCode}}'
docker inspect container_name --format='{{.State.Error}}'
docker inspect container_name | grep -A5 '"State"'
# Step 3: Enter running container for live debugging
docker exec -it container_name /bin/sh # sh (alpine/minimal images)
docker exec -it container_name bash # bash (debian/ubuntu)
docker exec -it container_name env # Check environment variables
# Step 4: Debug a crashed container (can't exec into it)
docker run -it --entrypoint /bin/sh myapp:1.0 # Override entrypoint
# Now manually run: node server.js and see the error
# Step 5: Check resource issues
docker stats container_name --no-stream
docker inspect container_name | grep -i oom # OOM killed?
# Step 6: Check events (useful for start/stop cycles)
docker events --filter container=container_name
# Step 7: Network debugging from inside container
docker exec container_name wget -qO- http://other-service:8080/health
docker exec container_name nslookup other-service # DNS check

Interview tip: Start with docker logs then docker inspect. If the container keeps crashing (restart loop), use docker run -it --entrypoint sh to start a shell and manually run the command to see the error.


Q19: Container starts and immediately exits — how do you debug?

Section titled “Q19: Container starts and immediately exits — how do you debug?”

Answer: This is one of the most common Docker problems. Follow this checklist:

Terminal window
# 1. Check logs FIRST
docker logs container_name
# Look for error messages, stack traces, missing env vars
# 2. Check exit code
docker inspect container_name --format='{{.State.ExitCode}}'
Exit CodeCauseFix
0Intentional exit (CMD completed)App ran and finished (for jobs, this is OK)
1Application error/crashCheck logs for exception
125Docker errorCheck docker command syntax
126Permission denied to CMDCheck file permissions
127CMD not foundCheck CMD path in Dockerfile
137OOM killed or docker killIncrease memory limit
143SIGTERM (graceful stop)Normal shutdown
Terminal window
# 3. Run interactively to see error output directly
docker run -it myapp:1.0
# 4. Override entrypoint to enter shell and debug
docker run --entrypoint /bin/sh -it myapp:1.0
# Now manually run your command: node server.js
# 5. Common causes:
# - App script not executable: chmod +x entrypoint.sh
# - Wrong CMD path: CMD ["/app/server"] but file is at /app/bin/server
# - Missing required env var: DB_URL not set → crash
# - Port already in use on host
# - Config file not found
# 6. Check OOM
docker inspect container_name | grep OOMKilled
# true → add --memory limit or fix memory leak

Interview tip: Always check docker logs first. 90% of the time the answer is right there.


Q20: How do you monitor Docker containers?

Section titled “Q20: How do you monitor Docker containers?”

Answer:

Terminal window
# Real-time resource stats (like top but for containers)
docker stats
docker stats --no-stream # One-time snapshot
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
# Check configured resource limits
docker inspect container_name | grep -A5 'HostConfig' | grep -i 'memory\|cpu'

Full monitoring stack with docker-compose:

monitoring-stack.yml
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
privileged: true
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
Terminal window
# Check health status of container
docker inspect container_name --format='{{.State.Health.Status}}'
# healthy | unhealthy | starting
# View health check history
docker inspect container_name | jq '.[0].State.Health.Log'
# List containers by status
docker ps --filter "health=unhealthy"
docker ps --filter "status=exited"

Interview tip: docker stats is your first tool for CPU/memory debugging. For production, pair cAdvisor (metrics collector) + Prometheus (metrics storage) + Grafana (dashboards).


Answer:

AspectDocker SwarmKubernetes
Setupdocker swarm init (1 command)kubeadm/eksctl (complex)
Learning curveLowHigh
Scalingdocker service scaleHPA + manual
Auto-healing✅ Yes (basic)✅ Yes (advanced)
Load balancingBuilt-in (VIP routing)Requires Ingress controller
StorageNamed volumesPV/PVC/StorageClass
ConfigSecrets/ConfigsConfigMap/Secrets
NetworkingOverlay networkCNI plugins (Calico, Flannel)
Community/ecosystemSmaller, decliningMassive, growing
Enterprise adoptionSmall-medium teamsLarge enterprise
Terminal window
# Docker Swarm — setup
docker swarm init --advertise-addr 192.168.1.10
# On worker nodes:
docker swarm join --token <token> 192.168.1.10:2377
# Deploy replicated service
docker service create \
--name web \
--replicas 3 \
-p 80:80 \
--update-parallelism 1 \
--update-delay 10s \
nginx:alpine
# Scale
docker service scale web=5
# Rolling update
docker service update --image nginx:1.25 web
# View service status
docker service ls
docker service ps web
docker service logs web -f

Interview tip: “Swarm is Docker’s built-in orchestrator — simpler but less powerful. In 2024, most companies use Kubernetes. Choose Swarm if you need quick orchestration without the K8s learning curve.”


Answer:

DriverWhere logs goUse Case
json-fileLocal JSON files (default)Dev, simple single-host
localCompressed local filesMore efficient than json-file
syslogSystem syslogOn-premise Linux
journaldsystemd journalsystemd-based systems
fluentdFluentd daemonEFK stack
awslogsAWS CloudWatch LogsAWS deployments
gcplogsGoogle Cloud LoggingGCP deployments
splunkSplunk HTTP event collectorEnterprise
noneDisabledSecurity-sensitive containers
Terminal window
# Check current logging driver
docker info | grep "Logging Driver"
# Set per-container with rotation (production best practice)
docker run \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
--log-opt compress=true \
myapp
# AWS CloudWatch
docker run \
--log-driver=awslogs \
--log-opt awslogs-group=/docker/myapp \
--log-opt awslogs-region=us-east-1 \
--log-opt awslogs-stream=web-1 \
myapp
# Set default for ALL containers in daemon.json
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
# Then: sudo systemctl restart docker

Common mistake: Default json-file logging has no size limit — logs can fill up your disk. Always set max-size and max-file in production.


Q23: What is a Docker registry and how do you run a private one?

Section titled “Q23: What is a Docker registry and how do you run a private one?”

Answer: A Docker registry is a server that stores and distributes Docker images.

Cloud-managed registries (recommended for production):

RegistryProviderFree tier
Docker HubDocker1 private repo
ECRAWSPer-storage cost
ACRAzurePer-storage cost
GCR/Artifact RegistryGooglePer-storage cost
GitHub Container Registry (ghcr.io)GitHubFree for public
Terminal window
# Run local private registry (for development/air-gapped)
docker run -d \
--name local-registry \
-p 5000:5000 \
--restart always \
-v registry-data:/var/lib/registry \
registry:2
# Push to local registry
docker tag myapp:1.0 localhost:5000/myapp:1.0
docker push localhost:5000/myapp:1.0
# Pull from local registry
docker pull localhost:5000/myapp:1.0
# Secure with TLS and authentication
docker run -d \
--name secure-registry \
-p 443:5000 \
-v /certs:/certs \
-v /auth:/auth \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2
# List images in registry
curl http://localhost:5000/v2/_catalog
# {"repositories":["myapp","nginx"]}
# List tags for an image
curl http://localhost:5000/v2/myapp/tags/list

Interview tip: For production, always use a managed registry (ECR, ACR, GCR) — they handle redundancy, scanning, and access control. Self-hosted registries are mainly for air-gapped environments.


Q24: How do you implement health checks in Docker?

Section titled “Q24: How do you implement health checks in Docker?”

Answer: Health checks tell Docker whether your container is working correctly. Docker Swarm and Compose use health status to route traffic and restart unhealthy containers.

# In Dockerfile
# Parameters:
# --interval: how often to run (default 30s)
# --timeout: max time for check (default 30s)
# --start-period: grace period on start (default 0s) — crucial!
# --retries: fail after this many consecutive failures (default 3)
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Or use wget (available in alpine when curl isn't)
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
# For databases: check if server is ready
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=5 \
CMD pg_isready -U postgres || exit 1
# In docker-compose.yml
services:
api:
image: myapp:1.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
start_period: 40s # App may take 40s to start — don't fail early!
retries: 3
# depends_on with condition — waits for healthy status
frontend:
depends_on:
api:
condition: service_healthy # Waits for api to be healthy!
Terminal window
# Check health status
docker inspect --format='{{.State.Health.Status}}' container_name
# healthy | unhealthy | starting
# View health check history (last 5 checks)
docker inspect container_name | jq '.[0].State.Health'
# List unhealthy containers
docker ps --filter "health=unhealthy"

Common mistake: Forgetting start_period. If your app takes 30 seconds to start, health checks will fail 3 times and mark container unhealthy before it even starts. Always set start_period to your app’s typical startup time.


Q25: Explain Docker layer caching and how to leverage it.

Section titled “Q25: Explain Docker layer caching and how to leverage it.”

Answer: Docker caches each instruction as a layer. Once ANY layer changes, ALL subsequent layers are rebuilt (cache miss).

# ❌ WRONG — npm install runs on EVERY code change
COPY . . # Changes every time
RUN npm install # Always re-runs = slow!
# ✅ CORRECT — npm install only re-runs when package.json changes
COPY package*.json ./ # Rarely changes → stays cached
RUN npm install # Cached if package.json unchanged!
COPY . . # Changes often — but this is the LAST expensive step

General rule: Order by change frequency (least frequent → most frequent)

FROM node:18-alpine # Almost never changes ✅ cached
WORKDIR /app # Never changes ✅ cached
COPY package*.json ./ # Changes occasionally ✅ usually cached
RUN npm ci # Expensive! cached when above unchanged ✅
COPY . . # Changes every commit ❌ always rebuilds
RUN npm run build # Only runs when code changes
Terminal window
# Force rebuild without cache (useful in CI)
docker build --no-cache -t myapp:1.0 .
# BuildKit persistent cache (speeds up repeated CI builds)
# .dockerignore + BuildKit cache mount
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
# npm cache persists between builds → faster!
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# pip cache persists too!

Interview tip: Layer cache is invalidated by content change, not filename. If package.json is unchanged, RUN npm install uses the cache even across different build machines IF using a shared cache (e.g., GitHub Actions cache). This is one of the biggest performance wins in CI/CD.


Q26: What is BuildKit and what are its advantages?

Section titled “Q26: What is BuildKit and what are its advantages?”

Answer: BuildKit is Docker’s next-generation image build engine.

Terminal window
# Enable BuildKit
export DOCKER_BUILDKIT=1
# Or in daemon.json
{
"features": { "buildkit": true }
}
# Build with BuildKit
docker buildx build -t myapp:1.0 .
# BuildKit features
# 1. Parallel stage builds
FROM base AS stage1
FROM base AS stage2
# 2. Cache mounts
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# 3. Secret mounts (never stored in image)
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret > /app/secret
# 4. SSH mounts for private repos
RUN --mount=type=ssh \
git clone git@github.com:org/private-repo.git

Q27: How do you build multi-architecture images?

Section titled “Q27: How do you build multi-architecture images?”

Answer:

Terminal window
# Create and use a multi-arch builder
docker buildx create --name multiarch --use
# Build for multiple platforms
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
-t username/myapp:latest \
--push \
.
# Inspect manifest
docker buildx imagetools inspect username/myapp:latest

Answer: Docker Content Trust (DCT) ensures image integrity via digital signatures.

Terminal window
# Enable DCT
export DOCKER_CONTENT_TRUST=1
# Sign and push image (requires notary key)
docker trust key generate my-key
docker trust sign username/myapp:1.0
# Inspect trust data
docker trust inspect username/myapp:1.0
# Verify pull (only signed images allowed)
docker pull username/myapp:1.0

Q29: How do you handle Docker in production?

Section titled “Q29: How do you handle Docker in production?”

Answer: Production Docker checklist:

Terminal window
# Complete production docker run command
docker run \
--name myapp \
--detach \
# --- Image ---
myapp:1.5.3 \ # Always pin version, never :latest
# --- Restart ---
--restart unless-stopped \ # Auto-restart on failure (not always — could hide bugs)
# --- Resources ---
--memory=512m \ # Memory limit (OOM kill if exceeded)
--memory-swap=512m \ # No swap (disk-based = slow)
--cpus=1.0 \ # CPU limit
--pids-limit=100 \ # Limit processes (prevent fork bombs)
# --- Security ---
--user 1001:1001 \ # Non-root user
--read-only \ # Immutable root filesystem
--tmpfs /tmp:rw,noexec,nosuid \ # Writable temp, no execution
--cap-drop ALL \ # Drop all capabilities
--cap-add NET_BIND_SERVICE \ # Add only what's needed
--security-opt no-new-privileges:true \
# --- Networking ---
--network my-app-net \ # Isolated network
-p 127.0.0.1:8080:3000 \ # Bind to localhost only
# --- Logging ---
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
# --- Health ---
--health-cmd='curl -f http://localhost:3000/health || exit 1' \
--health-interval=30s \
--health-timeout=5s \
--health-retries=3 \
--health-start-period=30s

Restart policies explained:

Terminal window
# no — Never restart (useful for one-off jobs)
# on-failure — Only restart if exit code != 0
# always — Always restart (even on docker stop — restarts on daemon start)
# unless-stopped — Restart unless manually stopped (most common for services)
docker run --restart=unless-stopped myapp

Interview tip: Use --restart=unless-stopped for services. Use --restart=on-failure:3 for jobs that might fail (limits retries). Never use --restart=always in production as it prevents intentional stops from sticking.


Q30: What happens when you run docker run?

Section titled “Q30: What happens when you run docker run?”

Answer: Step-by-step execution:

docker run -d -p 8080:80 --name my-nginx nginx:alpine
Step 1: Docker CLI parses command, sends to daemon
→ POST /containers/create to Docker daemon (Unix socket: /var/run/docker.sock)
Step 2: Daemon checks if nginx:alpine exists locally
→ Not found → pulls from Docker Hub (registry.hub.docker.com)
→ Downloads each layer
Step 3: Daemon creates container object
→ Assigns container ID (e.g., a1b2c3d4e5f6...)
Step 4: Creates Linux namespaces
→ PID namespace (container has its own PID 1 = nginx)
→ NET namespace (container gets own network stack)
→ MNT namespace (container has isolated filesystem view)
→ UTS namespace (container has own hostname)
→ IPC namespace
Step 5: Applies cgroups (resource limits)
→ CPU, memory limits configured
Step 6: Sets up networking
→ Creates veth pair (virtual ethernet)
→ Assigns IP from bridge network (172.17.0.x)
→ Sets up port forwarding: host:8080 → container:80
Step 7: Mounts filesystem
→ Stacks image layers (overlayfs)
→ Creates read-write layer on top
→ Mounts volumes/bind mounts
Step 8: Starts process
→ Runs ENTRYPOINT/CMD as PID 1 inside container
→ Container is now 'running'
Terminal window
# See the daemon API calls
docker run --rm -it nginx:alpine &
# Docker used POST /containers/create then POST /containers/{id}/start
# Check what PID nginx has on HOST (different from container's PID 1)
docker inspect my-nginx --format='{{.State.Pid}}'
# → 12345 (host PID)
pgrep nginx # Same 12345 appears here too

Interview tip: “Every container is just a process on the host with isolated namespaces and limited resources via cgroups. There’s no magic — docker run creates namespaces, sets up networking, and exec()s your process.”


Q31: What is the difference between COPY and ADD in Dockerfile?

Section titled “Q31: What is the difference between COPY and ADD in Dockerfile?”

Answer:

FeatureCOPYADD
Copy local files✅ Yes✅ Yes
Copy from URL❌ No✅ Yes
Extract tar archives❌ No✅ Yes (auto-extracts)
Recommended?✅ PreferredOnly when needed
# COPY — simple and predictable (recommended)
COPY ./app /app
COPY package*.json /app/
# ADD — use only for these specific cases
ADD https://example.com/file.tar.gz /tmp/ # Download from URL
ADD archive.tar.gz /app/ # Auto-extract tar

Interview tip: “Use COPY unless you need URL downloading or automatic tar extraction — COPY is more transparent and predictable.”


Q32: What is the difference between RUN, CMD, and ENTRYPOINT?

Section titled “Q32: What is the difference between RUN, CMD, and ENTRYPOINT?”

Answer:

InstructionWhen runsOverride at runtimePurpose
RUNDuring docker buildN/AInstall software, build steps
CMDWhen container startsdocker run image <cmd>Default arguments
ENTRYPOINTWhen container starts--entrypoint flagFixed executable
# RUN — runs during build (creates layer)
RUN apt-get install -y curl
# CMD — default command, easily overrideable
CMD ["node", "server.js"]
# docker run myimage python app.py ← overrides CMD
# ENTRYPOINT — fixed executable
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# docker run myimage -c /etc/nginx/nginx.conf ← CMD becomes arg to ENTRYPOINT

Answer: Docker caches each layer. Once a layer changes, ALL subsequent layers are invalidated.

# BAD - npm install runs every time ANY file changes
COPY . .
RUN npm install
# GOOD - npm install only runs when package.json changes
COPY package*.json ./
RUN npm install # Cached unless package.json changes
COPY . . # Only this layer re-runs when code changes
Layer cache hit: ✅ Uses cached layer (fast)
Layer cache miss: ❌ Rebuilds this + all subsequent layers (slow)
COPY package.json → RUN npm install → COPY . → RUN build
↑ ↑
Rarely changes Changes often
(cache hit ✅) (cache miss ❌ but only this layer)

Q34: What is Docker namespace and cgroups?

Section titled “Q34: What is Docker namespace and cgroups?”

Answer: Docker uses Linux kernel features for isolation:

  • Namespaces → What a process can see (isolation)
  • cgroups → What a process can use (resource limits)
Terminal window
# Namespaces Docker uses:
# - PID namespace: container has its own PID 1
# - NET namespace: container has its own network stack
# - MNT namespace: container has its own filesystem view
# - UTS namespace: container has its own hostname
# - IPC namespace: container has its own IPC resources
# - USER namespace: container can have its own user IDs
# Verify: Container PID 1 is different from host
docker run -d nginx
docker exec -it nginx_container ps aux # Shows PID 1 inside container
ps aux | grep nginx # Different PID on host
# cgroups — resource limits
docker run --memory=512m --cpus=1.0 nginx
# Check cgroup limits
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes

Q35: How do you minimize Docker image size?

Section titled “Q35: How do you minimize Docker image size?”

Answer:

# 1. Use minimal base images
FROM scratch # Empty image (for static binaries)
FROM alpine:3.18 # ~5MB
FROM distroless/nodejs # ~30MB, no shell (secure)
FROM node:18-alpine # ~170MB
# 2. Multi-stage builds
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine AS runtime
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
# Result: 170MB instead of 1GB
# 3. Combine RUN commands
# BAD: 3 layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# GOOD: 1 layer
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
# 4. Use .dockerignore
# .dockerignore
node_modules/
.git/
*.log
.env
dist/
coverage/

Q36: What is Docker Compose health check dependency?

Section titled “Q36: What is Docker Compose health check dependency?”

Answer:

# Problem: app starts before db is ready → crash
services:
app:
depends_on:
- db # Only waits for container START, not readiness
# Solution: Use condition: service_healthy
services:
app:
image: myapp:1.0
depends_on:
db:
condition: service_healthy # Waits for db to be healthy
restart: unless-stopped
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s

Answer: Overlay networks allow containers on different Docker hosts to communicate — used in Docker Swarm.

Terminal window
# Create overlay network (requires Swarm mode)
docker swarm init
docker network create \
--driver overlay \
--attachable \
my-overlay-net
# Deploy service on overlay network
docker service create \
--name web \
--network my-overlay-net \
--replicas 3 \
nginx
# Containers on different nodes can talk via overlay
# Container on Node1 → Container on Node2 via my-overlay-net

Q38: How do you copy files between container and host?

Section titled “Q38: How do you copy files between container and host?”

Answer:

Terminal window
# Host → Container
docker cp ./config.json my-container:/app/config.json
docker cp ./ssl-certs/ my-container:/etc/ssl/
# Container → Host
docker cp my-container:/app/logs/error.log ./local-error.log
docker cp my-container:/var/log/ ./container-logs/
# Common use case: Backup data from container
docker cp postgres-db:/var/lib/postgresql/data/ ./postgres-backup/

Q39: What is the difference between docker stop and docker kill?

Section titled “Q39: What is the difference between docker stop and docker kill?”

Answer:

CommandSignalBehavior
docker stopSIGTERM → SIGKILL (after timeout)Graceful shutdown
docker killSIGKILL (immediately)Force kill
Terminal window
# docker stop — sends SIGTERM, waits 10s, then SIGKILL
docker stop my-container # Default 10s grace period
docker stop --time=30 my-container # 30s grace period
# docker kill — immediate SIGKILL
docker kill my-container
docker kill --signal=SIGTERM my-container # Can specify signal
# Best practice: Use 'docker stop' in production
# App should handle SIGTERM for graceful cleanup:
# - Close DB connections
# - Finish in-flight requests
# - Flush buffers

Q40: How do you pass environment variables to Docker containers?

Section titled “Q40: How do you pass environment variables to Docker containers?”

Answer:

Terminal window
# 1. Inline at runtime
docker run -e DB_HOST=localhost -e DB_PORT=5432 myapp
# 2. From host environment variable
docker run -e DB_PASSWORD myapp # Passes $DB_PASSWORD from host
# 3. From .env file
docker run --env-file .env myapp
# 4. In docker-compose.yml
services:
app:
environment:
- DB_HOST=postgres
- NODE_ENV=production
- DB_PASSWORD=${DB_PASSWORD} # From host .env
env_file:
- .env.production # File-based
Terminal window
# Check what environment a container has
docker exec my-container env
docker inspect my-container | grep -A20 Env

Q41: What are Docker container exit codes?

Section titled “Q41: What are Docker container exit codes?”

Answer:

Exit CodeMeaningCommon Cause
0SuccessNormal exit
1Application errorApp crash, unhandled exception
125Docker daemon errorInvalid docker command
126Permission deniedCMD not executable
127Command not foundWrong CMD path
137SIGKILLOOM killed, docker kill
139SegfaultApplication segmentation fault
143SIGTERMGraceful stop (docker stop)
Terminal window
# Check exit code
docker inspect container_name --format='{{.State.ExitCode}}'
# Container OOM killed?
docker inspect container_name | grep OOMKilled
# "OOMKilled": true → increase memory limit

Q42: What is Docker socket and why is it dangerous to mount?

Section titled “Q42: What is Docker socket and why is it dangerous to mount?”

Answer: The Docker socket (/var/run/docker.sock) is how Docker CLI talks to the Docker daemon.

Terminal window
# Mounting Docker socket gives container FULL control over host Docker
docker run -v /var/run/docker.sock:/var/run/docker.sock myapp
# This container can now:
# - Create containers on the host
# - Stop/delete any container
# - Escape to host by running privileged container
# = Essentially root on the host!
# Safer alternative: Use Docker-in-Docker (DinD) for CI
# Or use Kaniko for building images without Docker socket
docker run \
gcr.io/kaniko-project/executor:latest \
--context=dir:///workspace \
--destination=myapp:latest

Q43: How do you implement Docker image tagging strategy?

Section titled “Q43: How do you implement Docker image tagging strategy?”

Answer:

Terminal window
# Strategy: semantic tags + commit SHA + latest
# During CI build
IMAGE=myorg/myapp
GIT_SHA=$(git rev-parse --short HEAD)
VERSION=v1.5.0
# Build and tag multiple ways
docker build -t $IMAGE:$GIT_SHA .
docker tag $IMAGE:$GIT_SHA $IMAGE:$VERSION
docker tag $IMAGE:$GIT_SHA $IMAGE:latest
# Push all tags
docker push $IMAGE:$GIT_SHA # Immutable, always points to this build
docker push $IMAGE:$VERSION # Semantic version
docker push $IMAGE:latest # Latest release
# For GitFlow branches
docker tag $IMAGE:$GIT_SHA $IMAGE:main-latest # Main branch latest
docker tag $IMAGE:$GIT_SHA $IMAGE:develop-latest # Dev branch latest

Q44: What is a multi-stage build and when should you use it?

Section titled “Q44: What is a multi-stage build and when should you use it?”

Answer: Multi-stage builds use multiple FROM statements to separate build and runtime environments.

# Go application — classic multi-stage
# Stage 1: Build (uses full Go environment)
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .
# Stage 2: Runtime (minimal, just the binary)
FROM scratch AS runtime # Empty image!
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]
# Final image: ~15MB vs 800MB with full Go image
Terminal window
# Build specific stage
docker build --target builder -t myapp:build .
docker build --target runtime -t myapp:prod .

When to use:

  • Compiled languages (Go, Java, Rust, C++)
  • Frontend apps (build with Node, serve with nginx)
  • Whenever build tools shouldn’t be in production

Q45: How do you handle Docker resource cleanup?

Section titled “Q45: How do you handle Docker resource cleanup?”

Answer:

Terminal window
# Remove stopped containers
docker container prune
# Remove unused images
docker image prune # Only dangling images
docker image prune -a # All unused images
# Remove unused volumes
docker volume prune
# Remove unused networks
docker network prune
# Nuclear option — remove everything unused
docker system prune -a --volumes
# WARNING: This removes ALL unused images, containers, networks, volumes
# Check disk usage
docker system df
docker system df -v # Verbose breakdown
# Automate cleanup in CI
docker rmi $(docker images -f "dangling=true" -q)

Q46: What is Docker Swarm and how does it differ from Kubernetes?

Section titled “Q46: What is Docker Swarm and how does it differ from Kubernetes?”

Answer:

AspectDocker SwarmKubernetes
Setupdocker swarm init (1 command)Complex (kubeadm, eksctl)
Scalingdocker service scaleHPA + manual
Load balancingBuilt-in (VIP)Requires Ingress controller
Rolling updatesBuilt-inBuilt-in
Config managementDocker secrets/configsConfigMap/Secrets
StorageVolumesPV/PVC/StorageClass
NetworkingOverlayCNI plugins
Learning curveLowHigh
Production useSmall teamsEnterprise
Terminal window
# Swarm setup
docker swarm init --advertise-addr 192.168.1.10
# Deploy stack (Compose file on Swarm)
docker stack deploy -c docker-compose.yml myapp
# Scale service
docker service scale myapp_web=5
# View services
docker service ls
docker service ps myapp_web

Q47: How do you implement Docker image vulnerability scanning in CI/CD?

Section titled “Q47: How do you implement Docker image vulnerability scanning in CI/CD?”

Answer:

# GitHub Actions — scan before push
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: table
exit-code: '1' # Fail pipeline if vulnerabilities found
ignore-unfixed: true # Ignore vulns with no fix
severity: CRITICAL,HIGH # Only fail on critical/high
- name: Push (only if scan passes)
if: success()
run: |
docker tag myapp:${{ github.sha }} myorg/myapp:latest
docker push myorg/myapp:latest

Q48: What is Docker’s Union File System?

Section titled “Q48: What is Docker’s Union File System?”

Answer: Docker uses a Union File System (overlayfs) to stack image layers on top of each other.

Image layers (read-only):
Layer 4: COPY app/ /app → /app/server.js
Layer 3: RUN npm install → /app/node_modules/
Layer 2: COPY package.json → /app/package.json
Layer 1: FROM node:18 → /usr/bin/node, /lib/, etc.
Container runtime adds:
Layer 5: Read-Write layer → Any file changes happen here
When you delete a file in a container:
A "whiteout" file is created in the RW layer
It masks the file from lower layers (doesn't actually delete from image)
Terminal window
# Inspect image layers
docker history myapp:1.0
docker inspect myapp:1.0 | jq '.[0].RootFS'
# View overlayfs mounts
docker inspect container_name | jq '.[0].GraphDriver'

Q49: How do you run Docker containers in production securely?

Section titled “Q49: How do you run Docker containers in production securely?”

Answer:

Terminal window
# Secure docker run command with all best practices
docker run \
--name myapp \
--user 1001:1001 \ # Non-root user
--read-only \ # Read-only root filesystem
--tmpfs /tmp:rw,noexec,nosuid \ # Writable temp dir, no exec
--cap-drop ALL \ # Drop all Linux capabilities
--cap-add NET_BIND_SERVICE \ # Add only what's needed
--security-opt no-new-privileges:true \
--security-opt seccomp=default \
--memory=512m \ # Memory limit
--memory-swap=512m \ # No swap
--cpus=1.0 \ # CPU limit
--pids-limit=100 \ # Limit process spawning
--restart=unless-stopped \ # Auto-restart policy
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
--health-cmd="curl -f http://localhost:8080/health" \
--health-interval=30s \
--network my-private-net \
--env-file /run/secrets/myapp.env \
myapp:1.5.0 # Pinned version, not :latest

Q50: What is the difference between Docker volumes and bind mounts in practice?

Section titled “Q50: What is the difference between Docker volumes and bind mounts in practice?”

Answer:

FeatureNamed VolumeBind Mount
Managed byDockerHost OS
Path/var/lib/docker/volumes/Any host path
Portable✅ Yes❌ Host-dependent
Backup/restoredocker volume commandsRegular file ops
PerformanceOptimized (Linux)Slightly slower
Best forProduction dataLocal development
/var/lib/docker/volumes/myapp-data/_data
# Named volume (production)
docker run -v myapp-data:/app/data postgres:15
# Bind mount (development - live reload)
docker run -v $(pwd)/src:/app/src -v $(pwd)/public:/app/public myapp
# Changes to ./src immediately reflected in container
# Read-only bind mount (config files)
docker run -v /host/config.json:/app/config.json:ro myapp
# Backup named volume
docker run --rm \
-v myapp-data:/source:ro \
-v $(pwd)/backup:/backup \
alpine tar czf /backup/data-$(date +%Y%m%d).tar.gz /source


Back to DevOps Q&A Index