Prepare for Docker interview questions grouped by experience level, from freshers to staff engineers.
Docker Interview Question & Answers
0-2 Years
Docker is a platform for building, packaging, and running applications inside containers. It solves the it works on my machine problem, ensuring an application runs the exact same way in development, testing, and production, since a container bundles the application together with everything it genuinely needs to run.
A virtual machine virtualizes an entire genuine computer, including its own full operating system kernel, making it heavier and slower to start. A container instead shares the host machine's underlying operating system kernel and only packages the application and its specific dependencies, making containers significantly lighter and much faster to start.
A container doesn't need to genuinely boot an entire separate operating system the way a virtual machine does. It shares the genuine host's already-running kernel, so starting a container is really just starting a genuinely isolated process, which takes a fraction of a second rather than the genuine minute or more a full VM boot typically takes.
A container has its own genuinely isolated filesystem, process space, and network interface, so processes running inside it can't directly see or interfere with processes running in another container or on the genuine host system, even though they're all genuinely sharing the exact same underlying kernel.
Docker Desktop is an application providing a genuinely convenient way to actually run Docker on a local development machine, particularly on macOS and Windows, where Docker's own Linux-based container technology genuinely needs a lightweight virtual machine running underneath to actually work.
Docker Engine is the genuinely core underlying software actually running and managing containers, images, and networks. When you run a docker command, it's genuinely communicating with Docker Engine, which actually does the real work of creating and managing containers.
The Docker CLI is the command-line client you actually type commands into, like docker run. The Docker daemon is the background process that genuinely receives those commands over an API and actually does the real work of managing images, containers, and networks. The CLI itself never directly manages a container. It just sends a request to the daemon, which does the actual work.
A Docker image is a genuinely static, read-only template containing an application and its dependencies. A container is an actual, genuinely running instance created from that image, and you can genuinely run several separate containers from the exact same single image at once.
docker run nginx genuinely pulls the nginx image (if it isn't already available locally) and starts a genuinely new container from it. Adding -d runs it in genuinely detached mode, in the background, and -p 8080:80 genuinely maps port 8080 on the host to port 80 inside the container.
docker ps lists genuinely currently running containers. docker ps -a genuinely shows every container, including ones that have already stopped, which is useful for finding a container that genuinely exited unexpectedly.
docker stop container_name genuinely sends a stop signal, letting the container shut down gracefully. docker rm container_name genuinely removes it entirely once it's stopped, freeing up the resources and the name it was actually using.
docker stop genuinely sends a termination signal and waits a grace period for the container to actually shut down cleanly on its own. docker kill genuinely sends an immediate, forceful termination signal with no grace period at all, used when a container genuinely isn't responding to a normal stop request.
docker logs container_name prints that container's genuine output. Adding -f genuinely follows the logs live, similar to tail -f, letting you actually watch new log output as it's genuinely generated in real time.
A Dockerfile is a genuine text file containing step-by-step instructions for actually building a Docker image, specifying a base image to genuinely start from, files to copy in, and commands to actually run during the build process.
FROM specifies the genuine base image a new image is actually built on top of, like FROM node:18, which starts from an already-existing image with Node.js 18 genuinely pre-installed, rather than needing to install it entirely from scratch.
COPY genuinely copies files from the build context into the image, doing genuinely nothing more than that. ADD does the exact same thing but also genuinely supports extracting a local compressed archive automatically and fetching a remote URL, extra behavior that COPY deliberately keeps genuinely simple and predictable by not supporting.
RUN executes a genuine command during the actual image build process, like RUN npm install, and the genuine result of that command becomes a permanent part of the resulting image.
CMD specifies the genuine default command a container runs when started, but it can genuinely be overridden by a command passed at docker run time. ENTRYPOINT specifies a genuine command that always runs, with any additional arguments passed at docker run time genuinely appended to it rather than replacing it entirely.
docker build -t my-app . genuinely builds an image using the Dockerfile in the current directory, tagging the genuinely resulting image with the name my-app so it can actually be referenced easily later.
docker pull ubuntu genuinely downloads the specified image to the local machine, making it actually available to run later, without genuinely starting a container from it right away.
docker exec -it container_name bash genuinely opens an interactive bash shell inside the specified running container, letting you actually explore its filesystem or run a genuinely one-off command directly inside it.
-i keeps genuine standard input open, letting you actually type into the container. -t allocates a genuine pseudo-terminal, giving proper formatting for an interactive shell session. Combined as -it, they let you actually interact with a container's shell the way you would with a normal, real terminal.
docker container prune genuinely removes every stopped container in one command, after prompting for confirmation, which is useful for actually cleaning up disk space that accumulates from genuinely leftover, stopped containers nobody's actually using anymore.
docker images (or docker image ls) genuinely lists every image available locally, showing each image's repository name, tag, and actual size, letting you actually see what's currently taking up space on the local machine.
docker rmi image_name genuinely removes the specified image, though it will genuinely fail if a container, even a stopped one, is still actually referencing that image, requiring you to actually remove that container first.
Any data written inside a container's own filesystem is genuinely lost once that specific container is removed. A container's filesystem is genuinely built from its image, and writes during the container's life exist only in a genuinely temporary layer on top of that image, discarded when the container itself is actually deleted.
A volume provides genuinely persistent storage that exists independently of any specific container's own lifecycle, letting data actually survive a container being stopped, removed, or recreated, which a container's own default, temporary filesystem genuinely can't provide on its own.
docker run -v my-volume:/app/data my-image genuinely mounts the volume named my-volume to the /app/data path inside the container, so any data the application writes there actually persists in that volume beyond the container's own lifecycle.
A named volume is genuinely managed entirely by Docker itself, stored in a location Docker controls. A bind mount instead genuinely maps a specific, existing directory from the host machine directly into the container, useful for actually sharing local source code with a container during development.
The default bridge network lets containers on the exact same host genuinely communicate with each other through IP addresses, and Docker automatically genuinely connects any container to it unless you specifically configure something different.
Containers on a genuinely shared, custom network (rather than the default bridge) can reach each other directly using each container's own actual container name as a hostname, letting one container genuinely connect to another, like a web application connecting to a database container, using a simple, memorable name rather than a raw IP address.
Docker Compose lets you actually define and run a genuinely multi-container application, like a web server, a database, and a cache together, using one single YAML configuration file, rather than manually starting and connecting each individual container by hand with a genuinely long series of separate commands.
docker compose up --build rebuilds any service whose Dockerfile or build context has changed before starting it, rather than reusing a stale, already-built image. Without the --build flag, Compose would simply reuse whatever image already exists locally, even if the underlying Dockerfile has since genuinely changed.
A services section, listing each genuine container the application needs, along with each service's own image (or a build instruction), any genuine ports to expose, environment variables, and volumes it actually needs mounted.
docker compose up genuinely starts every service defined in the file, building any genuine images that need building first. Adding -d runs everything in genuinely detached mode, in the background, rather than keeping the terminal attached to the running logs.
docker compose down genuinely stops and removes every container that docker compose up had actually started, along with the genuine network Compose created for them, cleaning everything up together in one single command.
Docker Compose automatically creates a genuinely shared network for all the services defined within it, letting containers actually reach each other directly by their genuine service name, like connecting to a database container simply using db as its actual hostname.
3-6 Years
Each instruction in a Dockerfile genuinely creates a new, cached layer, and Docker reuses genuinely unchanged layers from a previous build rather than rebuilding them again. Ordering instructions so genuinely rarely-changing steps, like installing dependencies, come before genuinely frequently-changing steps, like copying application code, meaningfully speeds up repeated builds.
A multi-stage build uses genuinely multiple FROM statements in one Dockerfile, letting you use a genuinely larger image with build tools in an early stage, then copy only the actual, genuinely necessary compiled output into a much smaller, genuinely leaner final image, reducing the final image's size significantly.
Copying just the dependency manifest file, like package.json, and running the install step before copying the genuinely rest of the application code means Docker can genuinely reuse the cached dependency-install layer whenever only the application code changes, rather than needing to genuinely reinstall every dependency on every single build.
A .dockerignore file lists genuine files and directories to actually exclude from the build context sent to the Docker daemon, similar in spirit to a .gitignore file. It's used to avoid genuinely sending unnecessary files, like a local node_modules folder or genuinely sensitive local configuration, into the actual image build.
ARG defines a genuine value available only during the actual image build process itself, and it's genuinely not available inside a running container unless explicitly also set as an ENV. ENV defines a genuine value available both during the build and inside the actual running container.
bridge is the genuine default, letting containers on the same host communicate. host removes network isolation entirely, letting a container genuinely share the host's own network directly. overlay lets containers on genuinely different physical hosts communicate, used specifically in a multi-host Docker Swarm setup.
Containers on the genuine default bridge network can only reach each other by raw IP address, not by container name. A genuinely custom, user-defined bridge network provides automatic DNS resolution by container name, which is exactly why Docker Compose genuinely creates a custom network by default for the services it manages.
The -p flag on docker run, like -p 8080:80, genuinely maps port 8080 on the host to port 80 inside the container, letting external traffic reach the containerized application through the genuinely specified host port.
EXPOSE is genuinely documentation, informing anyone reading the Dockerfile which port the application inside genuinely listens on, but it doesn't itself actually publish that port to the host. The -p flag on docker run is what genuinely, actually makes a port reachable from outside the container.
Check whether they're genuinely on the exact same Docker network at all, since containers on genuinely different networks can't reach each other by default. docker network inspect on the relevant network shows exactly which containers are genuinely actually connected to it.
docker volume ls genuinely lists every volume, and docker volume inspect volume_name genuinely shows detailed information about a specific one, including exactly where its actual data is physically stored on the host filesystem.
The volume's actual data genuinely persists independently of the container's own lifecycle, since a volume exists as its own genuinely separate Docker-managed object, not tied directly to any one specific container.
docker volume rm volume_name genuinely removes the specified volume. The genuine precaution is confirming nothing actually still needs that data, since removing a volume permanently, genuinely deletes whatever it contained, with genuinely no way to recover it afterward.
A bind mount maps a genuinely specific host directory directly into a container. A genuinely common use case is mounting local source code into a development container, so a code change on the host is genuinely immediately reflected inside the running container without needing to actually rebuild the image.
A bind mount ties the container to the genuine host machine's own specific filesystem layout, which makes a container genuinely less portable across different hosts. In production, a named volume (or a genuinely remote storage backend) is usually preferred, since it doesn't genuinely depend on a specific host's own directory structure existing exactly the same way.
The environment key within a service's own definition in docker-compose.yml genuinely sets variables directly, or env_file points to a genuinely separate .env-style file, letting you actually keep configuration values out of the Compose file itself.
depends_on genuinely controls the order services are actually started in, but it doesn't genuinely wait for a dependent service to actually be ready to accept connections, only that its container has actually started, which can genuinely cause a race condition if the application itself doesn't handle a not-yet-ready dependency gracefully.
docker compose up --scale web=3 genuinely starts three instances of the web service, useful for actually testing basic load distribution locally, though genuinely full production-grade orchestration and scaling is typically better handled by a genuinely dedicated tool like Kubernetes rather than Compose alone.
A genuinely separate override file, like docker-compose.override.yml, automatically merges with the base docker-compose.yml, letting environment-specific settings, like a genuinely different volume mount for local development, stay genuinely cleanly separated from the shared, base configuration.
Alpine-based images are genuinely dramatically smaller, since Alpine Linux itself is a genuinely minimal distribution, which reduces the actual image size, speeds up pulling and deploying it, and genuinely reduces the surface area for a potential security vulnerability.
Alpine uses musl libc rather than the more common glibc, which can occasionally genuinely cause a compatibility issue with software that specifically expects glibc's own particular behavior, requiring extra genuine testing or a workaround that wouldn't genuinely be needed with a standard, more widely-compatible base image.
Combine related RUN instructions into fewer layers where genuinely sensible, remove genuinely unnecessary build tools and cache files within the same layer they were actually created in, and use a multi-stage build to genuinely exclude build-time-only dependencies from the actual final image entirely.
Each separate RUN instruction genuinely creates its own layer, and a temporary file created and deleted across genuinely two separate RUN instructions still leaves that temporary file's data genuinely present in the earlier layer. Combining the create-and-delete into one genuinely single RUN instruction means the temporary file never actually persists in any final layer at all.
6-8 Years
If an attacker genuinely manages to break out of container isolation, a container running as root gives them genuinely root-level access on the underlying host too. Running as a genuinely non-root user inside the container limits the real, potential damage such a breakout could actually cause.
Create a genuinely dedicated user with RUN adduser, and specify USER that_user_name later in the Dockerfile, which makes every subsequent instruction, and the genuinely final running container, execute as that specific non-root user rather than as root by default.
Image scanning tools, like Trivy or Docker Scout, automatically check a built image for genuinely known security vulnerabilities in its installed packages and dependencies. It solves the genuine problem of a vulnerable base image or dependency being deployed without anyone actually noticing the specific risk beforehand.
Pass it as an environment variable at docker run time, or genuinely better, use Docker secrets (in a Swarm context) or an external secrets manager, rather than hardcoding it directly into a Dockerfile or the image itself, where it would genuinely remain visible to anyone with access to that image.
Docker images are genuinely built in layers, and a secret written in an earlier layer genuinely remains present in that layer's own history, even if a genuinely later instruction deletes it from the visible final filesystem, meaning anyone with access to the image can still genuinely extract that secret from its earlier layers.
Running a container with --read-only genuinely prevents any process inside it from writing to its own filesystem at all, except to a genuinely specifically mounted, writable volume. It reduces the genuine, real damage a compromised process inside the container could actually do, since it genuinely can't modify the container's own filesystem.
Docker Content Trust uses digital signatures to verify an image genuinely came from its claimed publisher and hasn't been tampered with since being signed. It addresses the genuine problem of pulling and running a maliciously modified image that's been made to look like a trusted, legitimate one.
A container registry, like Docker Hub or a genuinely private equivalent, stores built container images so they can be genuinely pulled and run on any server or cluster that actually needs them. In a CI/CD pipeline, a genuine build stage pushes a freshly built image to the registry, and a genuine deployment stage pulls it from there.
Tag each image with something genuinely unique and traceable, like the Git commit hash, rather than always overwriting a generic tag like latest, so you can always genuinely trace exactly which specific code version produced a given deployed image, and can genuinely roll back to a specific, known previous version if actually needed.
latest genuinely doesn't guarantee any specific, stable version, it simply points to whatever was genuinely most recently pushed with that tag. Deploying based on latest can genuinely, unexpectedly pull in a newer, untested version, or make it genuinely hard to know exactly which specific version is actually currently running in production at any given moment.
The pipeline genuinely runs docker login using credentials, an API token or a service account key, stored securely in the CI/CD platform's own encrypted secrets storage, rather than a plaintext password hardcoded directly in the pipeline's own configuration file.
A multi-stage build with genuinely separate stages, and a build target flag (docker build --target dev or --target prod) lets the exact same Dockerfile genuinely produce a different final image depending on which specific target stage is actually selected at build time.
8-10 Years
Manually starting, stopping, and monitoring individual containers genuinely doesn't scale as the number of containers grows. An orchestration platform automates genuinely scheduling containers across a cluster of machines, restarting a genuinely failed container automatically, and scaling instances based on actual demand, none of which plain Docker commands genuinely handle on their own.
Docker Swarm is Docker's own genuinely built-in orchestration tool, simpler to actually set up and use than Kubernetes, but genuinely offering less flexibility and a smaller ecosystem. Kubernetes has genuinely become the dominant industry standard, offering meaningfully more capability at the cost of a genuinely steeper learning curve.
Running containers directly means a genuinely failed container, or an entire failed host, requires manual, human intervention to actually notice and recover from. An orchestration platform genuinely, automatically detects a failure and reschedules the affected workload elsewhere, without requiring a person to actually intervene manually every single time.
I'd weigh the genuine actual scale, availability requirements, and expected growth of the application. A genuinely small, low-traffic application with modest availability needs can run perfectly well on a single, well-managed host with Docker Compose, while an application genuinely needing high availability or the ability to scale across multiple machines calls for real orchestration.
Image immutability means a genuinely built image never changes once it's actually created, and any real change genuinely produces a new image with a new identifier instead. It matters because it guarantees the genuinely exact same image tested earlier is the exact same image actually deployed later, removing an entire genuine class of it worked in testing but not in production discrepancy.
A stateless container stores genuinely no persistent application data in its own local filesystem, instead genuinely relying on an external database or a shared storage service for anything that actually needs to persist. Statelessness matters because it lets an orchestration platform freely genuinely create, destroy, or reschedule a container's instances without any genuine risk of losing important application data.
Start by genuinely converting the existing Dockerfiles and docker-compose.yml configuration into equivalent Kubernetes manifests, Deployments and Services, testing thoroughly in a genuinely lower-risk environment first, and running the old and new setups genuinely in parallel briefly to confirm behavior matches before fully cutting over.
A HEALTHCHECK instruction in a Dockerfile defines a genuine command Docker periodically runs to actually verify a container is genuinely functioning correctly, beyond simply confirming its main process is technically still running. It solves the genuine problem of a container appearing up while its actual application inside has genuinely become unresponsive or broken.
docker run --memory=512m --cpus=1 my-image genuinely restricts the container to at most 512 megabytes of memory and one CPU core, preventing a single, genuinely misbehaving container from consuming excessive resources and negatively affecting other containers genuinely sharing the exact same host.
The container's process is genuinely killed by the kernel's out-of-memory mechanism, typically resulting in the container actually exiting and, depending on its genuine restart policy, being automatically restarted, though it will likely genuinely hit the exact same memory limit again unless the underlying, real memory issue is actually addressed.
Configure containers to genuinely write logs to standard output and standard error rather than to a local file, and use a genuine logging driver or a separate log-shipping agent to actually forward that output to a centralized, persistent logging system, rather than relying on a genuinely temporary container filesystem that's lost when the container is removed.
A restart policy determines whether Docker automatically genuinely restarts a container after it exits. no genuinely never restarts it automatically. on-failure restarts it genuinely only if it exited with a real error. always and unless-stopped genuinely restart it under almost any circumstance, including after the actual host itself reboots.
Start the genuinely new container version alongside the old one, verify it's actually healthy through a health check, then genuinely switch traffic over, typically through a reverse proxy or a load balancer, before finally stopping and removing the genuinely old container, avoiding any real gap where no container is actually available to serve traffic.
10+ Years
I'd weigh the genuine benefits, consistency across environments, easier local development, portability, against the real, added operational complexity Docker introduces for a genuinely small, simple application that might not actually need it. For most genuinely modern applications, containerizing is worth it, but a genuinely trivial, single-purpose script might not justify the extra overhead.
I'd establish a genuinely small set of approved, well-maintained base images that most teams can actually build from, and document the handful of Dockerfile conventions that actually matter most, security, layer ordering for cache efficiency, rather than a long, exhaustive style guide nobody actually reads in full.
I check whether it runs as a genuinely non-root user, whether the image is reasonably optimized rather than bloated with genuinely unnecessary build tools, and whether it's genuinely consistent with the organization's own established base images and conventions, rather than reinventing something inconsistent from scratch.
Integrate genuinely automated image scanning directly into the CI/CD pipeline itself, failing a build if a genuinely critical vulnerability is actually found, rather than relying on manual, ad hoc review. For standards that genuinely resist full automation, I'd document the handful of decisions that actually matter most, with the real reasoning behind each one.
I'd weigh Kubernetes's genuinely broader ecosystem and industry-standard status against the real cost of a team learning genuinely new tooling and migrating existing Swarm-based deployments. The migration is worth it once Swarm's own genuine limitations are actually blocking something concrete the organization actually needs, not simply because Kubernetes is currently more fashionable.
docker logs on the genuinely crashed container, combined with docker inspect to check its actual exit code and any relevant resource limit that might have genuinely been exceeded, is usually a strong starting point. A genuine out-of-memory kill often shows a specific, recognizable exit code that immediately points toward the actual real cause.
Track each container's genuine CPU and memory usage over time, alongside its actual health check status, alerting on meaningful deviation from an established, normal baseline. A container that's technically still running but repeatedly failing its health check is a genuinely real problem that pure uptime monitoring alone wouldn't catch.
Treat the base image's own actual behavior and installed tooling as a genuine contract with every team building on top of it. Adding something new is generally safe. Changing or removing something existing needs a documented deprecation period and direct communication before actual removal, rather than a silent breaking change that breaks other teams' builds unexpectedly.
I'd check the container's actual exit code and recent logs first to genuinely understand why it's failing, and roll back to the genuinely previous, known-good image if the timing of a recent deployment genuinely lines up, prioritizing stopping the active restart loop over fully understanding root cause immediately.
I'd load test with a genuinely realistic number of containers under real, expected traffic, verifying the host's own actual CPU, memory, and disk capacity can genuinely handle that load, and identify whether a single, larger host or genuinely multiple hosts (moving toward real orchestration) is the more appropriate path forward.
This is a judgment question interviewers use to see how you reason under genuine uncertainty, not to test a specific textbook fact. A strong answer names the actual constraint that forced the decision, the realistic options that were genuinely on the table, why you picked one knowing it wasn't guaranteed to be right, and what you'd do differently with what you know now.
I'd walk through one of their actual Dockerfiles together, timing a rebuild before and after reordering instructions for genuinely better cache usage, showing the concrete difference directly, rather than explaining layer caching as an abstract concept in isolation. Seeing their own build genuinely get noticeably faster tends to build that habit far more effectively than a general rule alone.
I wouldn't lead with security best practice in the abstract. I'd point to a specific, real, already-experienced incident or a genuine, credible risk scenario specific to their own actual application, and show concretely how running as a non-root user would have genuinely limited that risk, rather than arguing for it purely as a general principle.
I'd bring the actual, concrete scale and availability requirements of that specific service into the discussion, rather than a general, abstract preference for one approach over the other. Most disagreements like this genuinely resolve once both sides are looking at the exact same concrete requirements together, rather than arguing from differing, unstated assumptions about the actual real need.
I'd translate the work into terms leadership already tracks: the engineering hours currently lost waiting on slow builds each day, and the actual cloud storage and transfer costs tied to unnecessarily large images across every deployment. Framed as recovered engineering time and reduced infrastructure cost, it competes far better for prioritization than framed as a technical cleanup for its own sake.




