Docker Scenario Interview Questions and Answers
35 real-world Docker scenario-based interview questions and answers covering containers, networking, volumes, Docker Compose, security, performance troubleshooting, and CI/CD — Beginner to Advanced.
You would containerize the Node.js app so the developer only needs Docker installed.
Step 1 — Create a Dockerfile in the project root:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
Step 2 — Build the image:
docker build -t my-node-app .
Step 3 — Run the container:
docker run -p 3000:3000 my-node-app
The developer can now access the app at http://localhost:3000 without installing Node.js locally.
List all running containers to find the container ID or auto-generated name:
docker ps
Then stop it using the container ID or name:
docker stop <container_id_or_name>
Always name your containers using the --name flag to avoid this situation:
docker run --name my-app -d nginx
Step 1 — View container logs:
docker logs <container_id>
Step 2 — List exited containers:
docker ps -a
Step 3 — Inspect the exit code:
docker inspect <container_id> --format='{{.State.ExitCode}}'
Exit code reference:
| Code | Meaning |
|---|---|
0 | Exited normally |
1 | Application error |
137 | Killed (OOM or SIGKILL) |
125 | Docker daemon error |
126 | Permission denied |
127 | Command not found |
Step 4 — Run interactively to debug:
docker run -it <image> /bin/sh
Several techniques reduce image size significantly:
1. Use a smaller base image:
# Instead of:
FROM node:18
# Use:
FROM node:18-alpine
2. Use multi-stage builds (most impactful):
# Build stage — heavy, has all build tools
FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
# Production stage — only the output
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
3. Combine RUN commands to reduce layers:
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
4. Use .dockerignore to exclude unnecessary files:
node_modules
.git
*.log
tests/
.env
docker history my-image to see which layers are biggest and target those first.Use bind mounts to share host directories with the container:
docker run -v /host/path:/container/path my-image
Example — sharing a local src folder for live development:
docker run -v $(pwd)/src:/app/src -p 3000:3000 my-node-app
Changes made on the host are reflected instantly inside the container — perfect for development.
For production data persistence, use named volumes instead:
docker volume create mydata
docker run -v mydata:/app/data my-image
| Type | Use Case | Persists After Container Removed? |
|---|---|---|
| Bind mount | Development, sharing host files | Yes (lives on host) |
| Named volume | Production data, databases | Yes (managed by Docker) |
| tmpfs | Sensitive/temporary data in RAM | No |
Use a Docker network so containers can communicate by name:
# Step 1 — Create a custom network
docker network create myapp-network
# Step 2 — Run MySQL on the network
docker run -d \
--name mysql-db \
--network myapp-network \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=appdb \
mysql:8
# Step 3 — Run the app on the same network
docker run -d \
--name my-app \
--network myapp-network \
-e DB_HOST=mysql-db \
-e DB_PORT=3306 \
my-app-image
The app container can reach MySQL using the hostname mysql-db because they share the same network. Docker’s built-in DNS resolves container names automatically.
docker network commands needed.Use environment variables to inject config at runtime, keeping the same image across all environments. This follows the 12-factor app methodology.
# Development
docker run -e ENV=dev -e DB_URL=dev-db:5432 my-app
# Staging
docker run -e ENV=staging -e DB_URL=staging-db:5432 my-app
# Production
docker run -e ENV=prod -e DB_URL=prod-db:5432 my-app
Or use an env file per environment:
# dev.env
ENV=dev
DB_URL=dev-db:5432
LOG_LEVEL=debug
docker run --env-file dev.env my-app
Use Docker resource constraints at container startup:
# Limit memory to 512MB and CPU to 0.5 cores
docker run \
--memory=512m \
--memory-swap=512m \
--cpus=0.5 \
my-app
Resource flag reference:
| Flag | Description |
|---|---|
--memory | Max RAM the container can use |
--memory-swap | Max swap (set equal to --memory to disable swap) |
--cpus | Number of CPUs (fractional values allowed) |
--cpu-shares | Relative CPU weight (default 1024) |
Monitor live resource usage:
docker stats
docker stats --no-stream # One-shot snapshot
Never modify a running container directly. Follow the immutable infrastructure pattern — replace, don’t patch:
# Step 1 — Build a new image with a version tag
docker build -t my-app:v2.0 .
# Step 2 — Stop and remove the old container
docker stop my-app-container
docker rm my-app-container
# Step 3 — Start a fresh container with the new image
docker run -d --name my-app-container -p 3000:3000 my-app:v2.0
v2.0, git SHA) in addition to latest. This gives you a clear history and the ability to roll back instantly by running the previous tag.Use docker cp:
# Copy FROM container TO host
docker cp <container_name>:/path/in/container /path/on/host
# Real example — pull logs from a running container
docker cp my-app:/app/logs/app.log ./app.log
Copy from host TO container:
docker cp ./config.json my-app:/app/config.json
docker cp works even if the container is stopped — you don’t need to start it just to retrieve a file.Docker provides a granular prune system for each resource type:
# Remove all stopped containers
docker container prune
# Remove dangling images (untagged)
docker image prune
# Remove ALL unused images (not just dangling)
docker image prune -a
# Remove unused volumes
docker volume prune
# Remove unused networks
docker network prune
# Nuclear option — clean everything unused at once
docker system prune -a --volumes
Check disk usage before and after:
docker system df
docker system prune --volumes removes ALL unused volumes including ones with data you may still need. Always run docker system df first to see what will be affected.Use Docker Buildx for multi-platform builds:
# Step 1 — Create and activate a multi-platform builder
docker buildx create --name multibuilder --use
# Step 2 — Build and push for both platforms in one command
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myrepo/my-app:latest \
--push .
This creates a manifest list (multi-arch image) so Docker automatically pulls the correct image for the host’s architecture — the same tag works on both Intel Macs and Apple Silicon.
Verify the manifest:
docker buildx imagetools inspect myrepo/my-app:latest
Use Docker restart policies:
docker run -d --restart=unless-stopped my-app
Restart policy reference:
| Policy | Behaviour |
|---|---|
no | Never restart (default) |
on-failure | Restart only on non-zero exit code |
on-failure:5 | Restart up to 5 times, then stop |
always | Always restart, even after docker stop |
unless-stopped | Always restart unless manually stopped |
unless-stopped for long-running services. It survives host reboots and Docker daemon restarts but respects intentional docker stop commands from operators.Option 1 — Docker Secrets (Swarm mode):
# Create a secret
echo "supersecretpassword" | docker secret create db_password -
# Use it in a service
docker service create \
--secret db_password \
my-app
Inside the container, the secret is available at /run/secrets/db_password as a file.
Option 2 — Mounted secret files (standalone containers):
docker run \
-v /host/secrets/db_pass.txt:/run/secrets/db_password:ro \
my-app
Option 3 — External secrets managers (recommended for production):
- AWS Secrets Manager — fetch at runtime via IAM role, no credentials in container
- HashiCorp Vault — dynamic secrets with lease-based rotation
- Azure Key Vault — managed identity access
-e MY_SECRET=value. Environment variables are visible in docker inspect output, appear in process listings, and can leak through application logs.Step 1 — Gather evidence without touching the container:
# Inspect container metadata
docker inspect <container_id>
# Check running processes
docker top <container_id>
# View recent logs
docker logs --tail=200 <container_id>
Step 2 — Check for filesystem changes:
docker diff <container_id>
docker diff output codes:
A= file addedC= file changedD= file deleted
Unexpected binaries in /tmp, new cron jobs, or modified /etc/passwd are red flags.
Step 3 — Check network connections:
docker exec <container_id> netstat -tulnp
Step 4 — Export filesystem for forensic analysis:
docker export <container_id> > compromised.tar
Step 5 — Quarantine: disconnect from all networks:
docker network disconnect mynetwork <container_id>
This isolates the container from other services while preserving its state for investigation.
Optimization 1 — Order Dockerfile layers by change frequency:
# Rarely changes — cached unless package.json changes
COPY package*.json ./
RUN npm install
# Changes on every push — always rebuilds from here
COPY . .
RUN npm run build
Optimization 2 — Enable BuildKit:
DOCKER_BUILDKIT=1 docker build .
Optimization 3 — Use BuildKit cache mounts:
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm \
npm install
The npm cache persists between builds — dramatically speeds up dependency installs.
Optimization 4 — Use a registry cache backend in CI:
docker buildx build \
--cache-from=type=registry,ref=myrepo/my-app:cache \
--cache-to=type=registry,ref=myrepo/my-app:cache,mode=max \
-t myrepo/my-app:latest .
Optimization 5 — Use GitHub Actions layer cache:
- name: Build and push
uses: docker/build-push-action@v4
with:
cache-from: type=gha
cache-to: type=gha,mode=max
Use multiple isolated networks to enforce traffic boundaries:
# Create two separate networks
docker network create frontend-net
docker network create backend-net
# Frontend: only on frontend-net
docker run -d --name frontend --network frontend-net frontend-image
# Backend: bridge between both networks
docker run -d --name backend backend-image
docker network connect frontend-net backend
docker network connect backend-net backend
# Database: only on backend-net
docker run -d --name database --network backend-net db-image
Result:
| Connection | Allowed? |
|---|---|
frontend → backend | ✅ Yes (share frontend-net) |
backend → database | ✅ Yes (share backend-net) |
frontend → database | ❌ No (no shared network) |
The database is completely invisible to the frontend — there is no route to it, even if an attacker compromises the frontend container.
Bind to 127.0.0.1 (localhost) instead of 0.0.0.0:
docker run -p 127.0.0.1:3000:3000 my-app
The app is reachable at http://localhost:3000 on the host but not accessible from other machines on the network.
Comparison:
# Binds to 0.0.0.0 — accessible from any machine on the network ⚠️
docker run -p 3000:3000 my-app
# Binds to localhost only — private to the host machine ✅
docker run -p 127.0.0.1:3000:3000 my-app
-p 3000:3000 binds to 0.0.0.0, meaning the port is open to the entire network. Always use 127.0.0.1: prefix for internal services like admin panels, metrics endpoints, or dev servers.Connect one container to the other’s network:
docker network connect network-b container-a
Or create a shared network and connect both:
docker network create shared-net
docker network connect shared-net container-a
docker network connect shared-net container-b
A container can belong to multiple networks simultaneously, so connecting to an additional network does not remove it from its existing networks.
Verify connectivity:
# Check which networks container-a is on
docker inspect container-a --format='{{json .NetworkSettings.Networks}}' | jq .
# Test from inside container-a
docker exec container-a ping container-b
Use named volumes for persistent data storage that survives container restarts and deletion:
# Create a named volume
docker volume create pgdata
# Run PostgreSQL with persistent volume
docker run -d \
--name postgres \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:15
Even if the container is deleted, the volume persists:
docker rm postgres # Container is gone
docker volume ls # pgdata still exists ✅
Recreate the container pointing to the same volume to recover instantly:
docker run -d --name postgres -v pgdata:/var/lib/postgresql/data postgres:15
Backup the volume to a file:
docker run --rm \
-v pgdata:/data \
-v $(pwd):/backup \
alpine tar czf /backup/pgdata-backup.tar.gz /data
Mount the same directory or volume into multiple containers simultaneously:
Using bind mount (host directory):
docker run -d --name app1 -v $(pwd)/config:/etc/config:ro app-image
docker run -d --name app2 -v $(pwd)/config:/etc/config:ro app-image
docker run -d --name app3 -v $(pwd)/config:/etc/config:ro app-image
Using a named volume:
docker volume create shared-config
# Pre-populate the volume
docker run --rm -v shared-config:/config -v $(pwd)/config:/src alpine cp -r /src/. /config/
# Mount read-only in all app containers
docker run -d --name app1 -v shared-config:/etc/config:ro app-image
docker run -d --name app2 -v shared-config:/etc/config:ro app-image
:ro flag prevents any container from accidentally modifying shared configuration. Without it, one misbehaving container could corrupt config for all others.# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/appdb
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- app-net
restart: unless-stopped
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-net
redis:
image: redis:7-alpine
networks:
- app-net
volumes:
pgdata:
networks:
app-net:
Start all services:
docker compose up -d
# View running services
docker compose ps
# View logs for all services
docker compose logs -f
depends_on only waits for the container to start, not for the service inside it to be ready to accept connections. Fix this with healthchecks:
services:
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10
start_period: 10s # Grace period before health checks begin
app:
build: .
depends_on:
db:
condition: service_healthy # Wait until health check passes
For MySQL:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
For Redis:
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
Use Compose file overrides — a base file with environment-specific overrides layered on top:
# docker-compose.yml — base shared config
services:
web:
image: my-app
environment:
- NODE_ENV=production
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
# docker-compose.dev.yml — development overrides
services:
web:
build: . # Build from source instead of pulling image
volumes:
- .:/app # Live reload — mount source code
environment:
- NODE_ENV=development
ports:
- "9229:9229" # Node.js debugger port
# docker-compose.prod.yml — production overrides
services:
web:
image: myrepo/my-app:latest # Pull versioned image
restart: unless-stopped
deploy:
resources:
limits:
memory: 512m
Run with the appropriate override:
# Development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Later overrides win on any conflicting keys, and non-conflicting keys are merged.
Add a dedicated non-root user in your Dockerfile:
FROM node:18-alpine
# Create a non-root group and user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm install
# Switch to non-root user before CMD
USER appuser
CMD ["node", "index.js"]
Verify the container runs as non-root:
docker run my-app whoami
# appuser
Additionally, harden the container at runtime:
docker run \
--read-only \ # Immutable root filesystem
--no-new-privileges \ # Prevent privilege escalation via setuid
--cap-drop ALL \ # Drop all Linux capabilities
--cap-add NET_BIND_SERVICE \ # Add back only what you need
--security-opt=no-new-privileges \
my-app
securityContext.runAsNonRoot: true and securityContext.readOnlyRootFilesystem: true in your Pod spec.1. Enable Docker Content Trust (image signing):
# Enable globally in your shell or CI environment
export DOCKER_CONTENT_TRUST=1
# Push automatically signs the image
docker push myrepo/my-app:latest
2. Pin images to immutable digests (not mutable tags):
# Tags can be overwritten at any time — digests cannot
FROM node:18-alpine@sha256:a9e6b0b7a36c5da51d5a0d7c95bb3bba29c3b5e1bd0b5a1e9e9c9c0c0c0c0c0
3. Scan images for vulnerabilities before deploying:
# Docker Scout (built into Docker Desktop)
docker scout cves my-app:latest
# Trivy (popular open-source scanner)
trivy image my-app:latest
4. Use a private registry with access control:
- AWS ECR — IAM-based access, integrated with ECS/EKS
- Harbor — self-hosted, built-in Trivy scanning, policy enforcement
- JFrog Artifactory — enterprise registry with audit trails
Immediate response — move fast:
Step 1 — Revoke the exposed credentials immediately
- Change passwords, rotate API keys, revoke tokens — do this before anything else
Step 2 — Delete the image from Docker Hub
- Docker Hub → Repository → Tags → Delete the affected tag
- Note: Docker Hub does not support private image deletion via CLI — use the web UI
Step 3 — Push a clean replacement image
# Build without secrets
docker build -t myrepo/my-app:latest .
docker push myrepo/my-app:latest
Step 4 — Audit who pulled the image
- Check Docker Hub pull statistics and access logs
- Review cloud provider access logs for any use of the exposed credentials
Step 5 — Scan all other images for secrets
trufflehog docker --image myrepo/my-app:latest
Prevention going forward:
# .dockerignore — exclude secret files from build context
.env
*.pem
secrets/
credentials.json
# Pre-commit hook to detect secrets
pip install detect-secrets
detect-secrets scan > .secrets.baseline
Step 1 — Monitor in real time:
docker stats <container_id>
# Shows: CPU%, MEM usage/limit, NET I/O, BLOCK I/O
Step 2 — Inspect resource limits:
docker inspect <container_id> | grep -A 10 "HostConfig"
Step 3 — Profile inside the container:
docker exec -it <container_id> top # CPU and process list
docker exec -it <container_id> df -h # Disk space
docker exec -it <container_id> free -m # Memory breakdown
docker exec -it <container_id> iostat 1 5 # Disk I/O
Step 4 — Check for CPU throttling:
docker exec <container_id> cat /sys/fs/cgroup/cpu/cpu.stat
# throttled_time > 0 means container is CPU-constrained
Common fixes:
| Symptom | Fix |
|---|---|
| High CPU% | Increase --cpus, optimise app code |
| Memory near limit | Increase --memory, fix memory leak |
| Slow disk I/O | Use named volumes instead of bind mounts on Docker Desktop |
| High NET I/O | Check for chatty connections, enable keep-alive |
| Image too large | Multi-stage build, alpine base image |
Step 1 — Test basic connectivity from inside the container:
docker exec -it <container_id> ping 8.8.8.8
docker exec -it <container_id> curl https://google.com
Step 2 — Check DNS resolution:
docker exec -it <container_id> nslookup google.com
docker exec -it <container_id> cat /etc/resolv.conf
Step 3 — Inspect the container’s network configuration:
docker inspect <container_id> | grep -A 20 "Networks"
docker network inspect <network_name>
Step 4 — Check Docker daemon DNS config:
cat /etc/docker/daemon.json
Common causes and fixes:
| Cause | Fix |
|---|---|
| No network attached | docker network connect bridge <container> |
| DNS not resolving | Add "dns": ["8.8.8.8"] to /etc/docker/daemon.json |
| Host firewall blocking | Check iptables -L and ufw status on host |
Network marked --internal | Recreate network without --internal flag |
Docker daemon restarted and reset iptables | Restart the container |
# Restart Docker daemon after changing daemon.json
sudo systemctl restart docker
Step 1 — Check Docker’s disk usage:
docker system df
# Shows: images, containers, volumes, build cache sizes
Step 2 — Clean up aggressively:
# Remove stopped containers, unused images, unused networks, build cache
docker system prune -a --volumes
Step 3 — Check host disk space:
df -h
du -sh /var/lib/docker/* # Find what's using space in Docker's directory
Step 4 — Optimize Dockerfile to avoid the problem recurring:
# Clean package caches in the SAME RUN layer to avoid bloating intermediate layers
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
# Use multi-stage builds to discard build-time dependencies
FROM golang:1.21 AS builder
RUN go build -o app .
FROM alpine:3.18
COPY --from=builder /app . # Final image has no Go toolchain
Step 5 — Set up periodic cleanup on CI servers:
# Add to crontab: clean Docker resources weekly
0 3 * * 0 docker system prune -af --volumes
GitHub Actions example — build, test, push on every push to main:
# .github/workflows/docker.yml
name: Build and Push Docker Image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build image and run tests
run: |
docker build -t my-app:test .
docker run --rm my-app:test npm test
- name: Build and push final image
uses: docker/build-push-action@v5
with:
push: true
tags: |
myrepo/my-app:latest
myrepo/my-app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Best practices:
- Tag images with both
latestand the commit SHA for full traceability - Run tests before pushing — fail fast if they don’t pass
- Use GitHub Actions GHA cache for faster builds
- Store Docker Hub credentials as encrypted repository secrets — never hardcode them
docker scout cves or trivy image step between the test and push steps to block images with critical CVEs from reaching production.Use a blue-green deployment strategy with a reverse proxy (Nginx or Traefik) in front:
#!/bin/bash
# deploy.sh
NEW_IMAGE="my-app:v2"
OLD_CONTAINER="my-app-blue"
NEW_CONTAINER="my-app-green"
# Step 1 — Pull the new image
docker pull $NEW_IMAGE
# Step 2 — Start the green (new) container on a different port
docker run -d \
--name $NEW_CONTAINER \
-p 3001:3000 \
$NEW_IMAGE
# Step 3 — Wait and health check the new container
sleep 10
if curl -sf http://localhost:3001/health; then
echo "Green container is healthy. Switching traffic..."
# Step 4 — Update Nginx upstream to point to new container
# sed -i 's/3000/3001/' /etc/nginx/conf.d/app.conf && nginx -s reload
# Step 5 — Remove the old container
docker stop $OLD_CONTAINER
docker rm $OLD_CONTAINER
# Step 6 — Rename green to blue (canonical name)
docker rename $NEW_CONTAINER my-app-blue
echo "✅ Deployment successful!"
else
echo "❌ Health check failed. Rolling back..."
docker stop $NEW_CONTAINER
docker rm $NEW_CONTAINER
fi
docker service update --image my-app:v2 my-service performs rolling updates natively with configurable --update-parallelism and automatic rollback on health check failures.Backup — SQL dump:
# Dump a single database to a timestamped file
docker exec postgres pg_dump -U postgres mydb > backup_$(date +%Y%m%d_%H%M%S).sql
# Dump all databases
docker exec postgres pg_dumpall -U postgres > full-backup-$(date +%Y%m%d).sql
Backup — full volume snapshot:
# Spin up a temporary container to tar the volume
docker run --rm \
-v pgdata:/source:ro \
-v $(pwd)/backups:/backup \
alpine tar czf /backup/pgdata-$(date +%Y%m%d).tar.gz -C /source .
Restore — from SQL dump:
docker exec -i postgres psql -U postgres mydb < backup_20260407.sql
Restore — from volume snapshot:
docker run --rm \
-v pgdata:/target \
-v $(pwd)/backups:/backup \
alpine tar xzf /backup/pgdata-20260407.tar.gz -C /target
Automate with a host cron job:
# /etc/cron.d/postgres-backup
0 2 * * * root docker exec postgres pg_dumpall -U postgres \
> /backups/full-backup-$(date +\%Y\%m\%d).sql
Step 1 — Confirm it is OOM killed:
docker inspect <container_id> | grep -i oom
# "OOMKilled": true
# Check kernel logs for more detail
dmesg | grep -i "oom\|killed"
Step 2 — Monitor current memory usage:
docker stats --no-stream <container_id>
Step 3 — Choose a fix based on root cause:
Option A — Increase the memory limit (quick fix):
docker run --memory=2g --memory-swap=2g my-app
Option B — Fix an application memory leak (proper fix):
- Generate a heap dump and analyse with language-specific profilers
- Implement pagination instead of loading all records at once
- Tune garbage collection settings (e.g. for JVM:
-Xmx1g -XX:+UseG1GC)
Option C — Control eviction priority with OOM score:
# Critical service — less likely to be killed when host is under pressure
docker run --oom-score-adj=-500 critical-service
# Low priority batch job — kill this first
docker run --oom-score-adj=500 batch-job
--memory=512m without setting --memory-swap, Docker defaults to --memory-swap=1024m (double). Set --memory-swap equal to --memory to disable swap entirely, or explicitly higher to allow it.Approach 1 — Docker-in-Docker (true DinD):
docker run --privileged docker:dind
The inner container runs its own Docker daemon. Requires --privileged flag which grants near-full host access — a significant security risk.
Approach 2 — Docker socket mount (recommended for most CI use cases):
docker run -v /var/run/docker.sock:/var/run/docker.sock docker:cli
The inner container talks to the host’s Docker daemon directly. No privilege escalation needed. Simpler and faster, but the container has full control over all containers on the host.
Approach 3 — Kaniko (best for Kubernetes/restricted environments):
# Build Docker images without any Docker daemon access
- name: Build with Kaniko
image: gcr.io/kaniko-project/executor
args:
- --context=dir:///workspace
- --dockerfile=Dockerfile
- --destination=myrepo/my-app:latest
Kaniko builds images entirely in userspace — no daemon, no privileges, works inside unprivileged containers and Kubernetes pods.
Approach 4 — Podman (daemonless, rootless):
podman build -t my-app .
podman run my-app
Podman is a drop-in Docker replacement that runs without a daemon and supports rootless containers natively.
Risk comparison:
| Approach | Security Risk | Speed | Complexity |
|---|---|---|---|
DinD (--privileged) | 🔴 High | Fast | Medium |
| Socket mount | 🟡 Medium | Fast | Low |
| Kaniko | 🟢 Low | Medium | Medium |
| Rootless Podman | 🟢 Low | Fast | High |
Quick Reference Cheatsheet
# Container lifecycle
docker run -d --name app -p 8080:80 nginx # Run detached with name
docker start / stop / restart app # Control a container
docker rm -f app # Force remove running container
# Debugging
docker logs -f app # Follow logs in real time
docker exec -it app /bin/sh # Open a shell inside container
docker inspect app # Full metadata JSON
docker stats # Live CPU/memory/network usage
docker top app # Processes inside container
docker diff app # Filesystem changes vs image
# Images
docker build -t myapp:v1 . # Build from Dockerfile
docker pull / push myrepo/myapp:v1 # Registry operations
docker images # List local images
docker rmi myapp:v1 # Remove image
docker history myapp:v1 # Show layer breakdown
# Networks
docker network create mynet # Create a network
docker network connect mynet app # Connect container to network
docker network ls / inspect / rm # Manage networks
# Volumes
docker volume create mydata # Create a named volume
docker volume ls / inspect / rm # Manage volumes
docker run -v mydata:/app/data myapp # Mount volume in container
# Cleanup
docker system prune -a --volumes # Remove everything unused
docker system df # Show disk usage breakdown
Add More Questions to This Guide
Know questions that should be here? Share them and help the community!
Open Google Form