Docker & Docker Compose — Interview Questions

Stack context: This system uses Docker Compose with 14 containers: 6 Spring Boot services + 4 PostgreSQL instances + Apache Kafka (KRaft) + Confluent Schema Registry + Redis + Zipkin. All services communicate on a Docker bridge network. Testcontainers is used in integration tests to spin up real infrastructure.


Q1 — What is Docker and what problem does it solve? junior

Answer: Docker is a containerization platform that packages an application and all its dependencies into a portable, isolated container. Containers run consistently across any environment (developer laptop, CI, staging, production).

Problems it solves:

Docker vs VM:

Virtual Machine Docker Container
OS Full guest OS per VM Shares host kernel
Size GBs MBs
Startup time Minutes Seconds
Isolation Strong (hypervisor) Process-level namespaces
Resource use High Low

Q2 — What is a Docker image and how is it different from a container? junior

Answer:

Image (read-only) → Container (image + writable layer)

Analogy: Image = class definition; Container = object instance.

Key commands:

# Build image from Dockerfile in current directory
docker build -t order-service:1.0.0 .

# Run a container from the image
docker run -d -p 8083:8083 --name order-service order-service:1.0.0

# List running containers
docker ps

# See all containers (including stopped)
docker ps -a

# Remove container
docker rm -f order-service

# List images
docker images

# Remove image
docker rmi order-service:1.0.0

Q3 — What is a Dockerfile and what are the key instructions? junior

Answer: A Dockerfile is a text file containing instructions for building a Docker image. Each instruction creates a new layer.

# Base image
FROM eclipse-temurin:21-jre

# Set working directory
WORKDIR /app

# Copy application JAR (built externally with Maven)
COPY target/order-service-1.0.0-SNAPSHOT.jar app.jar

# Expose port (documentation only — doesn't publish the port)
EXPOSE 8083

# Environment variables with defaults
ENV SPRING_PROFILES_ACTIVE=docker
ENV JAVA_OPTS="-Xms256m -Xmx512m"

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
  CMD curl -f http://localhost:8083/actuator/health || exit 1

# Command to run
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Key instructions:


Q4 — What are Docker image layers and why do they matter for build performance? senior

Answer: Every RUN, COPY, and ADD instruction creates a new layer. Layers are cached — if a layer's content doesn't change, Docker reuses the cached layer.

Build cache optimization:

# SLOW — copy everything first; any source change invalidates all subsequent layers
COPY . .
RUN ./mvnw package -DskipTests

# FAST — dependencies change rarely; copy pom.xml first and cache the mvn install layer
COPY pom.xml .
COPY src/main/resources ./src/main/resources
RUN ./mvnw dependency:go-offline

# Source changes only invalidate from here
COPY src/main/java ./src/main/java
RUN ./mvnw package -DskipTests

Layer ordering rule: Put infrequently changing instructions first, frequently changing instructions last.

Layer inspection:

docker history order-service:1.0.0
docker inspect order-service:1.0.0 | jq '.[0].RootFS.Layers'

This system: The pre-built JAR is copied in a single COPY instruction (Maven builds externally). In a CI pipeline, use multi-stage builds for clean separation.


Q5 — What is a multi-stage build and why is it used? senior

Answer: Multi-stage builds use multiple FROM instructions to keep the final image small by discarding build tools and intermediate files.

Java multi-stage build:

# ---- Build stage ----
FROM maven:3.9.6-eclipse-temurin-21 AS builder
WORKDIR /build

# Cache dependencies first
COPY pom.xml .
RUN mvn dependency:go-offline -q

# Build application
COPY src ./src
RUN mvn package -DskipTests -q

# ---- Extract layers (Spring Boot layered JAR) ----
FROM eclipse-temurin:21-jre AS extractor
WORKDIR /app
COPY --from=builder /build/target/order-service-*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

# ---- Final runtime image ----
FROM eclipse-temurin:21-jre
WORKDIR /app

# Copy Spring Boot layers (most stable first = better caching)
COPY --from=extractor /app/dependencies/ ./
COPY --from=extractor /app/spring-boot-loader/ ./
COPY --from=extractor /app/snapshot-dependencies/ ./
COPY --from=extractor /app/application/ ./

EXPOSE 8083
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

Benefits:


Q6 — What is Docker Compose and what problem does it solve? junior

Answer: Docker Compose is a tool for defining and running multi-container applications. Instead of running docker run commands manually for each container, you define all services in a docker-compose.yml and start everything with one command.

This system's docker-compose.yml (14 containers):

docker compose up -d --build    # start all 14 containers in detached mode
docker compose down             # stop and remove containers
docker compose logs -f order-service  # follow logs
docker compose ps               # show container status

Benefits:


Q7 — What are the key sections of a docker-compose.yml file? junior

Answer:

version: '3.9'

services:         # Container definitions
  order-service:
    image: order-service:latest          # or build: ./order-service
    ports:
      - "8083:8083"                      # host:container
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://order-db:5434/orderdb
      - SPRING_KAFKA_BOOTSTRAP_SERVERS=kafka:9092
    depends_on:
      order-db:
        condition: service_healthy       # wait for health check
      kafka:
        condition: service_healthy
    networks:
      - ecommerce-network
    volumes:
      - ./logs:/app/logs                 # mount host directory

  order-db:
    image: postgres:16
    environment:
      POSTGRES_DB: orderdb
      POSTGRES_PASSWORD: ${DB_PASSWORD}  # from .env file
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

networks:
  ecommerce-network:
    driver: bridge

volumes:
  order-db-data:                         # named volume (persists across restarts)
    driver: local

Q8 — What is the difference between depends_on and health checks? junior

Answer: depends_on without health check: Starts the dependent container when the dependency container starts — NOT when it's ready to accept connections. PostgreSQL container starts in ~1 second but is ready for queries after ~5-10 seconds.

Problem: order-service starts right after postgres container starts, but Postgres isn't ready → Flyway migration fails.

Solution: depends_on + condition: service_healthy:

services:
  order-service:
    depends_on:
      order-db:
        condition: service_healthy       # wait until health check passes
      kafka:
        condition: service_healthy

  order-db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d orderdb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s    # grace period before health check starts

Health check conditions:


Q9 — What are Docker volumes and what are the different types? junior

Answer: Volumes persist data beyond the container lifecycle. When a container is deleted, its writable layer (container data) is lost — volumes survive.

Three types:

1. Named volumes (managed by Docker, best for databases):

volumes:
  order-db-data:

services:
  order-db:
    volumes:
      - order-db-data:/var/lib/postgresql/data  # named volume

2. Bind mounts (host path mapped into container):

services:
  order-service:
    volumes:
      - ./config:/app/config              # mount host directory (read-only)
      - ./logs:/app/logs                  # write logs to host

3. tmpfs mounts (in-memory, Linux only):

services:
  redis:
    tmpfs:
      - /data   # Redis data in memory — lost on container restart

When to use:


Q10 — How does Docker networking work and what are the network modes? senior

Answer: Containers communicate via Docker networks. Within the same network, containers reach each other by service name (DNS-resolved by Docker).

Network modes:

Mode Description Use case
bridge (default) Private virtual network; NAT to host Multi-container apps (this system)
host Container uses host's network namespace Maximum performance, no port mapping
none No networking Isolated containers
overlay Multi-host networking (Docker Swarm) Distributed cluster

Custom bridge network (best practice):

networks:
  ecommerce-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

Service DNS: Within ecommerce-network:

Port mapping (8083:8083): Makes the container's port 8083 accessible on the host as 8083. Without port mapping, the service is reachable only from within the Docker network.


Q11 — How do you configure environment variables securely in Docker Compose? senior

Answer: Option 1: .env file (recommended for development):

# .env file (in docker-compose directory, git-ignored!)
DB_PASSWORD=supersecret
REDIS_PASSWORD=redispass
JWT_SECRET=verylongsecretkey
services:
  order-db:
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}  # interpolated from .env

Option 2: env_file directive (per-service config):

services:
  order-service:
    env_file:
      - ./order-service/.env

Option 3: Docker secrets (for production/Swarm):

services:
  order-db:
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    external: true   # created with: docker secret create db_password <file>

Security rules:


Q12 — How do you build and push a Docker image to a registry? junior

Answer:

# Build image with tag
docker build -t myregistry.io/kafka-redis/order-service:1.0.0 \
             -t myregistry.io/kafka-redis/order-service:latest \
             ./order-service

# Login to registry
docker login myregistry.io -u username -p password
# (better: use docker credential helpers, not CLI passwords)

# Push image
docker push myregistry.io/kafka-redis/order-service:1.0.0
docker push myregistry.io/kafka-redis/order-service:latest

# Pull image on another machine
docker pull myregistry.io/kafka-redis/order-service:1.0.0

Tagging strategy:

GitHub Container Registry:

docker tag order-service:latest ghcr.io/username/order-service:latest
docker push ghcr.io/username/order-service:latest

Q13 — What is the difference between ENTRYPOINT and CMD in a Dockerfile? junior

Answer:

ENTRYPOINT CMD
Purpose Main executable Default arguments
Override docker run --entrypoint docker run <image> <cmd>
Combination ENTRYPOINT + CMD = executable + args If ENTRYPOINT set, CMD becomes args to ENTRYPOINT
# Pattern 1: ENTRYPOINT only
ENTRYPOINT ["java", "-jar", "app.jar"]
# docker run myimage --server.port=9090  → java -jar app.jar --server.port=9090

# Pattern 2: ENTRYPOINT + CMD (CMD provides defaults)
ENTRYPOINT ["java"]
CMD ["-jar", "app.jar"]
# docker run myimage -jar other.jar  → java -jar other.jar (CMD overridden)

# Pattern 3: Shell form (can use $JAVA_OPTS)
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Best practice for Spring Boot:

ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

-XX:+UseContainerSupport (default in JDK 11+): JVM respects Docker memory limits instead of using the host's total RAM.


Q14 — How does container networking work between services in Docker Compose? junior

Answer: When services are defined in the same Docker Compose file and connected to the same network, they can reach each other using the service name as the hostname.

Example (this system):

services:
  order-service:
    # Can reach kafka at kafka:9092
    environment:
      SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092
      SPRING_DATASOURCE_URL: jdbc:postgresql://order-db:5434/orderdb
      SPRING_REDIS_HOST: redis

  kafka:
    image: confluentinc/cp-kafka:7.6.1
    ports:
      - "9092:9092"    # accessible from host at localhost:9092
                       # accessible from containers at kafka:9092

  order-db:
    image: postgres:16
    ports:
      - "5434:5432"    # host port 5434 maps to container port 5432

Port mapping for host access:

Common mistake: Configuring Spring Boot with localhost:5432 — this looks up localhost inside the container, not the host machine.


Q15 — What is docker compose up --build vs docker compose up? junior

Answer:

Command Behavior
docker compose up Uses existing images; builds if image doesn't exist
docker compose up --build Force-rebuilds images before starting (picks up code changes)
docker compose build Build images without starting containers
docker compose up -d Start in detached mode (background)
docker compose down Stop and remove containers (volumes preserved)
docker compose down -v Stop, remove containers AND volumes (delete DB data)
docker compose restart order-service Restart a single service
docker compose pull Pull latest images from registry

Workflow when you change code:

# Rebuild and restart only the changed service
docker compose up -d --build order-service

# Or rebuild all
docker compose up -d --build

This system's build script (build.sh/build.ps1):

  1. Runs ./mvnw package -DskipTests for all services.
  2. Runs docker compose up -d --build to rebuild images and restart.

Q16 — How do you debug a running container? junior

Answer:

# View live logs
docker compose logs -f order-service

# View last 100 lines
docker compose logs --tail=100 order-service

# Execute a shell inside a running container
docker compose exec order-service bash
# or (if bash not available)
docker compose exec order-service sh

# Inside the container — check process, connections
ps aux
netstat -tulpn
curl http://localhost:8083/actuator/health

# Inspect container config and network
docker inspect order-service

# Check environment variables
docker compose exec order-service env | grep SPRING

# Copy file from container to host
docker cp order-service:/app/logs/application.log ./

Remote JVM debugging (add to ENTRYPOINT):

ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]
ports:
  - "5005:5005"   # expose debug port

Connect IDE remote debugger to localhost:5005.


Q17 — What is Docker's resource limiting and why is it important? senior

Answer: Without resource limits, one container can monopolize CPU or memory, starving other containers (or crashing the host).

Docker Compose resource limits:

services:
  order-service:
    deploy:
      resources:
        limits:
          cpus: '1.0'         # max 1 CPU core
          memory: 512m        # max 512 MB RAM
        reservations:
          cpus: '0.25'        # guaranteed 0.25 CPU
          memory: 256m        # guaranteed 256 MB

JVM and container memory: Without -XX:+UseContainerSupport (default ON in JDK 11+), JVM reads the host's total RAM (e.g., 32 GB) and sizes heap accordingly, ignoring the Docker memory limit → OOMKilled.

With container support:

# JVM uses 75% of container memory limit
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
# 512m container → 384m heap max

CPU throttling: cpus: '0.5' limits the container to 50% of one core using Linux CFS (completely fair scheduler) quota.


Q18 — What is a Docker health check and how does it work? senior

Answer: A health check periodically runs a command inside the container to determine if the application is healthy and ready for traffic.

In Dockerfile:

HEALTHCHECK --interval=30s \
            --timeout=5s \
            --start-period=60s \
            --retries=3 \
  CMD curl -sf http://localhost:8083/actuator/health | grep -q '"status":"UP"' || exit 1

In docker-compose.yml:

services:
  order-service:
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://localhost:8083/actuator/health"]
      interval: 30s
      timeout: 5s
      start_period: 60s   # grace period — don't check for 60s after start
      retries: 3          # mark unhealthy after 3 consecutive failures

Health states:

Integration with depends_on:

order-service:
  depends_on:
    order-db:
      condition: service_healthy

order-service waits until order-db is healthy before starting.


Q19 — How does Testcontainers use Docker and what are its advantages over embedded alternatives? senior

Answer: Testcontainers starts real Docker containers during tests and provides connection details (host, port) for the application to connect.

@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");

    @Container
    static KafkaContainer kafka = new KafkaContainer(
        DockerImageName.parse("confluentinc/cp-kafka:7.6.1"));

    @DynamicPropertySource
    static void overrideProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
    }

Advantages over embedded alternatives:

Embedded H2 Testcontainers PostgreSQL
DB dialect H2 (different SQL) Real PostgreSQL
Flyway migrations Some incompatibilities 100% compatible
JSON/JSONB Not supported Full support
Triggers, procedures Limited Full support
Test confidence Lower Higher

This system: Integration tests use Testcontainers with real PostgreSQL, real Kafka, and real Redis — same versions as production.


Q20 — What is Docker image scanning and why is it important? senior

Answer: Image scanning analyzes Docker images for known CVE (Common Vulnerability Exposures) vulnerabilities in base OS packages and application dependencies.

Common tools:

# Trivy (free, fast)
trivy image order-service:1.0.0

# Docker Scout (built into Docker Desktop)
docker scout cves order-service:1.0.0

# Snyk
snyk container test order-service:1.0.0

Sample output:

CRITICAL  CVE-2024-XXXX  openssl/libssl3  3.1.4  3.1.5  Fix available
HIGH      CVE-2024-YYYY  libc-bin         2.36   2.37   Fix available

CI/CD integration:

# GitHub Actions
- name: Scan image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'order-service:latest'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'   # fail CI on CRITICAL/HIGH

Minimize attack surface:


Q21 — What is the difference between docker compose v1 and v2? junior

Answer:

Docker Compose v1 Docker Compose v2
Command docker-compose (hyphen) docker compose (space)
Binary Separate Python binary Plugin built into Docker CLI
Installation pip install docker-compose Bundled with Docker Desktop
Performance Slower (Python) Faster (Go)
depends_on condition Not supported service_healthy supported
profiles Limited Full support

This system: Uses docker compose (v2) with depends_on: condition: service_healthy.

Profiles (useful for development vs production):

services:
  zipkin:
    profiles: ["observability"]   # only started with --profile observability

  order-service:
    # no profile — always started
docker compose --profile observability up -d   # starts all + Zipkin
docker compose up -d                           # starts core services only

Q22 — How do you handle log aggregation for containerized services? senior

Answer: Containers write logs to stdout/stderr. Docker captures these and makes them available via docker logs. For production, aggregate logs centrally.

Docker logging drivers:

services:
  order-service:
    logging:
      driver: "json-file"        # default — writes to host filesystem
      options:
        max-size: "100m"         # rotate at 100MB
        max-file: "5"            # keep 5 files

    # Or send directly to log aggregator:
    logging:
      driver: "fluentd"
      options:
        fluentd-address: "localhost:24224"
        tag: "order-service.{{.ID}}"

ELK Stack integration (Elasticsearch, Logstash, Kibana):

  1. Services log JSON to stdout.
  2. Filebeat (sidecar or host agent) ships logs to Logstash.
  3. Logstash parses and indexes to Elasticsearch.
  4. Kibana provides search and visualization.

Structured logging (JSON format) in Spring Boot:

logging:
  structured:
    format.console: ecs   # ECS JSON format (Spring Boot 3.4+)

Every log line becomes a JSON object with traceId, spanId, service.name, making log correlation trivial.


Q23 — What is Docker's --network host mode and when is it used? senior

Answer: In host network mode, the container shares the host's network namespace — no network isolation, no port mapping needed.

# Container uses host network directly
docker run --network host order-service:latest
# Listens on host's port 8083 directly (no docker NAT)

When to use:

Security risk: The container can access all host network interfaces and services on localhost. No network isolation from the host.

Linux only: --network host is not supported on Docker Desktop for Mac/Windows (which runs Docker in a Linux VM).

This system: Uses custom bridge networks (ecommerce-network) — best for microservices where services need to find each other by name.


Q24 — How do you perform zero-downtime deployments with Docker Compose? senior

Answer: Docker Compose is primarily designed for development. Zero-downtime deployment requires careful orchestration:

Rolling restart (single service update):

# Build new image
docker compose build order-service

# Scale to 2 instances
docker compose up -d --scale order-service=2

# Wait for new instance to be healthy
# Remove old instance (requires sticky routing or stateless service)
docker compose up -d --scale order-service=1

Blue-green (external load balancer required):

services:
  order-service-blue:
    image: order-service:1.0.0
    labels:
      traefik.enable: "true"
      traefik.http.routers.order-blue.rule: "Host(`order.example.com`)"

  order-service-green:
    image: order-service:1.1.0
    labels:
      traefik.enable: "false"   # disabled initially

Production recommendation: Use Kubernetes (kubectl rollout) for true zero-downtime deployments. Docker Compose is not designed for production orchestration at scale.


Q25 — What is docker system prune and when should you use it? junior

Answer: docker system prune removes unused Docker resources (stopped containers, unused images, unused networks, unused volumes).

# Remove stopped containers, unused networks, dangling images
docker system prune

# Also remove unused volumes (WARNING: deletes data!)
docker system prune --volumes

# Remove all unused images (not just dangling)
docker system prune -a

# Check disk usage before pruning
docker system df

Types of cleanup:

docker container prune    # remove stopped containers
docker image prune        # remove dangling (untagged) images
docker image prune -a     # remove all unused images
docker volume prune       # remove unused volumes
docker network prune      # remove unused networks

CI/CD best practice: Run docker system prune -f in CI after each build to prevent disk exhaustion from accumulated build artifacts. Use with caution in production (volumes contain data).


Q26 — How does Kafka run in Docker in KRaft mode without ZooKeeper? senior

Answer: Traditional Kafka requires ZooKeeper for cluster metadata management. KRaft (Kafka Raft) embeds cluster coordination directly into Kafka brokers, eliminating the ZooKeeper dependency.

This system's Kafka config:

kafka:
  image: confluentinc/cp-kafka:7.6.1
  environment:
    KAFKA_NODE_ID: 1
    KAFKA_PROCESS_ROLES: broker,controller    # single node = both roles
    KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
    KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
    KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
    KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
    KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
    CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk  # must be stable UUID
  volumes:
    - kafka-data:/var/lib/kafka/data      # persist topic data

Key listeners:

Benefits: No ZooKeeper container needed — reduces 14 containers to 13 (this system still uses 14 because of Schema Registry, Redis, Zipkin).


Q27 — What are Docker Compose profiles and how do you use them? junior

Answer: Profiles allow you to selectively start subsets of services. Services without a profile are always started; services with a profile only start when that profile is activated.

services:
  # Core services — always started
  order-service:
    image: order-service:latest

  kafka:
    image: confluentinc/cp-kafka:7.6.1

  # Optional observability stack
  zipkin:
    image: openzipkin/zipkin:3
    profiles: ["observability"]

  prometheus:
    image: prom/prometheus:latest
    profiles: ["observability"]

  grafana:
    image: grafana/grafana:latest
    profiles: ["observability"]

  # Development tools
  pgadmin:
    image: dpage/pgadmin4:latest
    profiles: ["tools"]

Usage:

# Core services only
docker compose up -d

# Core + observability
docker compose --profile observability up -d

# Core + observability + tools
docker compose --profile observability --profile tools up -d

Env variable alternative:

COMPOSE_PROFILES=observability,tools docker compose up -d

Q28 — How do you implement a container init sequence for dependent services? senior

Answer: depends_on: condition: service_healthy handles most cases. For complex init sequences, use init containers or wait scripts.

Custom wait script:

# wait-for-it.sh in image
COPY wait-for-it.sh /wait-for-it.sh
RUN chmod +x /wait-for-it.sh
ENTRYPOINT ["/wait-for-it.sh", "kafka:9092", "--", "java", "-jar", "app.jar"]

Kubernetes init container pattern (Docker Compose equivalent):

services:
  schema-migrator:          # run migrations first, exit 0
    image: flyway:10
    command: migrate
    depends_on:
      order-db:
        condition: service_healthy

  order-service:
    depends_on:
      schema-migrator:
        condition: service_completed_successfully
      kafka:
        condition: service_healthy

Best practice: Let Spring Boot's Flyway run migrations on startup (current approach). The service itself handles the init sequence. Only use separate migrators for shared schemas or complex multi-service migration coordination.


Q29 — How do you optimize Docker image size? senior

Answer: Large images increase pull times, storage costs, and attack surface.

Strategies:

1. Use minimal base images:

# 700 MB
FROM openjdk:21
# 200 MB
FROM eclipse-temurin:21-jre
# 50 MB (no shell, no package manager)
FROM gcr.io/distroless/java21-debian12

2. Multi-stage builds: Separate build tools from runtime (see Q5).

3. Minimize layers: Combine related RUN commands:

# 3 layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# 1 layer
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

4. .dockerignore: Exclude unnecessary files from build context:

target/
.git/
*.md
src/test/

5. Spring Boot layered JAR (see Q5): Separate dependencies from application code for better caching.

Size analysis:

docker history order-service:latest --no-trunc  # see layer sizes
dive order-service:latest                        # interactive layer explorer

Q30 — What are the security best practices for Docker in production? senior

Answer:

1. Run as non-root user:

RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser   # never run as root

2. Use read-only filesystem (where possible):

services:
  order-service:
    read_only: true
    tmpfs:
      - /tmp   # only /tmp is writable

3. Drop Linux capabilities:

security_opt:
  - no-new-privileges:true
cap_drop:
  - ALL
cap_add:
  - NET_BIND_SERVICE  # only if binding to port < 1024

4. Scan images for CVEs (see Q20): Integrate trivy in CI pipeline.

5. Use specific image tags (not latest): postgres:16.3 instead of postgres:latest.

6. Limit network exposure: Only publish necessary ports. Internal services should not have ports: mappings.

7. Secret management: Never pass secrets via environment variables for production. Use Docker secrets, Vault, or cloud secret managers.

8. Resource limits: Prevent resource exhaustion attacks (see Q17).

9. Container registry security: Use private registries, enable vulnerability scanning, sign images (Docker Content Trust).

10. Rootless Docker: Run Docker daemon as non-root (Podman alternative).