Docker Interview Questions and Answers Part 01
All Interview questions related to Docker tool
🟢 Basic
Short answer: Docker packages an application and everything it needs into a portable unit called a container.
That package can run in development, testing, and production with the same application files, runtime, libraries, and configuration. This reduces environment-related surprises such as “it works on my machine.”
Containers share the host operating system kernel, so they usually start faster and use fewer resources than virtual machines. They also make it easy to run several isolated application instances on one host.
Interview example: I can build a Node.js image once, test that image in CI, and deploy the same image to production. The runtime setup is no longer recreated manually on each server.
Short answer: An image is the packaged template; a container is an instance created from that image.
A Docker image is a read-only package containing the application, its dependencies, and the runtime configuration. Images are stored locally or in a registry.
A container is a running or stopped instance of an image. It gets a thin writable layer, so changes made inside it are lost when the container is removed unless data is stored in a volume or bind mount.
Think of an image as a blueprint and a container as a house built from that blueprint. One image can create many containers.
| Image | Container | |
|---|---|---|
| Definition | Read-only blueprint/template | Running instance of an image |
| State | Static (immutable) | Dynamic (can be started/stopped/deleted) |
| Storage | Stored on disk | Lives in memory + writable layer |
Short answer: A Dockerfile is a text file containing the repeatable instructions Docker uses to build an image.
It describes the base image, working directory, files to copy, dependencies to install, runtime settings, and default startup command. Docker executes those instructions in order and creates reusable image layers.
Example of Dockerfile:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]
Common instructions include FROM, WORKDIR, COPY, RUN, ENV, EXPOSE, USER, ENTRYPOINT, and CMD. EXPOSE documents the listening port; it does not publish that port by itself.
Short answer: Build an image from the Dockerfile, then start a container from the image.
Step 1: Build the image
docker build -t my-app:1.0 .
The final . is the build context, usually the current directory.
Step 2: Run a container
docker run -d --name my-app-container -p 8080:3000 my-app:1.0
Here -d runs in the background, --name gives the container a predictable name, and -p 8080:3000 maps host port 8080 to container port 3000.
Short answer: Docker Hub is a public container registry used to find, store, and share Docker images.
It hosts maintained official images such as nginx, postgres, and node, as well as images published by individuals and organizations. Private registries are often preferred for internal production images.
# Authenticate to Docker Hub. Use a token rather than a password where supported.
docker login
# Tag your image for Docker Hub
docker tag my-app:1.0 yourusername/my-app:1.0
# Push to Docker Hub
docker push yourusername/my-app:1.0
docker pull yourusername/my-app:1.0
An image name normally follows registry/namespace/repository:tag. If the registry is omitted, Docker uses Docker Hub by default.
Short answer: Docker is a workflow made up of a few connected parts:
- Docker client and CLI: The
dockercommand sends requests to the Docker daemon. - Docker daemon: The engine that builds images and creates, runs, and manages containers, networks, and volumes.
- Dockerfile: The build instructions for an image.
- Image: An immutable package used to create containers.
- Container: An isolated process created from an image.
- Registry: A service such as Docker Hub, Amazon ECR, or Harbor that stores and distributes images.
- Compose: A tool for defining and running related containers from a YAML file.
The Docker CLI and daemon may run on the same machine, but the client can also communicate with a remote daemon through a configured Docker context.
🟡 Intermediate
Short answer: Docker Compose lets you define an application made up of multiple containers in one YAML file and manage it with one command.
It is especially useful for local development, integration testing, and small controlled environments. A Compose file can describe services, networks, volumes, ports, and environment variables. Compose does not turn a single Docker host into a production cluster; use an orchestrator when you need scheduling and high availability across many nodes.
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pg_data:/var/lib/postgresql/data
volumes:
pg_data:
docker compose up -d # Start all services
docker compose down # Stop and remove containers
docker compose logs -f # Follow logs
docker compose ps # Status of services
Short answer: Both keep data outside a container’s writable layer, but Docker manages a volume while the host manages a bind mount.
Volumes are named storage areas managed by Docker. They are a good default for databases and other application data because the container does not need to know the host’s directory layout.
Bind mounts map a specific host path into the container. They are convenient for local development, source-code sharing, and configuration files, but they couple the container to that host path.
In short, choose a volume when Docker should manage persistent application data, and choose a bind mount when you intentionally need a host file or directory.
| Volume | Bind Mount | |
|---|---|---|
| Managed by | Docker | Host OS |
| Path | /var/lib/docker/volumes/ | Any path on host |
| Portability | High | Low |
| Use case | Production, databases | Local dev, live reload |
Tip: Persistence is not the same as backup. Back up a volume separately; deleting the volume deletes its data.
Short answer: Docker networking controls how containers reach one another, the host, and external services.
For application stacks, create a user-defined network and let containers find one another by service or container name. Avoid relying on container IP addresses because they can change when a container is recreated. Publishing a port with -p is only needed when a host or external client must reach the container.
- Docker Network Types:
- Bridge
- Host
- None
- Overlay
| Driver | Description | Use Case |
|---|---|---|
bridge | This is the default network. Containers on the same bridge network can communicate with each other, and we can expose ports to access them from the host. | Most containers |
host | the container shares the host’s network directly, so there’s no isolation. It’s faster, but less secure because the container uses the host’s ports. | Performance-critical apps |
none | In this case, the container has no network access at all. It’s completely isolated. | Fully isolated tasks |
overlay | Connects containers across Docker hosts when used with Swarm. | Docker Swarm |
Kubernetes has its own networking model; do not assume Docker’s overlay driver is how Kubernetes networking works.
Short answer: A multi-stage build separates compiling the application from running it. The final stage copies only the runtime artifacts from the build stage.
This keeps compilers, package caches, source code, and development dependencies out of the production image. Smaller images generally download faster and have a smaller attack surface.
# --- Stage 1: Build ---
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # Produces /app/dist
# --- Stage 2: Production ---
FROM node:18-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist # Only copy build output
EXPOSE 3000
CMD ["node", "dist/server.js"]
Result: The final image contains the production dependencies and build output, but not the build toolchain.
docker build --target production -t my-app:prod .
Short answer: Pass non-sensitive configuration at runtime, so the same image can be promoted through environments without rebuilding it.
docker run -e NODE_ENV=production -e PORT=3000 my-app
docker run --env-file .env my-app
# Compose configuration
services:
app:
image: my-app
environment:
NODE_ENV: production
PORT: "3000"
env_file:
- .env.production
Use --env-file or Compose env_file for ordinary configuration, but do not commit passwords or API keys. Environment variables can be exposed through process inspection and logs; use Docker secrets or an external secrets manager for sensitive values.
Short answer: Prefer COPY for ordinary local files. Use ADD only when you deliberately need one of its extra behaviors, such as extracting a local tar archive.
| Feature | COPY | ADD |
|---|---|---|
| Local files and directories | Yes | Yes |
| Remote URL or Git source | No | Supported in some Dockerfile builders, but usually better handled explicitly with RUN curl or git |
| Local tar extraction | No | Yes, when the source is a supported local tar archive |
| Typical recommendation | Default choice | Use only when the extra behavior is intentional |
# Preferred - explicit and predictable
COPY ./src /app/src
COPY package.json /app/
# Use ADD only when you need tar extraction
ADD app.tar.gz /app/
# Avoid ADD for URLs - use curl/wget in RUN instead
RUN curl -o /tmp/file.zip https://example.com/file.zip
Best practice: Prefer COPY unless you specifically need ADD’s extra features.
Short answer: Start with the container state and logs, then inspect its configuration, resources, processes, and filesystem.
Use either the container ID or its name in these commands:
# Get a shell inside a running container
docker exec -it <container_id> /bin/sh # Alpine/minimal images
docker exec -it <container_id> /bin/bash # Debian/Ubuntu images
# View real-time logs
docker logs -f <container_id>
docker logs --tail 100 <container_id>
# Inspect container config, network, mounts
docker inspect <container_id>
# Resource usage (CPU, memory, I/O)
docker stats <container_id>
# Copy files out of a container
docker cp <container>:/app/logs/error.log ./error.log
# View processes inside container
docker top <container_id>
# View filesystem changes since container started
docker diff <container_id>
Short answer: List resources first, stop containers gracefully, and remove only resources you no longer need.
# --- Containers ---
docker ps # List running containers
docker ps -a # List all containers (including stopped)
docker stop <name|id> # Gracefully stop a container
docker kill <name|id> # Force stop a container
docker rm <name|id> # Remove a stopped container
docker rm -f <name|id> # Force remove (even if running)
# --- Images ---
docker images # List all images
docker rmi <image_id> # Remove an image
docker image prune # Remove all unused images
# Remove unused resources. Review the prompt carefully; this can remove images.
docker system prune -a
docker kill stops a process immediately, while docker stop gives it time to shut down cleanly. docker rm removes a container, not its image or named volumes.
Short answer: Stopping a container does not remove its writable layer, so the data is still present when that same container starts again. However, that data is tied to the container and can be lost when the container is removed.
For data that must survive container replacement, store it in a named volume, bind mount, or external database. Do not treat a container’s writable layer as a backup.
Short answer: Stop the container gracefully, then remove it.
docker container stop <container_id or container_name>
docker rm <container_id or container_name>
For a disposable container, docker rm -f <container_id or container_name> stops and removes it, but use that only when a graceful shutdown is not required.
Short answer: Yes, if a restart policy is configured. The default policy is no, so a container does not restart automatically after it exits.
Docker provides built-in restart policies:
no→ (default) never restartalways→ always restart if stopped/crashedunless-stopped→ restart unless you manually stop iton-failure→ restart only if container exits with error
Example:
docker run -d --restart=always nginx
Short answer: Unpause it first, then stop and remove it. A paused container is still considered running by Docker.
docker unpause <container_id or container_name>
docker stop <container_id or container_name>
docker rm <container_id or container_name>
Short answer: Use a named volume for Docker-managed data and a bind mount when you need to map a specific host path.
# Named volume — Docker manages the storage location
docker volume create app_data
docker run -d --name myapp -v app_data:/var/lib/app/data nginx
# Same thing with the newer, more explicit --mount syntax
docker run -d --name myapp \
--mount source=app_data,target=/var/lib/app/data \
nginx
# Bind mount — map an exact host path into the container
docker run -d --name myapp \
-v /home/user/app/config:/etc/app/config:ro \
nginx
# ":ro" makes it read-only inside the container
# --mount equivalent (more explicit, catches typos in flags earlier)
docker run -d --name myapp \
--mount type=bind,source=/home/user/app/config,target=/etc/app/config,readonly \
nginx
# Inspect what's actually mounted on a running container
docker inspect myapp --format '{{json .Mounts}}' | python3 -m json.tool
Quick reference: -v is shorter; --mount is more explicit and easier to review in scripts. Add :ro or readonly when the container only needs to read the mounted data.
🔴 Advanced
Short answer: Docker stores image build results as layers and reuses a layer when the instruction and the files it depends on have not changed.
When a layer changes, later layers must be rebuilt. Put stable, expensive steps before frequently changing application code.
# Poor cache usage: any source change forces dependency installation again.
FROM node:18-alpine
WORKDIR /app
COPY . . # Invalidated on ANY file change
RUN npm install # Reinstalls everything each time!
# Better cache usage: dependency installation changes only when dependency files change.
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./ # Only changes when deps change
RUN npm install # Cached until package.json changes
COPY . . # App code changes don't bust npm cache
RUN npm run build
Other useful practices: keep the build context small with .dockerignore, order instructions from least-changing to most-changing, use multi-stage builds, and use BuildKit cache mounts for package-manager caches.
# BuildKit cache mount for faster builds
RUN --mount=type=cache,target=/root/.npm \
npm ci
Short answer: Docker Swarm and Kubernetes both schedule and manage containers across multiple machines. Swarm is simpler and closely integrated with Docker, while Kubernetes provides a broader platform and ecosystem for complex workloads.
| Feature | Docker Swarm | Kubernetes |
|---|---|---|
| Setup complexity | Simple | Complex |
| Learning curve | Low | High |
| Scalability | Moderate | Enterprise-grade |
| Auto-healing | Basic | Advanced |
| Ecosystem | Docker-native | Massive (CNCF) |
| Typical fit | Smaller teams and simpler services | Complex, large-scale, or multi-team platforms |
# Initialize a Swarm cluster
docker swarm init --advertise-addr <manager-ip>
# Deploy a stack (like docker-compose for Swarm)
docker stack deploy -c docker-compose.yml myapp
# Scale a service
docker service scale myapp_web=5
# List services
docker service ls
# Rolling update
docker service update --image my-app:2.0 myapp_web
How I would answer in an interview: I would choose based on operational requirements, team expertise, ecosystem needs, and the platform already used by the organization. Kubernetes is not automatically better for every small deployment, and Swarm is not a substitute for Kubernetes-specific features.
Short answer: Keep secrets out of images, source control, and ordinary configuration wherever possible. Inject them at runtime through Docker secrets or an external secrets manager.
Environment variables are convenient for configuration, but they can be exposed through process inspection, debugging output, or accidental logging. They are not a complete secrets-management system.
Option 1: Docker Secrets (Swarm mode)
# Create a secret
echo "super_secret_password" | docker secret create db_password -
# Use in a service
docker service create \
--name myapp \
--secret db_password \
my-app:latest
Inside the container, secrets are available at /run/secrets/db_password.
Option 2: Docker Compose secrets (for dev)
services:
app:
image: my-app
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt # Never commit this file!
Option 3: External secrets managers
# Examples: HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault.
# Fetch or inject the value at runtime using the platform's supported integration.
Good practices: add local secret files to .gitignore, rotate credentials, grant least privilege, audit access, and make sure secrets do not appear in image layers or CI logs.
Short answer: Reduce the image attack surface, run with the least privilege possible, restrict capabilities, and scan both the image and the runtime configuration.
FROM node:18.19.0-alpine3.19 # Pin the base image; avoid :latest.
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
For applications that support it, Alpine or a distroless image can reduce the attack surface. Distroless images usually do not include a shell, so choose them only when your debugging and operational workflow supports that limitation.
# Scan image for vulnerabilities
docker scout cves my-app:latest
# or
trivy image my-app:latest
# Run with a read-only filesystem and fewer privileges.
docker run \
--read-only \
--no-new-privileges \
--cap-drop ALL \
my-app:latest
Also keep the Docker daemon and host patched, use trusted and pinned base images, define CPU and memory limits, and review the image’s packages. A minimal image alone does not make a container secure.
Short answer: Containers are isolated processes, not separate virtual machines. Docker relies on Linux kernel features to isolate processes and control their resources.
Namespaces — provide isolation of system resources:
| Namespace | Isolates |
|---|---|
pid | Process IDs (container can’t see host processes) |
net | Network interfaces, IP tables, ports |
mnt | Filesystem mount points |
uts | Hostname and domain name |
ipc | Inter-process communication |
user | User and group IDs |
Control groups (cgroups) limit and account for resource usage:
# Limit container to 512MB RAM and 1 CPU
docker run \
--memory="512m" \
--memory-swap="512m" \
--cpus="1.0" \
my-app
# View the limits Docker configured for a container
docker inspect <container_id> --format '{{json .HostConfig.Memory}}'
docker stats <container_id>
OverlayFS and image layers store image content efficiently. Read-only image layers can be shared by multiple containers, while each running container gets its own thin writable layer.
# See the layers of an image
docker history my-app:latest
# Inspect the storage driver
docker inspect my-app --format '{{json .GraphDriver}}'
The exact cgroup paths and storage-driver details depend on the host OS, Docker version, and whether the host uses cgroup v1 or v2.
Short answer: Run the new version alongside the old version, verify its health, move traffic gradually or switch it over, and keep the old version available for rollback.
Strategy 1: Swarm rolling update
docker service update \
--image my-app:2.0 \
--update-parallelism 1 \
--update-delay 10s \
--update-failure-action rollback \
myapp_web
Strategy 2: Blue-green with Nginx and Compose
# docker-compose.blue-green.yml
services:
app-blue:
image: my-app:1.0
networks: [proxy]
app-green:
image: my-app:2.0
networks: [proxy]
nginx:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
networks: [proxy]
# 1. Start green alongside blue.
docker compose up -d app-green
# 2. Verify green directly or through a health check.
curl http://localhost/health
# 3. Point the Nginx upstream at green, then reload Nginx.
docker exec nginx nginx -s reload
# 4. Remove blue only after green is stable.
docker compose stop app-blue
Strategy 3: Kubernetes Deployment rollout
kubectl set image deployment/my-app my-app=my-app:2.0
kubectl rollout status deployment/my-app
kubectl rollout undo deployment/my-app # Rollback if needed
Short answer: ENTRYPOINT defines the main executable; CMD supplies default arguments or a default command.
| CMD | ENTRYPOINT | |
|---|---|---|
| Syntax | CMD [“executable”,“param1”,“param2”] | ENTRYPOINT [“executable”,“param1”,“param2”] |
| Purpose | Default command or default arguments | Main executable |
| Override at runtime | Extra arguments replace the default command in exec-form examples | Use docker run --entrypoint to replace it |
| Common pairing | CMD ["server.js"] | ENTRYPOINT ["node"] |
# CMD - easily overridden
CMD ["node", "server.js"]
# ENTRYPOINT - fixed executable
ENTRYPOINT ["node"]
CMD ["server.js"] # default arg, can be overridden
# Run: docker run my-app debug.js
# Executes: node debug.js
Use exec form (["..."]) when possible so Docker can deliver signals correctly to the application. A common pattern is ENTRYPOINT for the executable and CMD for its default arguments.
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form