20 Controlling HTTP traffic to containers with a reverse proxy
This chapter explains why a reverse proxy is essential when running many containerized applications behind the standard HTTP and HTTPS ports (80 and 443). In Docker, only one container can bind to a given port on the host, so a reverse proxy becomes the single gateway that receives all external requests and forwards them to the correct internal containers. Placing the proxy in front centralizes routing, simplifies scaling and updates, improves security by keeping app containers internal-only, and lets teams use the same setup across development, test, and production. The chapter demonstrates these ideas using two popular proxies that run in containers: Nginx and Traefik.
With Nginx, the chapter shows host-based routing (using the Host header) and path-based routing to publish multiple apps from one domain or many domains. Nginx also adds infrastructure capabilities: it performs load balancing across multiple upstream containers, terminates SSL centrally (including redirects from HTTP to HTTPS and certificate management), and can cache responses to boost performance and reduce upstream load. Caching can also be tuned to serve stale content during upstream failures to improve perceived reliability. The trade-off is operational: Nginx relies on per-site configuration files and reloads on change, and although powerful and fast, it is container-agnostic and discovers backends via DNS/IP rather than integrating with Docker metadata.
Traefik, by contrast, is container-aware and builds its configuration dynamically by watching the Docker API. You attach routing rules through container labels, and Traefik composes entrypoints, routers, services, and middlewares to match requests by host and path, rewrite paths, and forward traffic to the right containers without manual reloads. It supports SSL termination with automatic certificate handling and makes features like sticky sessions straightforward via cookies; the main limitation is the lack of a built-in cache. The chapter concludes with three key patterns a reverse proxy enables: hosting many domains on the same ports, exposing microservices behind a single domain with path-based routing, and gradually extracting features from a legacy monolith into new services while keeping a unified external interface.
A reverse proxy is the gateway to your apps; application containers are not publicly available.
Nginx is an HTTP server—it can serve static content and run as a reverse proxy.
The reverse proxy in action, loading content from an application container behind the scenes
In a simple proxy configuration, Nginx sends the response from the app—even if it’s a failure.
Nginx takes care of load-balancing so you can run app containers at scale.
Nginx can route requests to different containers, based on the domain name or request path.
Nginx redirects HTTP requests to HTTPS and serves them with an SSL certificate.
Using Nginx as a caching proxy reduces the workload for application containers.
If the proxy has the response in its cache, it sends it without using the upstream.
Nginx caching can be fine-tuned to keep content fresh or to add reliability to your app.
The Traefik dashboard shows you the configuration for all the apps being proxied.
Traefik uses the Docker API to find containers and labels, using them to build configuration.
Routing rules let you present a multi-container app at a single domain.
Using Traefik for HTTPS—it can generate certificates or fetch them from third-party providers.
Enabling sticky sessions in Traefik—it uses cookies to send the client to the same container.
Using a reverse proxy to host many applications with different domain names in one cluster.
Microservices exposed by the reverse proxy are part of the same application domain.
The reverse proxy hides the monolithic architecture so it can be broken into smaller services.
Lab
We’ve got a whole new app for this lab—one that will clearly show the power of a caching reverse proxy. It’s a simple website that calculates pi to a specified number of decimal places. In the lab folder for this chapter, you can run the app with Docker Compose and browse to http://localhost:8031/?dp=50000 to see what pi looks like to 50,000 decimal places. Refresh the browser and you’ll see it takes just as long to compute the same response. Your job is to run the app behind a reverse proxy:
- The app should be available at the domain pi.local on the standard HTTP port.
- The proxy should cache responses so that when users repeat the same request, the response is served from the cache and is much faster than from the app.
- The proxy should add resilience, so if you kill the app container, any cached responses are still available from the proxy.
My solution is up on GitHub, and you’ll find there are huge time savings from caching proxies with compute-intensive work like this: https://github.com/sixeyed/diamol/ blob/2e/ch20/lab/README.md.
Learn Docker in a Month of Lunches, Second Edition ebook for free