Docker Networking Example

Docker Networking

Docker has different network drivers, which allow for definit how services running inside docker containers are exposed. The default driver ist the bridge network, which should be used when different containers need to communicate on the same docker host. To create a new bridge network the appropriate docker command can be used:

docker network create example

After the creation of the network, with the accompanying docker network ls command, the available networks can be listed, e.g.:

NETWORK ID     NAME      DRIVER    SCOPE
f6df22da8c8f bridge bridge local
427dc406e55e example bridge local
54e18a08b42a host host local
6300f30e1061 none null local

An existing containers can be connected to the example network:

docker network connect example CONTAINERNAME

When starting a new container with the run command the --network example argument could be used to connect the container directly at startup on the given network.

Example

For demonstrating how to use docker networking, a simple example is created. The example provides an HTTP API and a client, which each run in a separate docker container. The full example is available in a git repository for a detailed review.

API

A simple API is provided, which exposes a POST /hello HTTP endpoint. The endpoint would accept a JSON request body with a name property on it, e.g.:

{"name": "somebody"}

The response would then contain a JSON body, which would look something like this, considering the name set to somebody:

{"message": "Hello somebody"}

The full FastAPI code snippet would look like this:

from fastapi import FastAPI
from pydantic import BaseModel

class RequestBody(BaseModel):
name: str

app = FastAPI()

@app.post("/hello")
async def hello(requestBody: RequestBody):
name = requestBody.name
return {"message": "Hello {}".format(name)}

After building the docker image with name example-api, the container can be run with the follwing command:

docker run --name example-api-1 --network example -d example-api

The command lets the container run inside the example network with the name example-api-1. Other containers, which would run inside the example network, could access network exposed services of the example-api-1 directly by it's name (not just by the IP address). Docker provides a name resolution within the bridge network.

Note that no -p flag is set for the docker run command. This means the service provided by the container is only available inside the example network.

Client

Accompanying to the API, a client is provided, which would trigger a HTTP POST request with the required body. The URI endpoint is represented by an environment variable to the python code. This environment variable can be set for the docker container. The following code snippet would describe the client in full detail:

import os
import logging
import json
import requests
from dotenv import load_dotenv

load_dotenv()
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)

try:
request = {"name": "world"}
response = requests.post("{}/hello".format(os.getenv("API_URL")), data=json.dumps(request))
results = response.json()
logger.info(results)
except Exception as e:
logger.error(e)

After building the docker image with name example-client, the container can be run with the follwing command:

docker run --network example -e API_URL="http://example-api-1" -it --rm example-client

Conclusion

Docker networking provides powerful features, which allow for building microservices on decoupled networks on the docker host. The name of a docker container acts also as the hostname within the bridge network, which means no IP addresses need to be used within the application code. Important to note is also that, the -p flag for exposing a docker container to a specific port, has no effect inside the docker bridge network.