Docker – Part 4 – Networking

By Huntly Cameron Uncategorized

If you’ve been following along with this series you’ll have both the PHP API image and the React image.

When we’re running these images as containers, there’s double the effort of having to start each one individually. The React app depends on the PHP API.

If you’ve got the two PHP API and React containers running, you can stop and remove them.

docker stop php-api
docker stop react-slim
docker stop react # if you still have this running!

Luckily, we can use docker compose to help us with this. Anywhere on your computer, but somewhere that makes sense, create a php-react-bundle.yml file.

In that file add the following:

services:
  react:
    image: react-slim
    depends_on:
      - api
    ports:
      - 80:80

  api:
    image: php-simple-api
    ports:
      - 8080:8080

If you’ve not come across YAML before, indentation matters and typically 2 spaces are used for indentation. Any modern editor should pick this up for you.

Hopefully that file doesn’t seem two crazy. We’re telling docker compose that we want two services: our React app, and our PHP API. We’re putting it down as a condition that the react app depends on the API.

This means that when the containers are created, the PHP one, although second in the file, will be created before the React container.

To spin up our application, now we run:

docker compose -f php-react-bundle.yml up -d

This tells docker compose to take our file, essentially an application blueprint, and spin up the containers needed in detached mode so as not to take over the terminal showing us the logs from each of the containers.

Side note: if we had named it docker-compose.yml or compose.yaml or any variation thereof, it would have automatically picked it up.

Try running it with and without the -d flag at the end. You can also rename the file to compose.yml and run it without the -f flag too!

Networks

When you ran the compose for the first time, you probably got some output that looks like the following:

[+] Running 3/3
 ✔ Network me_default    Created
 ✔ Container me-api-1    Created
 ✔ Container me-react-1  Created  

The Network ‘me_default’ is not something we defined anywhere, so whats going on? Docker compose has created a custom network for us in which to run our containers in.

Docker networking is a fairly complex subject. But for our purposes we only need to know a few of the basics.

First what is a docker network? A network in docker is a isolated networking layer that we can attach containers to. This allows us to keep containers isolated and only access other containers that are in the same network.

The handy thing about docker networks is they have their own internal DNS that maps to the container names. If you have a container called “my-api”, and another called “my-cli-app”. Then from your “my-cli-app” you could make network requests to “my-api”. E.g curl http://my-api/some/path

When using docker compose, this DNS resolution is on the service, rather than the container. If we had some sort of CLI tool on our react container, we could contact the API via curl http://api/greeting/

In the context of this series, what this boils down to is that we could spin up multiple WordPress websites on the same host with each group of containers contained so that sites A, and B have their own dedicated networks. This means that each site can’t directly talk to each others containers as we wouldn’t want site A to access site B’s database for instance.

There are a few types of docker networks:

  • None: absolutely no network. Completely isolated.
  • Bridge – the default type of network. A private isolated network that we can attach one or more containers to.
  • Host – directly use the host machines networking. This makes containers to appear as running applications on the host by removing the isolation layer.

As well as these, there area also more advanced ones that are out of scope of this series: ipvlan, macvlan, and overlay.

All docker containers on bridge networks will have access to the host’s network to reach out to the internet, unless specifically configured otherwise.

If this interests you and you want to dive into the details, check out this excellent overview by Network Chuck: Docker networking is CRAZY!! (you NEED to learn it)

In practice

Lets alter our compose file so that we define the network explicitly ant attach each of our containers to that network:

services:
  react:
    image: react-slim
    container_name: react-slim
    depends_on:
      - api
    ports:
      - 80:80
    networks:
      - skynet

  api:
    image: php-simple-api
    container_name: phpapi
    ports:
      - 8080:8080
    networks:
      - skynet

networks:
  skynet:

For each service we’ve defined what networks it can attach itself to. Then at the end of the file we’ve defined our skynet network. Because we’ve not supplied any additional details, docker will create this as a default bridge type network.

We’ve also added the container_name option to each container to explicitly name them.

When you run docker compose up -d again to spin everything up, you’ll see the something like the following:

[+] Running 3/3
 ✔ Network someprefix_skynet  Created
 ✔ Container phpapi        Started
 ✔ Container react-slim    Started   

Lets now try and ping the php api from another container. To do this run the following:

docker run --rm --name php-ping alpine ping phpapi

You will get the following output:

ping: bad address 'phpapi'

This is because this alpine container is not on the same network as the phpapi container. Instead we need to specify the network the alpine container runs on. Look back to your docker compose for the Network someprefix_skynet and use whatever yours was called:

docker run --rm --name php-ping --network huntlyc_skynet alpine ping phpapi

All going well, you should see the following output:

PING phpapi (172.23.0.2): 56 data bytes
64 bytes from 172.23.0.2: seq=0 ttl=64 time=0.144 ms
64 bytes from 172.23.0.2: seq=1 ttl=64 time=0.169 ms
64 bytes from 172.23.0.2: seq=2 ttl=64 time=0.191 ms
^C
--- phpapi ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.144/0.168/0.191 ms