When deploying multiple websites on a single host using Docker, the traditional port-based exposure becomes impractical for web hosting scenarios. The solution lies in combining Docker's isolation capabilities with reverse proxy routing based on virtual hosts.
Here's a complete implementation using Nginx as reverse proxy and Docker containers:
# nginx.conf (host machine)
server {
listen 80;
server_name www.somewebsite.com;
location / {
proxy_pass http://container1:80;
proxy_set_header Host $host;
}
}
server {
listen 80;
server_name www.otherwebsite.com;
location / {
proxy_pass http://container2:80;
proxy_set_header Host $host;
}
}
Each website runs in its own container with identical port mappings, differentiated by hostname routing:
# docker-compose.yml for website A
version: '3'
services:
webserver:
image: custom-lamp-image
container_name: container1
ports:
- "8080:80"
volumes:
- ./site1:/var/www/html
# docker-compose.yml for website B
version: '3'
services:
webserver:
image: custom-lamp-image
container_name: container2
ports:
- "8081:80"
volumes:
- ./site2:/var/www/html
The overhead of containerization is minimal (1-3% CPU, negligible memory for isolated processes). Benefits include:
- Process isolation prevents resource contention
- Security boundaries between tenants
- Independent scaling of individual sites
- Simplified backup/restore via container snapshots
For large-scale hosting, automate container provisioning with this bash script:
#!/bin/bash
DOMAIN=$1
CONTAINER_NAME="site_${DOMAIN//./_}"
docker run -d \
--name $CONTAINER_NAME \
-e VIRTUAL_HOST=$DOMAIN \
-v /sites/$DOMAIN:/var/www/html \
custom-lamp-image
# Auto-generate Nginx config
echo "server {
listen 80;
server_name $DOMAIN;
location / {
proxy_pass http://$CONTAINER_NAME:80;
}
}" > /etc/nginx/conf.d/$DOMAIN.conf
nginx -s reload
For more complex scenarios, consider:
- Traefik as reverse proxy with automatic Docker discovery
- Kubernetes ingress controllers for large-scale deployments
- Apache with mod_proxy in place of Nginx
When managing shared hosting environments, isolation between user websites becomes critical. Traditional approaches using chroot or separate user accounts have limitations in resource control and security. Docker presents an intriguing alternative for creating isolated LAMP environments per website.
The core concept involves:
+-----------------------+
| Host System |
| - Reverse Proxy |
| - Docker Daemon |
+-----------------------+
| |
| +--> [Website1 Container] (LAMP stack)
| +--> [Website2 Container] (LAMP stack)
| +--> [WebsiteN Container] (LAMP stack)
Here's a practical implementation using Nginx as reverse proxy:
# docker-compose.yml for website containers
version: '3'
services:
website1:
image: custom-lamp-image
environment:
- VIRTUAL_HOST=www.somewebsite.com
volumes:
- ./website1:/var/www/html
website2:
image: custom-lamp-image
environment:
- VIRTUAL_HOST=www.otherwebsite.com
volumes:
- ./website2:/var/www/html
# Nginx reverse proxy configuration
server {
listen 80;
server_name www.somewebsite.com;
location / {
proxy_pass http://website1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 80;
server_name www.otherwebsite.com;
location / {
proxy_pass http://website2;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
While Docker adds minimal overhead (1-3% typically), consider:
- Shared database containers for better resource utilization
- Bind mounts for persistent storage instead of volumes
- Resource limits in docker-compose.yml
Container isolation provides:
- Process isolation between websites
- Filesystem isolation
- Network namespace separation
- Resource constraints per container
For dynamic environments, Traefik automatically discovers containers:
# docker-compose.yml with Traefik labels
services:
website1:
image: custom-lamp-image
labels:
- "traefik.http.routers.website1.rule=Host(www.somewebsite.com)"
volumes:
- ./website1:/var/www/html
Options for MySQL/MariaDB:
- Separate DB container per website (maximum isolation)
- Shared DB container with separate databases
- External managed database service
version: '3.7'
services:
wordpress1:
image: wordpress:php7.4-apache
environment:
WORDPRESS_DB_HOST: db1
WORDPRESS_DB_USER: user1
WORDPRESS_DB_PASSWORD: password1
WORDPRESS_DB_NAME: wp1
VIRTUAL_HOST: site1.example.com
volumes:
- ./wp-content1:/var/www/html/wp-content
db1:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: wp1
MYSQL_USER: user1
MYSQL_PASSWORD: password1