docker
Photo by docker.com

Configuring Docker Networks with Compose

Gain an understanding of Docker networking fundamentals, specifically with respect to Docker Compose, for the purpose of isolation or facilitating network communication between containers and the Docker host

Tuesday, Oct 17, 2023

avatardocker

Introduction

For over five years, I've employed Docker to deploy multi-tier applications for local development. My typical approach involves crafting just the necessary Docker Compose configuration to achieve the desired outcome, and I generally stop there.

Lately, I've encountered some challenges when revisiting a previous project, compelling me to delve back into the essentials of Docker networking and Docker Compose. Fortunately, this troubleshooting exercise not only acted as a valuable refresher but also provided me with new insights that I'd like to document for the benefit of others, including my future self (hello future me)

Reference Docker Documentation

Exposing a container to the Docker host

Docker containers are, by default, isolated and inaccessible to both the Docker host and the external environment. To make a container reachable from outside Docker, you must define port mappings during container creation or runtime.

When using the Docker command-line interface (CLI), this can be accomplished by specifying the -p or --publish flag and providing a value in the format "${HOST_PORT}:${CONTAINER_PORT}". This action establishes a connection between a port on the host and a port within the container, allowing external access to the containerized service.

Exactly, HOST_PORT represents the published port that is accessible by the Docker host, while CONTAINER_PORT is the port assigned to the container for internal service-to-service communication within the Docker network.

When you define these port mappings with the -p or --publish flag, Docker automatically handles the creation of the required firewall rules and network interfaces. This process enables both the Docker host and the external world to establish connections with the container, even though the container itself has no knowledge of its network configuration.

One practical scenario where this knowledge proves valuable is when you need to access your web application from a web browser or a containerized database with a DBMS (Database Management System) running externally to Docker, such as DataGrip.

Publishing ports with Docker Compose

docker-compose.yml

version: '3.8'
services:
  db:
    image: postgres:12.2-alpine
    ports:
      - 54321:5432
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres

Based on the provided Docker Compose configuration, after running docker compose up, you can access the Dockerized PostgreSQL database at the following connection details:

  • Host: localhost
  • Port: 54321

Additionally, the configured database credentials, which have been set through environment variables and assigned to the container, are as follows:

  • Username: postgres
  • Password: postgres
  • Database: mydb

These details will allow you to connect to the PostgreSQL database running in the container using the specified credentials and connection parameters.

Networking between containers

Docker Compose automatically generates a default bridge network with a name derived from the directory containing the current Compose file. For instance, a Compose file located at /app/docker-compose.yml would result in a network named app_default.

To view the name of the network created by Docker Compose after running docker compose up, you can open a new terminal session and use the docker network ls command.

If you want to customize the default network behavior, you can add a networks section at the top level of your Compose file, like this:

networks:
  default:
    name: mynetworkname

In this example, the network's name is explicitly set to "mynetworkname." This allows you to have greater control over the network naming and configuration within your Compose setup.

Service discoverability

In a default Docker Compose setup, all containers within a Compose service can communicate with and discover one another if they are not attached to a user-defined custom network. They are automatically added to the app_default network, and each container is given a DNS address name that matches its container name. For example, if a service is named "app," other containers can look up the hostname as "app" and obtain the container's IP address.

In your docker-compose.yml example:

version: '3.8'
services:
  app:
    image: ./Dockerfile
    environment:
      DB_USERNAME: postgres
      DB_PASSWORD: postgres
      DB_HOST: db
      DB_PORT: 5432
      DB_NAME: mydb
    ports:
      - 3000:3000
  db:
    image: postgres:12.2-alpine
    ports:
      - 54321:5432
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres

In this example, the "app" container can establish a database connection to the "db" container using a connection string like postgres://db:5432. On the other hand, when connecting from the host machine, the connection string would typically be postgres://${DOCKER_IP}:54321 or postgres://localhost:54321.

This setup allows seamless communication between containers within the same network, with host machine connections referencing the Docker host's IP or "localhost" along with the published port.

Docker user-defined network (recommended)

Advanced network topologies can be accomplished by employing custom "user-defined" networks. These customized networks offer the advantages of finely-grained container isolation and the ability to tailor network drivers, types, settings, and options to specific requirements.

A prime illustration of the utility of a "user-defined" network is when you need to isolate access to a PostgreSQL database container. Suppose you have a backend server application named app that requires network connectivity to a PostgreSQL container service called db for establishing a database management system connection. In this case, specifying the networks key at the service level and associating it with a shared user-defined network will establish the essential network interfaces, enabling seamless communication between the app and db service containers.

docker-compose.yml

version: '3.8'
services:
  app:
    image: ./Dockerfile
    environment:
      DB_USERNAME: postgres
      DB_PASSWORD: postgres
      DB_HOST: db
      DB_PORT: 5432
      DB_NAME: mydb
    ports:
      - 3000:3000
    networks:
      - backend
 
  db:
    image: postgres:12.2-alpine
#    ports:
#     - 54321:5432
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    networks:
      - backend
 
networks:
  backend:
    driver: bridge

It's worth highlighting that in the provided configuration, the port mappings have been intentionally removed. This means that the app container service can still effectively communicate with the PostgreSQL database running within the db container, utilizing the default PostgreSQL port, which is :5432.

Additional Resources & Links