How to Configure Nginx as a Reverse Proxy for Docker Containers: A Complete Guide
Nginx can terminate TLS and route traffic to Docker containers by running on a shared Docker bridge network, using container names as upstream addresses in proxy_pass directives.
The mikeroyal/Self-Hosting-Guide repository documents several methods to configure Nginx as a reverse proxy for Docker containers, ranging from manual configuration to automated solutions like nginx-proxy and Nginx Proxy Manager. This guide walks you through the architectural patterns and production-ready implementations found in the repository.
Architecture Overview
A typical reverse proxy setup routes traffic through a clean separation of concerns:
Client → Internet → Nginx (host) → Docker bridge network → Container(s)
Nginx runs either on the host or in its own container, listening on ports 80 and 443. It forwards requests based on the Host header or request path to the appropriate backend container. Docker containers expose only their internal ports, avoiding port collisions on the host and keeping the network surface minimal. TLS termination occurs at the Nginx layer, allowing backend containers to run unencrypted HTTP internally, which simplifies image builds and certificate management.
Setting Up the Docker Network
Before configuring Nginx, create an isolated bridge network that allows the proxy to communicate with application containers using DNS names.
# Create an isolated network for the proxy and the app containers
docker network create proxy-tier
This network enables Nginx to reach containers by their container names (e.g., webapp:8080) rather than IP addresses, which is essential for dynamic container environments.
Configuring Nginx as a Reverse Proxy
Manual Configuration
For static setups, mount a custom Nginx configuration file into an Nginx container. Create a virtual host file at nginx/conf.d/app.example.com.conf:
server {
listen 80;
server_name app.example.com;
# Optional TLS configuration
# listen 443 ssl;
# ssl_certificate /etc/nginx/certs/app.example.com.crt;
# ssl_certificate_key /etc/nginx/certs/app.example.com.key;
location / {
proxy_pass http://webapp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
The proxy_pass directive uses the container name webapp and internal port 8080, relying on Docker's embedded DNS resolver on the proxy-tier network.
Automatic Configuration with docker-gen
For dynamic environments where containers start and stop frequently, the nginx-proxy project (referenced in the repository's README.md under the Nginx Proxy section) uses docker-gen to automatically regenerate configuration files.
Add a docker-gen service to your docker-compose.yml:
nginx-gen:
image: nginxproxy/docker-gen
container_name: nginx-gen
restart: unless-stopped
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- /var/run/docker.sock:/tmp/docker.sock:ro
command: -watch -notify-sighup nginx /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
depends_on:
- nginx
networks:
- proxy-tier
When application containers set the VIRTUAL_HOST environment variable, docker-gen detects the change, renders a new Nginx configuration from the template, and sends a SIGHUP signal to reload Nginx without dropping connections.
Production-Ready Docker Compose Configuration
Below is a complete docker-compose.yml that implements the manual configuration pattern described in the mikeroyal/Self-Hosting-Guide repository:
version: "3.9"
services:
nginx:
image: nginx:stable-alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/certs:/etc/nginx/certs:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy-tier
webapp:
image: nginx:alpine
container_name: webapp
restart: unless-stopped
expose:
- "8080"
environment:
VIRTUAL_HOST: app.example.com
networks:
- proxy-tier
networks:
proxy-tier:
external: true
This configuration mounts the docker.sock file for optional integration with automation tools, exposes only port 8080 internally (not mapped to the host), and connects both services to the external proxy-tier network.
Alternative: Nginx Proxy Manager
According to the README.md section Nginx Proxy Manager (NPM), the repository also recommends a UI-driven alternative for those who prefer graphical management. Nginx Proxy Manager runs as a Docker container and provides a web interface for configuring reverse proxies, managing SSL certificates, and setting up redirections, eliminating the need to manually edit Nginx configuration files.
Summary
- Create a dedicated Docker network (
proxy-tier) to enable DNS-based service discovery between Nginx and your containers. - Expose only internal ports on application containers to avoid host port conflicts and reduce the attack surface.
- Use
proxy_passwith container names (e.g.,http://webapp:8080) to route traffic, leveraging Docker's internal DNS. - Terminate TLS at the Nginx layer to simplify certificate management and keep backend traffic unencrypted.
- Automate configuration with
docker-genor use Nginx Proxy Manager for a UI-based approach as documented in themikeroyal/Self-Hosting-Guiderepository.
Frequently Asked Questions
How does Nginx resolve container names as upstream addresses?
When Nginx and application containers share the same Docker bridge network (such as proxy-tier), Docker's embedded DNS server automatically resolves container names to their current IP addresses. Nginx uses standard DNS resolution for the hostname in the proxy_pass directive, allowing containers to be restarted or recreated with different IPs without reconfiguring Nginx.
Should Nginx run on the host or inside a container?
Both approaches work, but running Nginx in a container is generally preferred for consistency and ease of deployment. When containerized, Nginx can still bind to host ports 80 and 443 via port mappings, and it communicates with backend containers over the Docker network. This approach matches the containerized patterns documented in the mikeroyal/Self-Hosting-Guide repository.
What is the difference between expose and ports in Docker Compose?
The expose directive in a docker-compose.yml file makes a port available to other containers on the same network without publishing it to the host, while ports creates a binding between the host's network interface and the container. For reverse proxy setups, backend containers should use expose for their service ports, while the Nginx container uses ports to accept public traffic on 80 and 443.
How do I handle SSL certificates when using Nginx as a reverse proxy?
Mount SSL certificates into the Nginx container at a path like /etc/nginx/certs/ and configure the ssl_certificate and ssl_certificate_key directives in your server block. Nginx terminates the TLS connection and forwards unencrypted HTTP to the backend containers. Tools like nginx-proxy or Nginx Proxy Manager can automatically provision and renew Let's Encrypt certificates based on the VIRTUAL_HOST environment variable.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →