Networking & Multi-Container Architectures

35 minLesson 5 of 5

Learning Objectives

  • Configure port forwarding between host and containers
  • Set up private and public networks
  • Deploy multi-container environments
  • Allocate CPU and memory resources
  • Use loops to create scalable Vagrantfiles

Why Networking Matters

Real infrastructure involves multiple servers communicating over networks. Vagrant lets you simulate complex network topologies locally — perfect for testing before deploying to production.

Docker Network Types

TypeDescriptionUse Case
bridgeDefault isolated networkContainer-to-container communication
hostShares host network stackPerformance-critical apps
overlayMulti-host networkingDocker Swarm/clusters
noneNo networkingFully isolated containers

Port Forwarding

Port forwarding maps a port on your host machine to a port inside the container.

lab.vm.network "forwarded_port", guest: 80, host: 8080
lab.vm.network "forwarded_port", guest: 443, host: 8443

This means:

  • Traffic to localhost:8080 → forwarded to container port 80
  • Traffic to localhost:8443 → forwarded to container port 443

Handling Port Collisions

If port 8080 is already in use, Vagrant can auto-correct:

lab.vm.network "forwarded_port", guest: 80, host: 8080, auto_correct: true

Define a custom range for auto-correction:

lab.vm.usable_port_range = (9000..9100)

Practical Example: Web Server

ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
 
Vagrant.configure("2") do |config|
  config.vm.define "web_server" do |web|
    web.vm.network "forwarded_port", guest: 80, host: 9000, auto_correct: true
    web.vm.hostname = "webserver"
    web.vm.provider "docker" do |docker|
      docker.image = "nextgenplayground:vagrant"
      docker.has_ssh = true
      docker.privileged = true
      docker.create_args = ["-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro"]
      docker.name = "web_server"
    end
  end
end

After vagrant up, install nginx inside:

vagrant ssh
sudo apt-get install nginx -y
sudo systemctl enable --now nginx
exit

Now curl http://localhost:9000 returns the nginx welcome page from your host.

Private Networks

Private networks create isolated communication between containers — invisible to the outside world.

Static IP

web.vm.network "private_network", ip: "192.168.10.10", netmask: 24

DHCP (Automatic IP)

web.vm.network "private_network", type: "dhcp"

Public Networks

Public (bridged) networks put containers on the same network as your host:

web.vm.network "public_network", type: "dhcp"
# or with static IP:
web.vm.network "public_network", ip: "192.168.1.100"

Setting Hostnames

web.vm.hostname = "my-server"

This sets the container's hostname and updates /etc/hosts.

Resource Allocation

Control CPU and memory per container:

docker.create_args = ['--cpuset-cpus=1']     # 1 vCPU
docker.create_args = ['--memory=1g']          # 1GB RAM

Combine multiple args:

docker.create_args = [
  "-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro",
  "--cpuset-cpus=1",
  "--memory=1g"
]

Multi-Container Architecture

Here's a real-world example — three containers across two networks:

ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
 
Vagrant.configure("2") do |config|
 
  # Container 1: Frontend (Network A)
  config.vm.define "frontend" do |node|
    node.vm.network "private_network", ip: "192.168.1.10", netmask: 24
    node.vm.network "forwarded_port", guest: 80, host: 8080
    node.vm.hostname = "frontend"
    node.vm.provider "docker" do |docker|
      docker.image = "nextgenplayground:vagrant"
      docker.has_ssh = true
      docker.privileged = true
      docker.create_args = ["-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro", "--memory=1g", "--cpuset-cpus=1"]
      docker.name = "frontend"
    end
  end
 
  # Container 2: API Gateway (Network A + Network B)
  config.vm.define "api" do |node|
    node.vm.network "private_network", ip: "192.168.1.11", netmask: 24
    node.vm.network "private_network", ip: "192.168.2.11", netmask: 24
    node.vm.hostname = "api-gateway"
    node.vm.provider "docker" do |docker|
      docker.image = "nextgenplayground:vagrant"
      docker.has_ssh = true
      docker.privileged = true
      docker.create_args = ["-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro", "--memory=1g", "--cpuset-cpus=1"]
      docker.name = "api"
    end
  end
 
  # Container 3: Database (Network B only)
  config.vm.define "database" do |node|
    node.vm.network "private_network", ip: "192.168.2.10", netmask: 24
    node.vm.hostname = "database"
    node.vm.provider "docker" do |docker|
      docker.image = "nextgenplayground:vagrant"
      docker.has_ssh = true
      docker.privileged = true
      docker.create_args = ["-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro", "--memory=1g", "--cpuset-cpus=1"]
      docker.name = "database"
    end
  end
 
end

Network Topology

┌─────────────────────────────────────────────────┐
│                  Host Machine                    │
│                                                 │
│  ┌─── Network A (192.168.1.0/24) ───┐          │
│  │                                   │          │
│  │  ┌──────────┐   ┌──────────┐     │          │
│  │  │ frontend │   │   api    │     │          │
│  │  │ .1.10    │   │ .1.11   │     │          │
│  │  └──────────┘   └────┬─────┘     │          │
│  │                       │           │          │
│  └───────────────────────┼───────────┘          │
│                          │                      │
│  ┌─── Network B (192.168.2.0/24) ───┐          │
│  │                       │           │          │
│  │                  ┌────┴─────┐     │          │
│  │                  │   api    │     │          │
│  │                  │ .2.11    │     │          │
│  │                  └──────────┘     │          │
│  │                                   │          │
│  │  ┌──────────┐                     │          │
│  │  │ database │                     │          │
│  │  │ .2.10    │                     │          │
│  │  └──────────┘                     │          │
│  └───────────────────────────────────┘          │
└─────────────────────────────────────────────────┘

Testing Connectivity

# Connect to frontend
vagrant ssh frontend
 
# Can reach API on Network A
ping 192.168.1.11    # ✓ Success
 
# Cannot reach database (different network)
ping 192.168.2.10    # ✗ No response
 
exit
 
# Connect to API (has both networks)
vagrant ssh api
ping 192.168.1.10    # ✓ Reaches frontend
ping 192.168.2.10    # ✓ Reaches database

Using Loops for Scalable Configs

Instead of repeating configuration for similar containers, use Ruby loops:

ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
 
nodes = 4
 
(1..nodes).each do |i|
  Vagrant.configure("2") do |config|
    config.vm.define "node#{i}" do |node|
      node.vm.network "private_network", ip: "192.168.10.1#{i}", netmask: 24
      node.vm.hostname = "node#{i}"
      node.vm.provider "docker" do |docker|
        docker.image = "nextgenplayground:vagrant"
        docker.has_ssh = true
        docker.privileged = true
        docker.create_args = ["-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro"]
        docker.name = "node#{i}"
      end
    end
  end
end

This creates 4 containers (node1 through node4) with IPs 192.168.10.11 through 192.168.10.14 — in just 15 lines instead of 60+.

How the Loop Works

  • nodes = 4 — Number of containers to create
  • (1..nodes).each do |i| — Loop from 1 to 4
  • "node#{i}" — Ruby string interpolation (node1, node2, etc.)
  • "192.168.10.1#{i}" — Dynamic IP addresses

Combining All Network Options

config.vm.define "server" do |srv|
  srv.vm.hostname = "my-server"
  srv.vm.network "forwarded_port", guest: 80, host: 9000, auto_correct: true
  srv.vm.network "private_network", ip: "192.168.10.10", netmask: 24
  srv.vm.network "public_network", type: "dhcp"
  srv.vm.usable_port_range = (5000..5100)
  srv.vm.provider "docker" do |docker|
    docker.image = "nextgenplayground:vagrant"
    docker.has_ssh = true
    docker.privileged = true
    docker.create_args = ["-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro", "--memory=2g", "--cpuset-cpus=2"]
    docker.name = "server"
  end
end

Cleanup

Always clean up when done:

# Destroy specific environment
vagrant destroy -f
 
# Nuclear option: remove ALL Docker containers
docker rm -f $(docker ps -a -q)

Summary

  • Port forwarding maps host ports to container ports
  • Private networks isolate communication between containers
  • Public networks bridge containers to your LAN
  • Resource allocation controls CPU and memory per container
  • Multi-container architectures simulate real infrastructure
  • Loops make Vagrantfiles scalable and maintainable
  • Network isolation ensures containers only communicate as designed

Practice Exercise

Deploy an architecture with:

  • A web container on network 192.168.1.0/24 with IP .10, port 80 forwarded to host port 9000
  • An app container on both 192.168.1.0/24 (IP .11) and 192.168.2.0/24 (IP .11)
  • A db container on network 192.168.2.0/24 with IP .10

Verify that web can reach app but NOT db, and that app can reach both.

Next Steps

You now have the skills to design and deploy complex local environments with Vagrant. As you continue through the DevOps track, you'll use these skills to test Docker, Kubernetes, and CI/CD configurations locally before deploying to production.