Docker — Intermediate Questions and Answers

Beginner | Intermediate | Advanced | Docker Compose

1. What are the key Dockerfile best practices?

# .dockerignore
.git
node_modules
__pycache__
*.log
.env
Dockerfile
.dockerignore

2. CMD vs. ENTRYPOINT — what is the difference?

Both define what runs when the container starts, but they behave differently when you pass arguments to docker run:

CMDENTRYPOINT
PurposeDefault command — easily overriddenFixed executable — the container's purpose
docker run image foofoo replaces CMDfoo is appended as an argument
Typical useDefaults and argumentsCLIs and executables (nginx, java -jar ...)

They combine naturally: ENTRYPOINT is the executable, CMD is its default arguments:

ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]

# docker run myapi              -> python app.py --port 8000
# docker run myapi --port 9000  -> python app.py --port 9000
# docker run --entrypoint sh myapi  -> override for debugging

Always use the exec (JSON array) form — CMD ["python","app.py"]. The shell form (CMD python app.py) runs under /bin/sh -c, which does not forward signals like SIGTERM, so docker stop ends up force-killing your app after the grace period.

3. COPY vs. ADD — when does the difference matter?

COPY does one thing: copies local files into the image. ADD does that plus two extras: auto-extracting local .tar archives and fetching remote URLs. The community convention is to use COPY unless you specifically need tar extraction — it is explicit and predictable. Remote-URL downloads are better done with RUN curl (cacheable, inspectable).

4. Volumes vs. bind mounts vs. tmpfs — which mount do I use?

Mount typeStored whereManaged byBest for
Named volume -v mydata:/dataDocker storage area on the hostDocker CLI/APIPersistent production data (databases), portability
Bind mount -v /host/code:/appAny path on the hostYou (the host OS)Development: live-edit host code in the container
tmpfs mount --mount type=tmpfs,dst=/tmpHost memory onlyNever persistedSensitive temporary data, scratch files
# Development pattern: bind mount the source, keep dependencies inside
docker run -d -p 3000:3000 \
  -v "$(pwd)":/app \
  -v /app/node_modules \
  node:20 npm run dev

The second -v /app/node_modules is an anonymous volume that "shields" the container's installed dependencies from being hidden by the host bind mount — a very common trick.

5. How does container networking work?

Docker ships with several network drivers:

docker network create mynet
docker network ls
docker network inspect mynet
docker run -d --name db --network mynet postgres:16
docker network connect mynet web   # attach a running container

6. How do containers find each other by name?

On any user-defined network (custom bridge or Compose's default network), Docker runs an embedded DNS server: containers resolve each other by container name (and network aliases). This is the foundation of multi-container apps:

docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet -e DB_HOST=db my-api:1.0
# the api container connects using host "db" — DNS resolves it automatically

Best practice: put only the containers that need to talk on the same user-defined network, and keep databases off the public bridge.

7. ENV vs. ARG — build-time vs. run-time variables

ARGENV
Available duringdocker build onlyBuild and container runtime
Override--build-arg NAME=valuedocker run -e NAME=value
Persisted in imageNo (unless promoted to ENV)Yes — visible via docker inspect
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-slim

ENV APP_ENV=production \
    PORT=8000

Use ARG for build-time knobs (versions, build profiles). Use ENV for sensible defaults that can still be overridden per container. Never pass secrets as build args or ENV — both leak into image metadata and docker history (see Advanced: build secrets).

8. How do I configure a container without rebuilding the image?

The twelve-factor principle: config belongs in the environment, not the image. One image, many configurations:

docker run -d \
  -e DB_HOST=prod-db.internal \
  -e DB_PASSWORD_FILE=/run/secrets/db_pw \
  --env-file ./config/app.env \
  my-api:1.0

Options, from simple to robust: -e single variables, --env-file for batches, mounted config files for structured config, and orchestrator secrets for credentials.

9. How do HEALTHCHECKs work?

A HEALTHCHECK tells Docker how to test the application is actually working — not just that the process is alive:

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD curl -fsS http://localhost:8000/health || exit 1

10. How do I limit container resources and control restarts?

Without limits, one container can starve the whole host (the "noisy neighbor" problem):

docker run -d \
  --memory=512m --memory-swap=512m \
  --cpus=1.5 \
  --restart unless-stopped \
  my-api:1.0

Restart policies: no (default), on-failure[:max-retries], always, unless-stopped (like always, but stays stopped if you stopped it manually — the usual production choice).

11. What is Docker Compose and when do I reach for it?

Compose defines a multi-container application (app + database + cache + queue) in a single docker-compose.yml and manages the whole stack with one command: docker compose up. It handles the network creation, name-based DNS, startup order, and volumes for you. It is the default tool for local development and test environments. A full walkthrough lives in the dedicated Docker Compose page.

12. What is a good workflow for debugging a misbehaving container?

  1. docker ps -a — is it running, restarting, or exited? Check the status column.
  2. docker logs --tail 200 <name> — the application's own output is the first clue.
  3. Crash-looping? docker inspect --format '{{.State.ExitCode}} {{.State.Error}}' <name> for exit codes (137 = OOM/killed, 126/127 = command problems).
  4. docker exec -it <name> sh — poke around inside: env vars present? Can it reach the database? curl the dependency.
  5. docker inspect — verify mounts, ports, networks, and healthcheck results (.State.Health).
  6. Need a shell but the image has none? Run a debug container in the same network: docker run --rm -it --network <net> nicolaka/netshoot.

Most incidents boil down to: wrong environment variable, port already in use, unreachable dependency, or a volume masking application files.

← Back to IT Knowledge Base