Docker Interview Questions: A Hands-On Tutorial for Developers
Docker interview preparation becomes much easier when you stop memorizing isolated definitions and start building small, working examples. In this tutorial, you’ll create a containerized Python API, connect it to Redis with Docker Compose, inspect its networking and storage, and practice answering common interview questions using real commands.

If you are preparing for a DevOps role or planning to enroll in an AWS DevOps course in Bangalore, Docker interview questions are an important part of your preparation. Docker is widely used in CI/CD, cloud deployments, microservices, and infrastructure automation.
The examples use standard Docker workflows: images, containers, Dockerfiles, volumes, networks, Compose, caching, and troubleshooting. Docker’s official learning examples cover the same progression from running containers to building images, using volumes, networking services, Compose, and multi-stage builds. github
1. Prepare the Example Project
Create a new project:
mkdir docker-interview-lab
cd docker-interview-lab
Create this structure:
docker-interview-lab/
├── app.py
├── requirements.txt
├── Dockerfile
├── .dockerignore
└── compose.yaml
Create a small API
Add the following to app.py:
from flask import Flask, jsonify
import os
import socket
app = Flask(__name__)
@app.get("/")
def home():
return jsonify({
"message": "Docker interview lab is running",
"hostname": socket.gethostname(),
"environment": os.getenv("APP_ENV", "development")
})
@app.get("/health")
def health():
return jsonify({"status": "healthy"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
The important detail is:
app.run(host="0.0.0.0", port=5000)
If the application listens only on 127.0.0.1, it may work inside the container but remain inaccessible from your host machine.
Add this to requirements.txt:
Flask==3.1.0
Create .dockerignore:
__pycache__/
*.pyc
.git/
.env
.venv/
venv/
A .dockerignore prevents unnecessary files from being sent to the Docker build context. This makes builds faster and helps avoid accidentally copying local secrets or development files into the image.
2. Question: What Is Docker?
Docker is a platform for packaging and running applications in isolated containers.
A container includes the application and its required runtime dependencies, libraries, configuration, and filesystem changes. Unlike a virtual machine, a container does not usually include a complete guest operating system kernel. Containers share the host kernel while remaining isolated through operating-system features.
A useful interview answer is:
Docker packages an application and its dependencies into a portable image. A container is a running instance of that image, isolated from other processes but sharing the host operating system kernel.
Container versus virtual machine
| Area | Container | Virtual machine |
|---|---|---|
| Operating system | Shares the host kernel | Includes a guest OS |
| Startup time | Usually very fast | Usually slower |
| Resource usage | Generally lower | Generally higher |
| Isolation | Process-level isolation | Hardware or hypervisor-level isolation |
| Packaging unit | Image | VM disk image |
| Typical use | Services, CI, development | Stronger isolation, different kernels, legacy workloads |
Containers are not automatically equivalent to virtual machines. If an interviewer asks about security isolation, explain that the isolation boundary and threat model differ.
3. Question: What Is the Difference Between an Image and a Container?
An image is an immutable template containing application files, dependencies, metadata, and filesystem layers.
A container is a runnable instance of an image. When Docker starts a container, it adds a writable layer on top of the image’s read-only layers.
Try it:
docker pull python:3.12-slim
docker images
Run an interactive Python container:
docker run --rm -it python:3.12-slim python
Inside the Python prompt:
print("Running inside a container")
Exit:
exit()
The --rm flag removes the container after it stops.
Now run a named container in the background:
docker run -d \
--name interview-python \
python:3.12-slim \
sleep 300
Inspect it:
docker ps
docker inspect interview-python
Stop and remove it:
docker stop interview-python
docker rm interview-python
Useful distinction:
-
docker psshows running containers. -
docker ps -ashows running and stopped containers. -
docker imageslists local images. -
docker inspectdisplays low-level metadata. -
docker logsdisplays a container’s standard output and error streams.
4. Question: What Happens During docker run?
Consider this command:
docker run -d --name web -p 8080:5000 my-api:1.0
Docker generally performs these steps:
- Checks whether the image exists locally.
- Pulls the image if it is unavailable locally.
- Creates a container from the image.
- Adds a writable container layer.
- Configures networking.
- Maps host port
8080to container port5000. - Starts the container’s configured process.
The port format is:
host_port:container_port
Therefore:
-p 8080:5000
means that requests to port 8080 on your machine are forwarded to port 5000 inside the container.
5. Build the API Image
Add this to Dockerfile:
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
Build the image:
docker build -t docker-interview-api:1.0 .
Run it:
docker run -d \
--name docker-api \
-p 8080:5000 \
-e APP_ENV=container \
docker-interview-api:1.0
Test it:
curl http://localhost:8080/
curl http://localhost:8080/health
View logs:
docker logs docker-api
Follow logs continuously:
docker logs -f docker-api
Enter the running container:
docker exec -it docker-api sh
Inside the container:
pwd
ls
python --version
Leave the shell:
exit
Clean up:
docker stop docker-api
docker rm docker-api
6. Question: Explain the Main Dockerfile Instructions
FROM
Defines the base image:
FROM python:3.12-slim
Use a trusted and maintained base image. Docker’s build guidance recommends choosing trusted images, keeping images small, and rebuilding regularly so dependencies and base-image security fixes are included. docs.docker
WORKDIR
Sets the working directory for subsequent instructions:
WORKDIR /app
It is preferable to using repeated RUN cd /app commands because WORKDIR applies consistently to future instructions and the container’s default process.
COPY
Copies files from the build context into the image:
COPY requirements.txt .
COPY app.py .
RUN
Executes a command while building the image:
RUN pip install --no-cache-dir -r requirements.txt
The result becomes part of an image layer.
EXPOSE
Documents the port that the application expects to use:
EXPOSE 5000
It does not publish the port to the host. You still need:
-p 8080:5000
CMD
Defines the default command:
CMD ["python", "app.py"]
ENTRYPOINT
Defines the main executable for the container. For example:
ENTRYPOINT ["python"]
CMD ["app.py"]
The resulting default command is equivalent to:
python app.py
A common interview distinction is that CMD supplies defaults that can be replaced, while ENTRYPOINT is intended to define the executable. The exact behavior also depends on whether shell or exec form is used.
Prefer exec form:
CMD ["python", "app.py"]
It handles signals more reliably than:
CMD python app.py
7. Question: How Does Docker Image Layer Caching Work?
Each Dockerfile instruction can create a layer. Docker reuses a cached layer when the instruction and its relevant inputs have not changed.
This Dockerfile is cache-friendly:
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
If only app.py changes, Docker can reuse the dependency-installation layer.
This version is less efficient:
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
Any source-code change can invalidate the COPY . . layer and force the dependency installation to run again.
Build with plain progress output:
docker build --progress=plain -t docker-interview-api:1.1 .
Inspect image size:
docker image ls docker-interview-api
docker history docker-interview-api:1.1
Performance tips:
- Put stable instructions before frequently changing instructions.
- Use
.dockerignore. - Avoid copying the entire repository when only a few files are required.
- Combine related package-installation commands carefully.
- Remove package-manager caches where appropriate.
- Use BuildKit cache mounts for package managers in larger projects.
- Avoid rebuilding with
--no-cacheunless you specifically need a clean build.
8. Question: What Are Volumes and Bind Mounts?
Container filesystems are ephemeral. If a container is removed, data written only inside its writable layer disappears.
Named volume
Create a volume:
docker volume create app-data
Use it:
docker run --rm \
-v app-data:/data \
alpine \
sh -c "echo persistent-data > /data/example.txt"
Read it from another container:
docker run --rm \
-v app-data:/data \
alpine \
cat /data/example.txt
The data survives the first container because it is stored in the named volume.
Bind mount
A bind mount maps a host directory into a container:
docker run --rm \
-v "$(pwd)":/workspace \
alpine \
ls /workspace
Bind mounts are useful during development because changes made on the host are immediately visible inside the container.
When should you use each?
| Storage type | Best use |
|---|---|
| Named volume | Database data and Docker-managed persistent application data |
| Bind mount | Local development, source-code editing, configuration injection |
| Image layer | Static application files that do not need runtime persistence |
| External storage | Production data requiring independent durability and backup |
Do not treat a container’s writable layer as a database backup strategy.
9. Question: How Does Docker Networking Work?
Docker provides isolated networks that allow containers to communicate.
Create a network:
docker network create interview-net
Start a container on it:
docker run -d \
--name api \
--network interview-net \
docker-interview-api:1.0
Run a temporary troubleshooting container on the same network:
docker run --rm \
--network interview-net \
alpine \
ping -c 3 api
On a user-defined Docker network, containers can typically reach one another by container name. This is why application configuration should use a service name such as redis, not localhost.
Inside a container:
-
localhostmeans the current container. - The host machine is a different network namespace.
- Another container should be reached through its service or container name.
Inspect the network:
docker network inspect interview-net
Remove it after testing:
docker stop api
docker rm api
docker network rm interview-net
10. Add Redis with Docker Compose
Docker Compose is useful when an application consists of multiple services.
Create compose.yaml:
services:
api:
build:
context: .
container_name: interview-api
ports:
- "8080:5000"
environment:
APP_ENV: compose
REDIS_HOST: redis
depends_on:
- redis
redis:
image: redis:7-alpine
container_name: interview-redis
volumes:
- redis-data:/data
volumes:
redis-data:
Start both services:
docker compose up --build
Run in the background:
docker compose up -d --build
View service status:
docker compose ps
View logs:
docker compose logs -f api
Stop the services:
docker compose down
Stop them and remove the named volume:
docker compose down -v
Be careful with -v: it deletes the Compose-managed volume and its stored data.
A common Compose interview question
Does depends_on mean the dependency is ready?
Not necessarily. It controls startup order, but a service may still be initializing when the dependent application starts. Production applications should implement retries, connection timeouts, and health-aware startup behavior.
A stronger Compose configuration can add a health check:
services:
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
api:
build: .
depends_on:
redis:
condition: service_healthy
Your application should still handle temporary connection failures gracefully.
11. Question: What Is a Multi-Stage Build?
A multi-stage build separates compilation or dependency-building tools from the final runtime image.
For a Python application, a simple example is:
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
--prefix=/install \
-r requirements.txt
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY --from=builder /install /usr/local
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
Build it:
docker build -t docker-interview-api:multi-stage .
The final image does not need the builder’s temporary working files. This can reduce image size and attack surface. Docker’s official starter material includes multi-stage builds as a method for separating build-time and runtime dependencies. github
Multi-stage builds are especially useful for:
- Go binaries.
- Java applications.
- Node.js front-end bundles.
- TypeScript applications.
- Applications with native compilation dependencies.
12. Question: Why Should Containers Avoid Running as Root?
Running as root inside a container can increase the impact of an application compromise. It does not solve every container-security concern, but using a non-root user is a practical hardening step.
Update the Dockerfile:
FROM python:3.12-slim
WORKDIR /app
RUN useradd --create-home appuser
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=appuser:appuser app.py .
USER appuser
EXPOSE 5000
CMD ["python", "app.py"]
Check the user:
docker build -t docker-interview-api:secure .
docker run --rm docker-interview-api:secure id
Other security practices include:
- Do not place passwords or API keys in a Dockerfile.
- Do not commit
.envfiles containing credentials. - Use secret-management features appropriate to your deployment platform.
- Avoid
--privilegedunless there is a clearly justified requirement. - Expose only necessary ports.
- Use specific image versions rather than relying blindly on
latest. - Scan images in CI and rebuild them regularly.
- Use minimal runtime images where compatibility allows.
13. Practical GitHub Workflow
Put the project under version control:
git init
git add .
git commit -m "Add Docker interview lab"
Create a repository on GitHub, then connect and push it:
git branch -M main
git remote add origin YOUR_REPOSITORY_ADDRESS
git push -u origin main
Do not copy credentials into the repository. Your .dockerignore should exclude local environments and secret files, but review the repository before pushing:
git status
git diff --cached
A basic GitHub Actions workflow can build the image on every push. Create .github/workflows/docker.yml:
name: Docker Build
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Build image
run: docker build -t docker-interview-api:test .
This does not publish an image. It simply confirms that the Dockerfile can build in a clean environment.
For a real project, add:
- Unit tests.
- Image vulnerability scanning.
- Image tagging based on commit SHA.
- Registry authentication through GitHub Secrets.
- Deployment only after tests and security checks pass.
Docker maintains official example repositories covering Compose, sample applications, CI, image caching, and multi-stage builds. github
14. Troubleshooting Guide
Error: “Cannot connect to the Docker daemon”
Check whether Docker Desktop or the Docker service is running:
docker version
On Linux systems using systemd:
sudo systemctl status docker
sudo systemctl start docker
If permissions are the problem, your user may not have access to the Docker socket.
Error: “Port is already allocated”
Find the process using the port:
docker ps
Change the host-side port:
docker run -p 8081:5000 docker-interview-api:1.0
The application still listens on port 5000 inside the container; only the host port changed.
The container exits immediately
A container stays alive only while its main process is running.
Check its status and logs:
docker ps -a
docker logs container_name
Do not use a long-running command merely to hide application failures. Identify why the main process stopped.
The API is unreachable
Check these items:
docker ps
docker logs docker-api
docker port docker-api
Also verify that:
- The application listens on
0.0.0.0. - The correct port mapping is used.
- The container is running.
- A host firewall is not blocking the port.
localhost does not reach Redis
From the API container, localhost refers to the API container itself. In Compose, use:
REDIS_HOST=redis
Do not use localhost unless Redis is running in the same container, which is usually a poor service-design choice.
Changes are not appearing
If the source is copied into the image, rebuild it:
docker compose up --build
For development, mount the source directory:
services:
api:
build: .
ports:
- "8080:5000"
volumes:
- .:/app
A bind mount can hide files that were copied into the image at the same path. This sometimes causes confusing behavior.
Permission denied on mounted files
The host user ID and container user ID may differ. Check:
id
docker run --rm image_name id
Choose a consistent development strategy rather than changing permissions broadly with unsafe commands.
15. Performance and Reliability Tips
Use these principles when answering practical Docker interview questions:
- Keep the build context small with
.dockerignore. - Order Dockerfile instructions to preserve cacheable layers.
- Use multi-stage builds for compiled artifacts.
- Select a suitable minimal base image, but test compatibility before switching to Alpine or distroless images.
- Avoid installing development tools in the runtime image.
- Use health checks for meaningful service readiness.
- Set CPU and memory limits where appropriate.
- Configure application workers based on workload rather than copying a default number.
- Send logs to standard output and error so the runtime can collect them.
- Add graceful shutdown handling for applications receiving termination signals.
- Use persistent volumes or external storage for stateful data.
- Tag releases immutably, for example with a version or commit SHA.
- Rebuild images regularly to receive dependency and base-image updates.
An image that builds quickly but contains unnecessary packages, runs as root, stores secrets, or loses data on restart is not production-ready.
16. Practice Exercises
Try these without looking at the answers first.
Exercise 1: Inspect a container
Run:
docker run -d --name nginx-lab -p 8088:80 nginx:stable
Then find:
- The container IP address.
- The image ID.
- The published port.
- The container’s main process.
Useful commands:
docker inspect nginx-lab
docker port nginx-lab
docker top nginx-lab
Clean up:
docker rm -f nginx-lab
Exercise 2: Prove volume persistence
- Create a named volume.
- Write a file from one container.
- Remove that container.
- Read the file from another container.
Explain why the file remains after the container is deleted.
Exercise 3: Break and fix networking
- Start Redis with Compose.
- Configure the API to use
localhost. - Observe the connection failure.
- Change the hostname to
redis. - Explain the difference between container-local and Compose-network addressing.
Exercise 4: Improve the Dockerfile
Modify the API image to:
- Run as a non-root user.
- Use a
.dockerignore. - Avoid pip cache files.
- Separate stable dependency installation from source-code copying.
- Add a health check.
Example:
HEALTHCHECK --interval=30s --timeout=3s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')"
Test the result:
docker inspect --format='{{json .State.Health}}' container_name
17. Quick Interview Revision
Here are concise answers you can practice aloud:
What is Docker?
A platform for packaging and running applications in isolated, portable containers.Image versus container?
An image is an immutable template; a container is a running instance of that image.What does
-p 8080:5000mean?
It maps host port8080to container port5000.Does
EXPOSEpublish a port?
No. It documents the intended container port. Port publishing requires-por Compose’sports.Why use
.dockerignore?
To reduce build context size and prevent unnecessary or sensitive files from entering the build.Why use multi-stage builds?
To keep build tools and temporary files out of the final runtime image.What happens to container data after removal?
Data in the container’s writable layer is lost; data in a named volume can persist.How do containers communicate in Compose?
Through the Compose network, usually using service names as DNS hostnames.Why should applications not depend on
localhostfor another service?
Becauselocalhostrefers to the current container.How do you troubleshoot a stopped container?
Checkdocker ps -a, inspect its exit status, and readdocker logs.What is a good Docker security practice?
Use trusted, versioned images, avoid secrets in images, run as non-root, limit privileges, and scan regularly.How do you improve build speed?
Use.dockerignore, order layers for caching, avoid unnecessary context files, and use multi-stage or BuildKit caching where appropriate.
Learning Resources
For continued practice, work through Docker’s official getting-started material and inspect its sample applications rather than only reading command references. The official examples demonstrate containers, images, volumes, bind mounts, networking, Compose, caching, and multi-stage builds in a connected workflow. github
Developers preparing for an AWS DevOps course and placement in Bangalore can use this hands-on Docker lab to practice containerization, networking, image optimization, Docker Compose, and troubleshooting.
The most effective routine is:
- Build a small service.
- Containerize it.
- Add a second service.
- Persist its data.
- Break the networking deliberately.
- Diagnose the failure from logs and inspection commands.
- Improve the Dockerfile.
- Automate the build with CI.
That process prepares you for Docker interviews much better than memorizing commands alone.
Top comments (0)