Docker Interview Questions

Registry Mirroring and Pull-Through Cache

questions
Scroll to track progress

Your team pulls images from Docker Hub during CI. Docker Hub rate-limits unauthenticated requests to 100 per 6 hours. Your CI runs 50 builds per day; with multiple stages per build, you hit the rate limit by noon. Builds fail with "too many requests." You need a solution that doesn't require paying Docker Hub's Premium plan. Design a caching strategy.

Docker Hub rate limits are a real bottleneck. Solutions: (1) Authenticate requests: get a Docker Hub account and authenticate in CI. Even free accounts get 200 pulls per 6 hours. In CI, run docker login -u user -p token before pulling. (2) Use a pull-through cache (Docker Registry Mirror): set up a private Docker registry that caches pulls from Docker Hub. Configuration: in /etc/docker/daemon.json on build nodes, set "registry-mirrors": ["https://your-registry:5000"]. When the node pulls ubuntu:22.04, if it's not in your cache, it fetches from Docker Hub and caches it locally. Subsequent pulls are instant. (3) Use a managed registry with built-in caching: Artifactory, Nexus, or ECR (if on AWS) provide pull-through cache capabilities. (4) Cache images in your internal registry: instead of pulling from Docker Hub each time, periodically copy base images to your internal registry. Then pull from internal registry in CI. (5) Reduce image pulls: if you're pulling the same image 50 times per day, pull once and reuse it across builds. Use docker save/load to cache images locally. (6) For production deployment, use an air-gapped approach: pre-pull all images you need to a private registry before deployment. Then deploy only from the internal registry. Implementation: Set up a Docker Registry Mirror on your infrastructure: docker run -d -p 5000:5000 -v /var/lib/registry:/var/lib/registry registry:latest. Configure all build nodes to use this mirror: /etc/docker/daemon.json: "registry-mirrors": ["http://mirror:5000"]. This caches all pulls locally; subsequent pulls are fast and don't hit Docker Hub's rate limit.

Follow-up: How do you invalidate/refresh cached images in a mirror? What if Docker Hub updates a base image?

You've set up a Docker Registry Mirror, but your data center has poor connectivity to Docker Hub (high latency, packet loss). The initial cache population takes hours because each layer must be pulled over the slow link. You need the cache to populate faster. How do you optimize mirror population under bandwidth constraints?

Slow mirrors are worse than no mirrors. Optimize: (1) Parallelize pulls: instead of pulling images sequentially, pull multiple images in parallel. In CI, run docker pull ubuntu:22.04 & docker pull golang:1.18 &. This saturates available bandwidth. (2) Use layer deduplication: Docker layers are content-addressed by hash. If ubuntu:22.04 and ubuntu:22.10 share layers, the registry downloads them only once. Ensure your registry implementation supports this (most do). (3) Compress layers in transit: use HTTP compression (gzip) to reduce bandwidth. Docker Registry supports this. (4) Cache at multiple levels: set up regional mirrors in each data center. Each regional mirror caches from a upstream mirror or Docker Hub. This distributes load and reduces WAN traffic. (5) Batch pre-populate the cache during maintenance windows: don't wait for CI to populate the cache. During off-hours, systematically pull all images your team uses and cache them. Script: for image in $(cat required-images.txt); do docker pull $image; done. (6) Use image layer caching from build stage: if your build process generates intermediate images, cache them immediately. Don't re-download them later. (7) Implement predictive caching: analyze your CI usage and pre-pull images that are likely needed. (8) Use a faster upstream source: if Docker Hub is slow, try a CDN-backed mirror like a regional Docker Hub endpoint. Some regions have faster access. Implementation: Set up regional mirrors in each datacenter. Configure CI nodes to use local regional mirror first, then fallback to upstream. Implement batch pre-population script that runs daily during maintenance. This ensures the cache is warm by the time CI starts, avoiding slow initial pulls.

Follow-up: How does Docker layer deduplication work? Can different registries share layers?

You've deployed a Docker Registry Mirror, but you're concerned about cache consistency. A base image (ubuntu:22.04) is updated on Docker Hub with security patches. Your mirror still has the old image. CI nodes pull the stale image with vulnerabilities. How do you keep the mirror cache up-to-date with upstream?

Cache staleness is a problem. Image tags (e.g., ubuntu:22.04) can point to different layers over time. The mirror must handle this. Solutions: (1) Don't rely on tags for caching; use image digests: always pull ubuntu@sha256:abc123 instead of ubuntu:22.04. Digests never change; they identify exact content. If you want to update, pull a new digest explicitly. (2) Implement cache invalidation: configure the mirror to re-validate images periodically. Every 24 hours, check if the upstream image has changed. If so, re-pull. Registry configuration: set cache-control headers and expiration. (3) Use a pull-through cache that validates freshness: Artifactory and Nexus support metadata validation. They check if the upstream image is newer than the cached version before serving. (4) Manually trigger cache updates: when a base image is updated, trigger a manual refresh: docker pull --no-cache ubuntu:22.04, which forces re-fetch from upstream. (5) Use immutable tags in your applications: instead of pulling ubuntu:22.04 (mutable), use ubuntu@sha256:xyz (immutable digest). When you want to update, explicitly update the digest. This gives you control over when updates happen. (6) Separate base image management from application images: designate a team to manage base images. They periodically update base images, scan them for CVEs, and push new versions. Applications use pinned digests of these base images. (7) Implement a cache invalidation policy: set TTL for cached images: fresh for 24 hours, then re-validate. If upstream hasn't changed, serve from cache. If upstream updated, re-pull. Best practice: use digests for production deployments (immutable). Use tags for development (mutable, regularly refreshed). Configure the mirror to re-validate tags daily and auto-refresh if upstream changes. This balances performance (caching) with correctness (freshness).

Follow-up: What's the difference between image tags and digests? Why should you use digests in production?

You're using Docker Registry Mirror for pull-through cache. A developer accidentally pushes a malicious image to your internal registry (they think it's a normal layer). The malicious image gets cached and served to other builds. You realize too late—multiple builds have already used it. How do you prevent cache poisoning and recover from it?

Cache poisoning—injecting malicious content into the cache—is a security risk. Prevent and recover: (1) Use read-only pull-through caches: configure the registry mirror to only cache pulls from upstream, not accept pushes. In registry configuration: set readonly: true for pull-through endpoints. This prevents accidental (or malicious) pushes. (2) Separate registries for different purposes: a read-only pull-through cache for upstream images (ubuntu, golang, etc.). A separate internal registry where you push your own images. Never push to the pull-through cache. (3) Implement image signing and verification: all images in the internal registry must be signed. When pulling, verify the signature. Malicious unsigned images are rejected. Use Notary or Cosign for signing. (4) Use admission controllers to validate images: Kubernetes admission controller (Kyverno, OPA) validates all images against a policy: only allow images from trusted registries, signed, with valid signatures. (5) Audit and monitor the cache: log all pushes and pulls. Monitor for unusual activity. (6) For recovery from poisoning: identify affected builds (grep build logs for the malicious image digest). Re-run builds with clean images. Verify affected deployments haven't propagated the malicious image. (7) Implement cache invalidation: if poisoning is detected, clear the cache and re-populate from upstream. (8) Implement content verification: compute SHA256 hashes of cached images and compare against upstream. Mismatches indicate tampering. Implementation: Docker Registry configured as read-only pull-through cache. Separate internal registry for custom images (with push permissions). All custom images signed with Cosign. Admission controller verifies signatures before pod creation. This prevents poisoning while maintaining the benefits of caching.

Follow-up: How do you sign images and verify signatures in a CI/CD pipeline? What's the workflow?

You have multiple CI environments (dev, staging, prod) in separate regions. Each has its own Docker Registry Mirror. Developers push images to the dev mirror, which need to be promoted to staging and prod mirrors. Currently, there's no coordination—images are duplicated across mirrors, causing storage bloat and consistency issues. Design a multi-region registry strategy.

Multi-region registries require careful coordination. Design: (1) Use a single source-of-truth registry: one canonical registry (e.g., central ECR in us-east-1) holds all images. Regional mirrors are read-only pull-through caches of this canonical registry. Developers push only to the canonical registry. (2) Replicate images explicitly: use a promotion pipeline. When an image passes tests in dev, it's promoted to staging by tagging it staging-version and pushing to canonical registry. Staging region's mirror auto-caches it on next pull. (3) Use regional pull-through caches, not duplicate registries: each region runs a Docker Registry Mirror pointing to the canonical registry. Pulls in eu-west-1 hit the local EU mirror first (cache), then fallback to canonical registry in us-east-1. (4) Implement image lifecycle policies: images older than 90 days are deleted. This prevents indefinite storage growth. (5) Use geo-replication for the canonical registry: if using AWS ECR, enable cross-region replication. Images pushed to us-east-1 are automatically replicated to eu-west-1, ap-south-1, etc. (6) Implement a promotion workflow: dev image → passes tests → promoted to staging tag → deployed to staging region → passes integration tests → promoted to prod tag → deployed to prod region. Only prod-tagged images are deployed to production. (7) Use digests for immutability: tag images with both version (prod-1.0.0) and content digest (prod-1.0.0@sha256:xyz). This ensures deployed images are immutable even if tags are re-used. Implementation: Canonical ECR in us-east-1 with cross-region replication to eu-west-1, ap-south-1. Each region runs a local Docker Registry Mirror for pull-through cache. CI promotes images through dev -> staging -> prod by creating tags and pushing to canonical registry. This ensures single source of truth, prevents duplication, and enables efficient caching per region.

Follow-up: How do you handle image deletion across multiple regions? If you delete an image from the canonical registry, do replicas get deleted too?

Your Docker Registry Mirror is running on shared infrastructure with limited CPU and memory. As more CI builds run in parallel, the mirror becomes a bottleneck—it can't decompress and serve layers fast enough. Build pull times increase from 10 seconds to 60+ seconds. How do you scale the mirror?

Mirrors can become bottlenecks under load. Scale: (1) Add more mirror instances: run multiple registry replicas behind a load balancer. Each replica has its own local cache. Load balancer distributes pulls across them. Configuration: docker run -d -p 5000:5000 registry:latest (run on multiple nodes). Behind nginx load balancer that routes to each node. (2) Use high-performance storage backend: replace local filesystem with faster storage. Use SSD instead of HDD. Mount a network storage (NFS, S3) for layer cache. S3 is highly scalable but has higher latency; local SSD is faster. (3) Implement layer caching in memory: use a caching layer (Redis, memcached) in front of the registry to cache frequently accessed layers. (4) Use a managed registry service: delegate to Artifactory, Nexus, or ECR which handle scaling automatically. (5) Optimize compression: layers are served compressed (gzip). Decompression is CPU-intensive. Use faster compression (brotli, zstd) or serve pre-compressed layers. (6) Reduce layer sizes: in your Dockerfile, minimize layers and use layer caching efficiently. Smaller layers pull faster. (7) Implement parallel layer pulls: when pulling an image, Docker can fetch multiple layers in parallel (default 5). Increase this on CI nodes: in /etc/docker/daemon.json, set "max-concurrent-uploads": 20 and "max-concurrent-downloads": 20. (8) Use P2P layer sharing: tools like Harbor support peer-to-peer layer distribution. Nodes share layers with each other, reducing load on the central mirror. Implementation: Set up 3-5 registry replicas on SSD-backed nodes. Behind a load balancer. Enable parallel downloads on CI nodes. Monitor mirror CPU and memory; auto-scale if load increases. This ensures the mirror can handle high throughput without becoming a bottleneck.

Follow-up: What's the cost of running a private registry at scale? When does it make sense vs. using a managed service?

You're using Docker Registry Mirror for internal images. A developer wants to pull images from a private third-party registry (not Docker Hub). Your mirror doesn't know how to authenticate to third-party registries—it only proxies Docker Hub. How do you extend the mirror to support multiple upstream registries with different authentication?

Standard mirrors proxy a single upstream registry (Docker Hub). Supporting multiple upstreams with auth is complex. Solutions: (1) Use a sophisticated registry like Harbor: Harbor can proxy multiple registries and authenticate to each. Configuration: Harbor admin UI allows adding multiple proxy sources with their own credentials. (2) Separate your pull-through caches by upstream: instead of one universal mirror, run multiple mirrors, each proxying a different upstream. CI nodes configure docker daemon to know which mirror to use for which registry: "registry-mirrors": ["https://docker-mirror:5000", "https://artifactory:8081"] (primary, secondary). Docker tries them in order. (3) For pull-through cache that needs multi-upstream support, implement custom authentication logic: extend the registry with a custom middleware that checks which upstream a pull is for, loads the right credentials, and proxies accordingly. (4) Use a sidecar authentication proxy: in front of the registry, run an authentication proxy that intercepts pulls, injects the right credentials, and forwards to the registry. Then the registry proxies to the upstream. (5) Pre-pull and cache third-party images locally: instead of real-time proxying, periodically pull third-party images to your internal registry. Developers pull from internal registry, not third-party directly. (6) Use a service mesh (Istio) to inject credentials: the mesh can intercept pulls and inject credentials based on destination. (7) Manage credentials securely: store upstream credentials in a secrets store (HashiCorp Vault, AWS Secrets Manager). The registry/proxy retrieves them at runtime. Implementation: Use Harbor as the central mirror. Configure multiple proxy sources (Docker Hub, Artifactory, Quay). Each proxy source has its own upstream URL and credentials. CI nodes pull from Harbor; Harbor routes to the appropriate upstream based on image name. This provides a unified mirror interface while supporting multiple upstreams and authentication schemes.

Follow-up: How do you manage credentials securely for multiple upstream registries? Should they be in environment variables, config files, or secrets management systems?

Your organization is air-gapped (no internet access) for security reasons. You can't pull directly from Docker Hub. You need to pre-pull all images to an internal registry before deploying. You have hundreds of images across many projects. Design an image distribution process for air-gapped environments.

Air-gapped environments require offline image distribution. Process: (1) Set up a sneaker-net or manual distribution: download images outside the air-gap (on a connected machine), save them as tar files, transfer via USB/network to the air-gapped environment, then load into the internal registry. (2) Create a central image repository outside the gap: set up a machine with internet access that periodically pulls images from Docker Hub, upstream registries, etc. Save them locally. (3) Implement an image sync tool: use image-sync or similar tools to mirror registries. Outside the gap, sync images periodically: docker pull ubuntu:22.04 && docker save ubuntu:22.04 > ubuntu.tar. (4) Transfer via approved channels: move tar files through a secure transfer mechanism (encrypted USB, SFTP, approved networks). (5) Inside the gap, load images to local registry: docker load < ubuntu.tar && docker tag ubuntu:22.04 internal-registry:5000/ubuntu:22.04 && docker push internal-registry:5000/ubuntu:22.04. (6) Automate distribution: create a CI/CD pipeline that runs outside the gap. It pulls and builds images, saves them, generates a manifest file listing all images and their digests. Copy the manifest and tar files to a secure transfer location. Inside the gap, read the manifest and load all images. (7) Use a dedicated image distribution server: outside the gap, host a private registry with all images needed. Inside the gap, set up a one-way synchronization: a scheduled job periodically checks the outside registry (via approved network link) and pulls new images. (8) Maintain an image inventory: keep a list of all images your organization uses (Dockerfile base images, third-party images). Regularly update this inventory and sync all images. Implementation: External machine runs sync-registries.sh daily: for image in $(cat images.txt); do docker pull $image && docker save $image > $image.tar; done. Transfer tar files to air-gapped internal registry via approved channel. Inside air-gap, load-images.sh loads all tar files to local registry. All deployments pull from local registry. This ensures air-gapped environments have all necessary images without internet access.

Follow-up: How do you handle image updates in air-gapped environments? When Docker Hub releases a new version, how do you get it inside the gap?

Want to go deeper?