Docker interview questions covering images, containers, Dockerfile, Compose, networking, volumes, registries, security, and production deployment.
Docker is a platform for packaging, shipping, and running applications in containers. A container includes the application code, runtime, system libraries, and configuration needed to run consistently across environments. Docker is useful because it reduces "works on my machine" problems and makes local development, CI, and deployment more repeatable.
An image is a read-only template that contains filesystem layers and metadata. A container is a running or stopped instance of an image with its own writable layer, process namespace, network settings, and runtime state. Example: nginx:latest is an image; the running nginx process created from it is a container.
docker pull nginx:latest
docker run --name web -p 8080:80 nginx:latest
docker ps
A virtual machine runs a full guest operating system on a hypervisor, while a Docker container shares the host kernel and isolates processes with namespaces and cgroups. Containers are usually faster to start and lighter than VMs, but they are not the same security boundary as a fully isolated VM.
A Dockerfile is a text file containing build instructions for creating a Docker image. It defines the base image, dependencies, working directory, copied files, environment variables, exposed ports, health checks, and startup command.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
The build context is the set of files sent to the Docker daemon during docker build. COPY and ADD can only use files from this context. A large build context slows builds and can accidentally include secrets, logs, node_modules, or test artifacts.
docker build -t my-api:1.0 .
.dockerignore excludes files from the build context. It makes builds faster, reduces image risk, and prevents accidental inclusion of sensitive or unnecessary files. Common entries include .git, node_modules, logs, local env files, coverage reports, and temporary files.
.git
node_modules
.env
coverage
*.log
Docker images are built from layers. Each instruction such as RUN, COPY, or ADD can create a layer. Layers are cached and reused between builds when inputs do not change. Good Dockerfiles order stable steps before frequently changing steps to improve build speed.
Docker reuses cached layers when the instruction and its inputs match a previous build. If an early layer changes, later layers usually need to rebuild. For example, copying package files and installing dependencies before copying application code lets dependency installation stay cached when only source files change.
COPY package*.json ./
RUN npm ci
COPY . .
A multi-stage build uses one stage to compile or build artifacts and another smaller stage to run the application. This keeps compilers, test tools, and source-only build dependencies out of the final image.
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
Use small trusted base images, multi-stage builds, .dockerignore, production-only dependencies, combined package manager cleanup, and avoid copying unnecessary files. Smaller images transfer faster, scan faster, and usually have fewer vulnerabilities.
A base image is the starting filesystem and runtime for your image. Choose one based on runtime compatibility, security updates, size, debugging needs, and team familiarity. Official images, slim variants, and distroless images are common choices, but every choice has tradeoffs.
Distroless images contain only the application and runtime dependencies, without a package manager or shell. They reduce attack surface and image size, but debugging inside the container is harder. They are best for mature production services with good logs and observability.
ENTRYPOINT defines the executable that always runs, while CMD provides default arguments or a default command. CMD is easier to override at docker run time. ENTRYPOINT is useful when the container should behave like a specific executable.
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]
COPY copies files from the build context into the image and is preferred for most cases because it is explicit. ADD can also extract local tar archives and fetch remote URLs, which can surprise maintainers. Use ADD only when you need its extra behavior.
EXPOSE documents which port the containerized application listens on. It does not publish the port to the host by itself. To publish a port, use docker run -p or Compose ports.
EXPOSE 3000
docker run -p 8080:3000 my-api
ports publishes container ports to the host, such as 8080:80. expose makes ports available to other services on the Compose network without publishing them to the host. Use expose for internal service communication and ports for host or browser access.
services:
api:
image: my-api
expose:
- "3000"
nginx:
image: nginx
ports:
- "8080:80"
Docker networking connects containers to each other and to external networks. The default bridge network gives containers private IPs and NAT. User-defined bridge networks provide built-in DNS so containers can reach each other by service or container name.
A bridge network is a virtual network on one Docker host. Containers attached to the same user-defined bridge can communicate by name. It is common for local Compose stacks where an app container connects to a database container by service name.
docker network create app-net
docker run --network app-net --name db postgres
docker run --network app-net --name api my-api
Volumes are Docker-managed storage used to persist data outside the container writable layer. They are useful for databases, uploaded files, and state that must survive container recreation. Volumes are managed by Docker and are more portable than host-specific bind mount paths.
docker volume create pgdata
docker run -v pgdata:/var/lib/postgresql/data postgres
A bind mount maps a specific host path into a container, which is useful for local development. A named volume is managed by Docker and is usually better for persistent service data. Bind mounts can expose host files and behave differently across machines, so use them carefully.
Environment variables configure runtime behavior such as ports, database URLs, feature flags, and log levels. They should not be baked into images if values differ by environment. Do not treat plain environment variables as a complete secret-management solution for high-sensitivity data.
docker run -e APP_ENV=production -e LOG_LEVEL=info my-api
Avoid putting secrets in Dockerfiles, image layers, build arguments, or source control. Use platform secret managers, Docker secrets in Swarm, Kubernetes Secrets, cloud secret stores, or mounted secret files. Also scan image history and CI logs because secrets can leak during builds.
Docker Compose defines and runs multi-container applications using a YAML file. It is useful for local development, integration tests, and simple environments with services such as app, database, Redis, and worker.
services:
api:
build: .
ports:
- "8080:3000"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
depends_on controls startup order, but it does not always mean the dependency is ready to accept traffic. A database container can be started but still initializing. Use health checks, retry logic, or wait-for readiness patterns for reliable startup.
Compose profiles let you enable optional services only when needed. For example, you can keep observability, admin tools, or seed containers out of the default local stack and start them with a profile.
services:
adminer:
image: adminer
profiles: ["tools"]
# docker compose --profile tools up
A health check tells Docker or an orchestrator whether a container is healthy. It should test the app behavior that matters, not just whether the process exists. A good health check is fast, reliable, and does not overload dependencies.
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
Restart policies control whether Docker restarts containers after they exit. Common policies include no, on-failure, unless-stopped, and always. They help recover from crashes, but they can also hide crash loops if logs and monitoring are poor.
docker run --restart unless-stopped my-api
Use docker logs for stdout and stderr from a container. Containers should write logs to stdout/stderr rather than files inside the container. In production, a logging driver or platform agent should collect and ship logs centrally.
docker logs api
docker logs -f --tail=100 api
Start with docker ps, docker logs, docker inspect, docker stats, and health status. If the image has a shell, docker exec can open one. For minimal or distroless images, debug through logs, temporary debug images, sidecars, or reproduced local builds.
docker exec -it api sh
docker inspect api
docker stats api
docker run creates a new container from an image and starts it. docker exec runs an additional command inside an already running container. Use docker run to launch a service or one-off container, and docker exec to inspect or operate inside a container that already exists.
docker run --name api my-api
docker exec -it api sh
docker inspect returns detailed JSON metadata about images, containers, networks, and volumes. It is useful for checking environment variables, mounts, network settings, health status, entrypoint, command, labels, and image IDs.
Resource limits constrain CPU and memory usage for a container. They prevent one container from consuming all host resources. If limits are too low, the app may be throttled or killed; if absent, noisy containers can affect other workloads.
docker run --memory 512m --cpus 1.0 my-api
If a container exceeds its memory limit, the kernel may terminate its process with an out-of-memory kill. The container exits, and a restart policy or orchestrator may restart it. Good monitoring should alert on OOM kills because restarts alone do not fix the underlying memory issue.
Running as non-root reduces the impact of container breakout risks and accidental file permission damage. It is not a complete security solution, but it is a strong baseline. Images should create a user and switch to it with USER.
RUN addgroup -S app && adduser -S app -G app
USER app
Use tools such as Docker Scout, Trivy, Grype, Snyk, or registry-native scanners. Scanning should happen in CI and before deployment. Also patch base images regularly because many findings come from OS packages in the base layer.
trivy image my-api:1.0
A registry stores and distributes Docker images. Docker Hub, GitHub Container Registry, Amazon ECR, Google Artifact Registry, and Azure Container Registry are common examples. Production registries should use authentication, immutable tags or digests, scanning, and retention policies.
Use meaningful immutable tags such as git SHA, semantic version, or build number for deployments. Avoid relying on latest in production because it is mutable and makes rollbacks unclear. Many teams push both a version tag and a git SHA tag.
docker build -t registry.example.com/api:1.4.2 -t registry.example.com/api:git-a1b2c3d .
docker push registry.example.com/api:1.4.2
ARG is available during image build and is not automatically available at runtime. ENV is stored in the image and available to running containers. Do not pass secrets with ARG because values can appear in image history or build logs.
ARG NODE_VERSION=20
ENV NODE_ENV=production
Container immutability means containers should not be manually changed after they start. Instead of patching a running container, build a new image and replace the container. This makes deployments reproducible and rollbacks safer.
Containers should handle SIGTERM, stop accepting new work, finish in-flight requests within a timeout, close connections, and exit cleanly. This matters during deployments, scaling, and host shutdowns. Also avoid shell wrappers that fail to pass signals to the real application process.
The main process in a container runs as PID 1 and has special signal and child-process behavior. If it does not handle signals correctly or reap child processes, shutdown and zombie-process issues can happen. Use exec-form CMD or a tiny init such as tini when needed.
CMD ["node", "server.js"]
Labels attach metadata to images, containers, networks, and volumes. They are useful for ownership, version, source repository, build date, environment, automation, and cleanup rules.
LABEL org.opencontainers.image.source="https://github.com/example/api"
LABEL org.opencontainers.image.version="1.4.2"
Use prune commands to remove unused containers, images, networks, volumes, and build cache. Be careful with volumes because deleting them can remove persistent data. In CI, cleanup helps keep build machines from running out of disk.
docker system prune
docker image prune -a
docker volume prune
BuildKit is Docker modern build engine. It improves performance, caching, output, secrets handling, SSH forwarding, and multi-platform builds. It is commonly used through docker buildx for advanced CI builds.
Multi-platform builds create images for different CPU architectures such as linux/amd64 and linux/arm64. This matters when developers use Apple Silicon but production runs on amd64, or when images must support multiple deployment targets.
docker buildx build --platform linux/amd64,linux/arm64 -t my-api:1.0 --push .
Docker makes CI/CD builds repeatable by packaging dependencies and runtime into an image. A typical pipeline builds the image, runs tests, scans vulnerabilities, tags the image, pushes it to a registry, and deploys that immutable artifact to staging or production.
Docker builds and runs containers, while Kubernetes orchestrates containers across clusters. Kubernetes can run OCI-compatible images produced by Docker builds. In interviews, explain that Docker solves packaging and local runtime concerns, while Kubernetes handles scheduling, scaling, service discovery, rollout, and self-healing.
Common mistakes include running as root, using stale base images, copying secrets into images, exposing unnecessary ports, using latest in production, giving containers excessive privileges, mounting the Docker socket, ignoring image scans, and relying on containers as a complete security boundary.
Common anti-patterns include installing dependencies on every container start, storing persistent data inside the container writable layer, building huge images, mixing multiple unrelated services in one container, using bind mounts in production without reason, ignoring health checks, and manually changing running containers.
A strong demo uses a small Dockerfile, .dockerignore, non-root runtime where possible, clear port mapping, environment-based configuration, and a health endpoint. Then show how to build, run, inspect logs, and stop the container.
docker build -t demo-api .
docker run --rm --name demo-api -p 8080:3000 demo-api
docker logs -f demo-api
Explore 500+ free tutorials across 20+ languages and frameworks.