Docker Networks

Offline Mode

  • By default, Docker containers are not cut off from the network: each container joins the default bridge network, can reach other containers on it by IP (name-based DNS only works on user-defined networks), and has outbound access to the host and the internet. Ports are only reachable from outside if you publish them with -p.
  • To fully isolate a container, start it with --network none: it then gets only a loopback interface and cannot communicate with other containers, the host or the internet.
  • You might be thinking, “why would I want to turn off networking”??? Well, usually for security reasons. You might want to remove the network connection from a container in one of these scenarios:
    • You’re running 3rd party code that you don’t trust, and it shouldn’t need network access
    • You’re building an e-learning site, and you’re allowing students to execute code on your machines
    • You know a container has a virus that’s sending malicious requests over the internet, and you want to do an audit

Ping

  • To test the network connection between two containers, you can use the ping command.

    docker exec -it <container1_id> ping <container2_ip>
    • <container1_id>: The ID of the first container.
    • <container2_ip>: The IP address of the second container.

Break the Network

  1. Method 1 - Create in Offline Mode

    • To create a container in offline mode, you can use the following command:

      docker run -d --network none <image_name>
      • none: No network.
      • <image_name>: The name of the image.
  2. Method 2 - Disconnect

    • To break the network connection of a container, you can use the following command:

      docker network disconnect bridge <container_id>
      • bridge: The name of the network.
      • <container_id>: The ID of the container.
    • To reconnect the container to the network, you can use the following command:

      docker network connect bridge <container_id>
      • bridge: The name of the network.
      • <container_id>: The ID of the container.

Load Balancer

What is a Load Balancer?

  • A load balancer behaves as advertised: it balances a load of network traffic. Think of a huge website like Google.com. There’s no way that a single server (literally a single computer) could handle all of the Google searches for the entire world. Google uses load balancers to route requests to different servers.

How Does a Load Balancer Work?

A central server, called the “load balancer”, receives traffic from users (aka clients), then routes those raw requests to different back-end application servers. In the case of Google, this splits the world’s traffic across potentially many different thousands of computers.

LoadBalancer

How does it “balance” the traffic?

  • A good load balancer sends more traffic to servers that have unused resources (CPU and memory). The goal is to “balance the load” evenly. We don’t want any individual server to fail due to too much traffic. There are many strategies that load balancers use, but a simple strategy is the “round robin”. Requests are simply routed one after the other to different back-end servers.

Example of “Round Robin” Load Balancer

  • Request 1 → Server 1

  • Request 2 → Server 2

  • Request 3 → Server 3

  • Request 4 → Server 1

  • …

  • Issue with “Round Robin”: What if Server 1 is really slow? We don’t want to send more requests to Server 1 if it’s already overloaded. A better strategy is to send requests to the server with the fewest connections. This is called the “Least Connections” strategy.

Example of “Least Connections” Load Balancer

  • Here we have 2 servers. The load balancer keeps track of how many connections each server has. It sends the next request to the server with the fewest connections.

  • Request 1 → Server 1

  • Request 2 → Server 1

  • Request 3 → Server 2

  • Request 4 → Server 1

  • …

Scaling

  • Load balancers are a key part of scaling an application. If you have a website that’s getting more and more traffic, you can add more servers to handle the load. The load balancer will automatically route traffic to the new servers.
  • Load balancers can also help with redundancy. If one server fails, the load balancer can automatically route traffic to the other servers.
  1. Vertical Scaling - Adding more resources to a single server.

    • This is limited by the capacity of the server. You can only add so much RAM, CPU, etc.
    • Can be expensive.
  2. Horizontal Scaling - Adding more servers.

    • This is the preferred way to scale web applications.
    • Can be done with load balancers.
    • Complex to set up, but more scalable.

Application Servers

  • Application Servers are servers that run your application code. They are the servers that the load balancer routes traffic to.
  • First, we need to start some application servers so that we have something to load balance! We’ll be using Caddy, an awesome open-source load balancer/web server. Nginx and Apache are other popular alternatives that do similar things, but Caddy is a modern version written in Go, so I think it will be cool to play with.

What will our Web Server do?

  • Each application server will serve a slightly different HTML webpage. The reason they’re different is just so that we can see load balancing in action!

1. Pull the Caddy Docker Image

Caddy is a lightweight and powerful web server. Begin by pulling the Caddy image from Docker Hub:

docker pull caddy

This command downloads the latest Caddy image to your machine, making it available for running containers.


2. Create HTML Files

In your working directory, create two HTML files that will be served by different Caddy containers.

  • index1.html

    <html>
    <body>
        <h1>Hello from server 1</h1>
    </body>
    </html>
  • index2.html

    <html>
    <body>
        <h1>Hello from server 2</h1>
    </body>
    </html>

These simple HTML files will be served on different ports by separate Caddy containers.


3. Run Caddy Containers to Serve the HTML Files

  • Run the first Caddy container to serve index1.html on port 8001:

    docker run -p 8001:80 -v $PWD/index1.html:/usr/share/caddy/index.html caddy

    This command:

    • Maps port 8001 on your host to port 80 on the container (the default HTTP port).
    • Mounts the index1.html file from your current working directory ($PWD) into the container’s web root (/usr/share/caddy/index.html), so it can be served.
  • Run the second Caddy container to serve index2.html on port 8002:

    docker run -p 8002:80 -v $PWD/index2.html:/usr/share/caddy/index.html caddy

    Similar to the first container, this command maps port 8002 on your host to port 80 in the container and serves index2.html.


4. Running Containers in Detached Mode (Optional)

You can run the containers in the background using the -d flag. This allows you to close the terminal without stopping the containers.

docker run -d -p 8001:80 -v $PWD/index1.html:/usr/share/caddy/index.html caddy
docker run -d -p 8002:80 -v $PWD/index2.html:/usr/share/caddy/index.html caddy

5. Verify the Setup

Open your web browser and navigate to the following URLs:

  • For the first container:

    • URL: http://localhost:8001
    • You should see: Hello from server 1
  • For the second container:

    • URL: http://localhost:8002
    • You should see: Hello from server 2

This setup demonstrates how to use Docker to run multiple Caddy containers serving different HTML files on different ports. Each container runs independently, and you can access each one via its respective port.

Custom Networks

  • Docker allows us to create custom bridge networks so that our containers can communicate with each other if we want them to, but remain otherwise isolated. We’re going to build a system where the application servers are hidden within a custom network, and only our load balancer is exposed to the host.

  • Create a Custom Network

    docker network create my-network

    This command creates a custom bridge network named my-network.

  • List Networks

    docker network ls

    This command lists all the networks on your system, including the newly created my-network.

Example Notes: Attaching Caddy Servers to a Custom Docker Network and Testing with a Client Container

1. Create and Run Caddy Containers on a Custom Network

To manage inter-container communication, create Caddy containers on a custom Docker network called caddytest and give each container a unique name for easier identification.

  • Create the network first (otherwise --network caddytest fails):

    docker network create caddytest
  • Run the first Caddy container (caddy1) on port 8001:

    docker run --network caddytest --name caddy1 -p 8001:80 -v $PWD/index1.html:/usr/share/caddy/index.html caddy

    This command:

    • Network: Connects the container to the caddytest network, enabling communication between containers.
    • Name: Assigns the container the name caddy1.
    • Port mapping: Exposes port 8001 on your host machine.
    • Volume: Mounts index1.html to /usr/share/caddy/index.html inside the container.
  • Run the second Caddy container (caddy2) on port 8002:

    docker run --network caddytest --name caddy2 -p 8002:80 -v $PWD/index2.html:/usr/share/caddy/index.html caddy

    This is similar to caddy1 but serves index2.html and is mapped to port 8002.


2. Verify Name Resolution and Networking with Another Container

To test that the containers can communicate over the caddytest network, run an additional container and access the Caddy servers using their assigned names.

  • Start a shell session in a getting-started container attached to the caddytest network:

    docker run -it --network caddytest docker/getting-started /bin/sh

    This opens an interactive shell session inside a new container running on the same network (caddytest), allowing you to test communication between containers.

  • Test communication with caddy1 using curl:

    curl caddy1

    This command sends an HTTP request to caddy1, which should return the HTML content from index1.html.

  • Test communication with caddy2 using curl:

    curl caddy2

    Similarly, this should return the HTML content from index2.html.


3. Exiting the Shell Session and Restarting Named Containers

Once you’ve successfully received the expected HTML responses, you can exit the shell session:

exit

To restart the Caddy containers, you don’t need to run the entire docker run command again. Simply use the following commands to restart the named containers:

  • Start caddy1:

    docker start caddy1
  • Start caddy2:

    docker start caddy2

This will start the containers with their previous configurations, including the custom network and name assignments.


Summary

  • Created and ran two Caddy containers (caddy1 and caddy2) attached to the caddytest network.
  • Verified name resolution between containers by running a client container and using curl to communicate with the Caddy servers by name.
  • Restarted the named containers with docker start caddy1 and docker start caddy2 commands for convenience.

Configurign Load Balancer

  • We’ve confirmed that we have 2 application servers (Caddy) working properly on a custom bridge network. Let’s create a load balancer that balances network requests between the two! We’ll use a round-robin balancing strategy, so each request should route back and forth between the servers.

Caddyfile for the load balancer

  • Caddy works great as a file server, which is what our little HTML servers are, but it also works great as a load balancer! To use Caddy as a load balancer we need to create a custom Caddyfile to tell Caddy how we want it to balance traffic.

  • Create a new file in your local directory called Caddyfile:

    localhost:80
     
    reverse_proxy caddy1:80 caddy2:80 {
    lb_policy       round_robin
    }

    This tells Caddy to run on localhost:80, and to round robin any incoming traffic to caddy1:80 and caddy2:80. Remember, this only works because we’re going to run the loadbalancer within the same network, so caddy1 and caddy2 will automatically resolve to our application server’s containers.

Running the Load Balancer

  • Run the Caddy load balancer:

    docker run --network caddytest -v $PWD/Caddyfile:/etc/caddy/Caddyfile -p 8080:80 caddy

    This command:

    • Network: Connects the load balancer to the caddytest network.
    • Volume: Mounts the local Caddyfile to the container’s /etc/caddy/Caddyfile.
    • Port mapping: Maps host port 8080 to the container’s port 80 (-p host:container).
  • If you open your browser and navigate to http://localhost:8080, you should see the load balancer in action, alternating between the two HTML pages served by caddy1 and caddy2.