Secure your Docker deployments by creating isolated custom bridge networks, implementing container segmentation, and configuring network access controls to prevent unauthorized communication between containers.
Prerequisites
- Docker installed
- Root or sudo access
- Basic networking knowledge
- Understanding of container concepts
What this solves
Docker's default bridge network allows all containers to communicate freely, creating security risks in multi-application environments. Custom bridge networks provide container isolation, network segmentation, and granular access controls to prevent data breaches and lateral movement attacks.
Docker network fundamentals and security risks
Docker's default networking model creates several security vulnerabilities. The default bridge network assigns containers to a shared subnet where they can communicate without restrictions. This means a compromised web application container could access your database container directly.
Custom bridge networks solve this by creating isolated network segments. Each network operates independently with its own subnet, and containers can only communicate within their assigned network unless explicitly configured otherwise.
Step-by-step implementation
Install Docker and verify network capabilities
First, ensure Docker is installed and check the current network configuration.
sudo apt update
sudo apt install -y docker.io docker-compose
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
Log out and back in for group changes to take effect, then verify Docker networks:
docker network ls
docker network inspect bridge
Create isolated custom bridge networks
Create separate networks for different application tiers. This example creates networks for web, application, and database layers.
# Create frontend network for web servers
docker network create --driver bridge \
--subnet=172.20.1.0/24 \
--gateway=172.20.1.1 \
--opt com.docker.network.bridge.name=web-bridge \
frontend-net
Create backend network for application servers
docker network create --driver bridge \
--subnet=172.20.2.0/24 \
--gateway=172.20.2.1 \
--opt com.docker.network.bridge.name=app-bridge \
backend-net
Create database network for data tier
docker network create --driver bridge \
--subnet=172.20.3.0/24 \
--gateway=172.20.3.1 \
--opt com.docker.network.bridge.name=db-bridge \
--opt com.docker.network.bridge.enable_icc=false \
database-net
Configure network access policies with iptables
Implement firewall rules to control traffic between network segments. These rules prevent unauthorized cross-network communication.
# Block inter-container communication on database network
sudo iptables -I DOCKER-USER -i db-bridge -o db-bridge -j DROP
Allow only backend network to access database network
sudo iptables -I DOCKER-USER -s 172.20.2.0/24 -d 172.20.3.0/24 -j ACCEPT
Allow only frontend to access backend on specific ports
sudo iptables -I DOCKER-USER -s 172.20.1.0/24 -d 172.20.2.0/24 -p tcp --dport 8080 -j ACCEPT
Block direct frontend to database access
sudo iptables -I DOCKER-USER -s 172.20.1.0/24 -d 172.20.3.0/24 -j DROP
Save iptables rules
sudo iptables-save > /etc/iptables/rules.v4
Create network policy enforcement script
Automate network security policy application with a reusable script.
#!/bin/bash
Docker Network Security Policy Script
set -euo pipefail
echo "Applying Docker network security policies..."
Function to check if rule exists
rule_exists() {
iptables -C DOCKER-USER "$@" 2>/dev/null
}
Function to add rule if it doesn't exist
add_rule_if_not_exists() {
if ! rule_exists "$@"; then
iptables -I DOCKER-USER "$@"
echo "Added rule: $@"
else
echo "Rule already exists: $@"
fi
}
Database network isolation
add_rule_if_not_exists -i db-bridge -o db-bridge -j DROP
add_rule_if_not_exists -s 172.20.2.0/24 -d 172.20.3.0/24 -j ACCEPT
Frontend to backend communication (port 8080 only)
add_rule_if_not_exists -s 172.20.1.0/24 -d 172.20.2.0/24 -p tcp --dport 8080 -j ACCEPT
Block direct frontend to database
add_rule_if_not_exists -s 172.20.1.0/24 -d 172.20.3.0/24 -j DROP
Allow container health checks
add_rule_if_not_exists -p icmp --icmp-type echo-request -j ACCEPT
echo "Network security policies applied successfully"
Log current rules for audit
iptables -L DOCKER-USER -n --line-numbers > /var/log/docker-network-rules.log
echo "Rules logged to /var/log/docker-network-rules.log"
sudo chmod +x /opt/docker-network-security.sh
sudo /opt/docker-network-security.sh
Deploy containers to isolated networks
Launch containers in their designated network segments to test isolation.
version: '3.8'
services:
web:
image: nginx:alpine
container_name: frontend-web
networks:
- frontend-net
ports:
- "80:80"
environment:
- NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx/conf.d
restart: unless-stopped
app:
image: node:alpine
container_name: backend-app
networks:
- frontend-net
- backend-net
ports:
- "8080:8080"
working_dir: /app
command: sh -c "echo 'const express = require(\"express\"); const app = express(); app.get(\"/health\", (req, res) => res.json({status: \"healthy\"})); app.listen(8080);' > server.js && npm init -y && npm install express && node server.js"
restart: unless-stopped
database:
image: postgres:alpine
container_name: backend-db
networks:
- database-net
- backend-net
environment:
- POSTGRES_DB=appdb
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=securepassword123
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
networks:
frontend-net:
external: true
backend-net:
external: true
database-net:
external: true
volumes:
postgres_data:
docker-compose up -d
Configure container-level security policies
Add additional security constraints using Docker's built-in security options.
version: '3.8'
services:
secure-web:
image: nginx:alpine
container_name: secure-frontend
networks:
- frontend-net
security_opt:
- no-new-privileges:true
- apparmor:docker-default
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
read_only: true
tmpfs:
- /tmp:size=100M,noexec,nosuid,nodev
- /var/cache/nginx:size=50M,noexec,nosuid,nodev
- /var/run:size=10M,noexec,nosuid,nodev
user: "101:101"
restart: unless-stopped
secure-app:
image: node:alpine
container_name: secure-backend
networks:
- backend-net
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- SETGID
- SETUID
user: "1001:1001"
restart: unless-stopped
networks:
frontend-net:
external: true
backend-net:
external: true
Set up network monitoring and audit logging
Configure logging to monitor network traffic and detect security violations.
# Docker network security logging
:msg, contains, "DOCKER-USER" /var/log/docker-network-security.log
& stop
sudo systemctl restart rsyslog
Add logging to iptables rules
sudo iptables -I DOCKER-USER -s 172.20.1.0/24 -d 172.20.3.0/24 -j LOG --log-prefix "DOCKER-SECURITY-VIOLATION: "
sudo iptables -I DOCKER-USER -s 172.20.1.0/24 -d 172.20.3.0/24 -j DROP
Create network security monitoring script
Implement automated monitoring to detect and alert on security policy violations.
#!/bin/bash
Docker Network Security Monitor
SECURITY_LOG="/var/log/docker-network-security.log"
ALERT_EMAIL="admin@example.com"
LOGFILE="/var/log/docker-security-monitor.log"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOGFILE"
}
check_violations() {
local violations
violations=$(grep -c "DOCKER-SECURITY-VIOLATION" "$SECURITY_LOG" 2>/dev/null || echo 0)
if [ "$violations" -gt 0 ]; then
log_message "ALERT: $violations network security violations detected"
# Get recent violations
tail -n 20 "$SECURITY_LOG" | grep "DOCKER-SECURITY-VIOLATION" > /tmp/recent_violations.txt
# Send alert email if mail is configured
if command -v mail >/dev/null 2>&1; then
mail -s "Docker Network Security Alert" "$ALERT_EMAIL" < /tmp/recent_violations.txt
fi
return 1
fi
return 0
}
check_network_health() {
local networks
networks=("frontend-net" "backend-net" "database-net")
for network in "${networks[@]}"; do
if ! docker network inspect "$network" >/dev/null 2>&1; then
log_message "ERROR: Network $network is missing"
return 1
fi
done
log_message "All networks are healthy"
return 0
}
Main monitoring loop
log_message "Starting Docker network security monitoring"
while true; do
check_violations
check_network_health
# Check every 5 minutes
sleep 300
done
sudo chmod +x /opt/monitor-docker-security.sh
Create systemd service for monitoring
sudo tee /etc/systemd/system/docker-security-monitor.service > /dev/null << 'EOF'
[Unit]
Description=Docker Network Security Monitor
After=docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/opt/monitor-docker-security.sh
Restart=always
RestartSec=30
User=root
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now docker-security-monitor
Monitor and audit network security
Set up Prometheus monitoring integration
Configure metrics collection for network security monitoring using Docker's built-in metrics and custom exporters.
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: docker-prometheus
networks:
- monitoring-net
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.enable-lifecycle'
restart: unless-stopped
node-exporter:
image: prom/node-exporter:latest
container_name: docker-node-exporter
networks:
- monitoring-net
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
restart: unless-stopped
networks:
monitoring-net:
driver: bridge
ipam:
config:
- subnet: 172.20.4.0/24
volumes:
prometheus_data:
global:
scrape_interval: 30s
evaluation_interval: 30s
scrape_configs:
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
metrics_path: /metrics
scrape_interval: 30s
- job_name: 'docker-security'
static_configs:
- targets: ['host.docker.internal:9323']
metrics_path: /metrics
# Create monitoring network
docker network create --driver bridge --subnet=172.20.4.0/24 monitoring-net
Deploy monitoring stack
docker-compose -f docker-monitoring.yml up -d
For more advanced monitoring setups, check out our guide on monitoring Docker containers with Prometheus and Grafana.
Verify your setup
Test network isolation and security policies to ensure proper configuration.
# Check network isolation
docker exec frontend-web ping -c 3 backend-db
Should fail - no route to database network
Test allowed communication
docker exec frontend-web curl -f http://backend-app:8080/health
Should succeed - frontend can reach backend
Verify iptables rules
sudo iptables -L DOCKER-USER -n --line-numbers
Check security violations log
sudo tail -f /var/log/docker-network-security.log
Verify network configuration
docker network ls
docker network inspect frontend-net backend-net database-net
Check container security settings
docker inspect secure-frontend | grep -A 10 "SecurityOpt"
Monitor network traffic
sudo netstat -i
sudo ss -tulpn | grep docker
docker exec -it container_name /bin/sh to get shell access and test network connectivity between containers manually.Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Containers can't communicate | Not on same network | Add container to required network with docker network connect |
| iptables rules not applied | Docker daemon restart cleared rules | Add rules to startup script or use iptables-persistent |
| Network creation fails | Subnet conflict with existing networks | Choose different subnet range with docker network ls |
| Container health checks fail | ICMP blocked by iptables | Add rule: iptables -I DOCKER-USER -p icmp -j ACCEPT |
| Web app can't reach database | App container not on backend network | Connect to both frontend and backend networks |
| Security violations not logged | Rsyslog not configured | Restart rsyslog: sudo systemctl restart rsyslog |
--network=host in production. This bypasses all Docker networking security and exposes containers directly to the host network, eliminating isolation benefits.Advanced security configurations
For production environments, consider implementing additional security layers:
- Use Docker Secrets for sensitive data instead of environment variables
- Implement container runtime security with Falco for threat detection
- Configure image security scanning with vulnerability assessment tools
- Set up network policies in Kubernetes for orchestrated environments
- Implement zero-trust networking with service mesh technologies
For Kubernetes environments, explore implementing Kubernetes network policies for similar security benefits at the orchestration layer.
Next steps
- Implement container security with AppArmor and seccomp profiles
- Configure Docker secrets management with HashiCorp Vault
- Set up Docker container image vulnerability scanning
- Implement Docker Swarm encrypted networking for multi-host deployments
- Configure container runtime security monitoring and alerting
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Configuration variables
FRONTEND_SUBNET="172.20.1.0/24"
BACKEND_SUBNET="172.20.2.0/24"
DATABASE_SUBNET="172.20.3.0/24"
FRONTEND_GATEWAY="172.20.1.1"
BACKEND_GATEWAY="172.20.2.1"
DATABASE_GATEWAY="172.20.3.1"
# Usage function
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " --cleanup Remove Docker networks and iptables rules"
echo " --verify Verify network configuration"
echo " --help Show this help message"
echo ""
echo "Example: $0"
echo " $0 --cleanup"
exit 1
}
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function for trap
cleanup_on_error() {
log_error "Script failed. Cleaning up partial configuration..."
cleanup_networks 2>/dev/null || true
exit 1
}
# Detect distribution and package manager
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
IPTABLES_PERSISTENT_PKG="iptables-persistent"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
IPTABLES_PERSISTENT_PKG="iptables-services"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update || true"
IPTABLES_PERSISTENT_PKG="iptables-services"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution. /etc/os-release not found."
exit 1
fi
}
# Check if running as root or with sudo
check_privileges() {
if [ "$EUID" -ne 0 ]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
}
# Function to check if Docker network exists
network_exists() {
docker network ls --format "{{.Name}}" | grep -q "^$1$"
}
# Function to check if iptables rule exists
rule_exists() {
iptables -C DOCKER-USER "$@" 2>/dev/null
}
# Function to add iptables rule if it doesn't exist
add_rule_if_not_exists() {
if ! rule_exists "$@"; then
iptables -I DOCKER-USER "$@"
log_info "Added iptables rule: $*"
else
log_info "Rule already exists: $*"
fi
}
# Install Docker and required packages
install_docker() {
log_info "[1/7] Installing Docker and dependencies..."
$PKG_UPDATE
case "$PKG_MGR" in
apt)
$PKG_INSTALL docker.io docker-compose $IPTABLES_PERSISTENT_PKG
;;
dnf|yum)
$PKG_INSTALL docker docker-compose $IPTABLES_PERSISTENT_PKG
;;
esac
# Enable and start Docker service
systemctl enable docker
systemctl start docker
# Add user to docker group if SUDO_USER is set
if [ -n "${SUDO_USER:-}" ]; then
usermod -aG docker "$SUDO_USER"
log_warning "User $SUDO_USER added to docker group. Please log out and back in for changes to take effect."
fi
log_success "Docker installation completed"
}
# Create custom bridge networks
create_networks() {
log_info "[2/7] Creating custom bridge networks..."
# Create frontend network
if ! network_exists "frontend-net"; then
docker network create --driver bridge \
--subnet="$FRONTEND_SUBNET" \
--gateway="$FRONTEND_GATEWAY" \
--opt com.docker.network.bridge.name=web-bridge \
frontend-net
log_success "Created frontend-net network"
else
log_info "frontend-net network already exists"
fi
# Create backend network
if ! network_exists "backend-net"; then
docker network create --driver bridge \
--subnet="$BACKEND_SUBNET" \
--gateway="$BACKEND_GATEWAY" \
--opt com.docker.network.bridge.name=app-bridge \
backend-net
log_success "Created backend-net network"
else
log_info "backend-net network already exists"
fi
# Create database network with inter-container communication disabled
if ! network_exists "database-net"; then
docker network create --driver bridge \
--subnet="$DATABASE_SUBNET" \
--gateway="$DATABASE_GATEWAY" \
--opt com.docker.network.bridge.name=db-bridge \
--opt com.docker.network.bridge.enable_icc=false \
database-net
log_success "Created database-net network"
else
log_info "database-net network already exists"
fi
}
# Configure network security policies
configure_iptables() {
log_info "[3/7] Configuring network security policies..."
# Ensure DOCKER-USER chain exists
if ! iptables -L DOCKER-USER >/dev/null 2>&1; then
iptables -N DOCKER-USER
iptables -I FORWARD -j DOCKER-USER
fi
# Block inter-container communication on database network
add_rule_if_not_exists -i db-bridge -o db-bridge -j DROP
# Allow only backend network to access database network
add_rule_if_not_exists -s "$BACKEND_SUBNET" -d "$DATABASE_SUBNET" -j ACCEPT
# Allow only frontend to access backend on specific ports (8080)
add_rule_if_not_exists -s "$FRONTEND_SUBNET" -d "$BACKEND_SUBNET" -p tcp --dport 8080 -j ACCEPT
# Block direct frontend to database access
add_rule_if_not_exists -s "$FRONTEND_SUBNET" -d "$DATABASE_SUBNET" -j DROP
log_success "Network security policies configured"
}
# Create network policy enforcement script
create_policy_script() {
log_info "[4/7] Creating network policy enforcement script..."
cat > /usr/local/bin/docker-network-policy << 'EOF'
#!/usr/bin/env bash
# Docker Network Security Policy Script
set -euo pipefail
FRONTEND_SUBNET="172.20.1.0/24"
BACKEND_SUBNET="172.20.2.0/24"
DATABASE_SUBNET="172.20.3.0/24"
rule_exists() {
iptables -C DOCKER-USER "$@" 2>/dev/null
}
add_rule_if_not_exists() {
if ! rule_exists "$@"; then
iptables -I DOCKER-USER "$@"
echo "Added rule: $*"
fi
}
echo "Applying Docker network security policies..."
# Ensure DOCKER-USER chain exists
if ! iptables -L DOCKER-USER >/dev/null 2>&1; then
iptables -N DOCKER-USER
iptables -I FORWARD -j DOCKER-USER
fi
# Apply security rules
add_rule_if_not_exists -i db-bridge -o db-bridge -j DROP
add_rule_if_not_exists -s "$BACKEND_SUBNET" -d "$DATABASE_SUBNET" -j ACCEPT
add_rule_if_not_exists -s "$FRONTEND_SUBNET" -d "$BACKEND_SUBNET" -p tcp --dport 8080 -j ACCEPT
add_rule_if_not_exists -s "$FRONTEND_SUBNET" -d "$DATABASE_SUBNET" -j DROP
echo "Network security policies applied successfully"
EOF
chmod 755 /usr/local/bin/docker-network-policy
chown root:root /usr/local/bin/docker-network-policy
log_success "Network policy enforcement script created"
}
# Save iptables rules
save_iptables() {
log_info "[5/7] Saving iptables rules..."
case "$PKG_MGR" in
apt)
mkdir -p /etc/iptables
iptables-save > /etc/iptables/rules.v4
;;
dnf|yum)
systemctl enable iptables
service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables
;;
esac
log_success "iptables rules saved"
}
# Verify configuration
verify_configuration() {
log_info "[6/7] Verifying configuration..."
# Check Docker networks
for network in frontend-net backend-net database-net; do
if network_exists "$network"; then
log_success "Network $network exists"
else
log_error "Network $network not found"
return 1
fi
done
# Check iptables rules
if rule_exists -i db-bridge -o db-bridge -j DROP; then
log_success "Database isolation rule active"
else
log_warning "Database isolation rule not found"
fi
# Check if policy script exists and is executable
if [ -x /usr/local/bin/docker-network-policy ]; then
log_success "Network policy script installed"
else
log_error "Network policy script not found or not executable"
return 1
fi
log_success "Configuration verification completed"
}
# Cleanup function
cleanup_networks() {
log_info "Cleaning up Docker networks and iptables rules..."
# Remove custom networks
for network in frontend-net backend-net database-net; do
if network_exists "$network"; then
docker network rm "$network" 2>/dev/null || true
log_success "Removed network: $network"
fi
done
# Remove iptables rules
iptables -D DOCKER-USER -i db-bridge -o db-bridge -j DROP 2>/dev/null || true
iptables -D DOCKER-USER -s "$BACKEND_SUBNET" -d "$DATABASE_SUBNET" -j ACCEPT 2>/dev/null || true
iptables -D DOCKER-USER -s "$FRONTEND_SUBNET" -d "$BACKEND_SUBNET" -p tcp --dport 8080 -j ACCEPT 2>/dev/null || true
iptables -D DOCKER-USER -s "$FRONTEND_SUBNET" -d "$DATABASE_SUBNET" -j DROP 2>/dev/null || true
# Remove policy script
rm -f /usr/local/bin/docker-network-policy
log_success "Cleanup completed"
}
# Main function
main() {
local cleanup_mode=false
local verify_mode=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--cleanup)
cleanup_mode=true
shift
;;
--verify)
verify_mode=true
shift
;;
--help)
usage
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
# Set up error handling
trap cleanup_on_error ERR
check_privileges
detect_distro
if [ "$cleanup_mode" = true ]; then
cleanup_networks
exit 0
fi
if [ "$verify_mode" = true ]; then
verify_configuration
exit 0
fi
# Main installation process
log_info "Starting Docker network security implementation..."
install_docker
create_networks
configure_iptables
create_policy_script
save_iptables
verify_configuration
log_success "[7/7] Docker network security implementation completed successfully!"
log_info ""
log_info "Networks created:"
log_info " - frontend-net ($FRONTEND_SUBNET)"
log_info " - backend-net ($BACKEND_SUBNET)"
log_info " - database-net ($DATABASE_SUBNET)"
log_info ""
log_info "Policy script available at: /usr/local/bin/docker-network-policy"
log_info "Run '$0 --verify' to check configuration"
log_info "Run '$0 --cleanup' to remove all networks and rules"
}
main "$@"
Review the script before running. Execute with: bash install.sh