Skip to content

Run Multiple Docker Containers Together

In the last two labs you built two images in two different languages: dotnet-day1:1.0 (ASP.NET Core, port 8081) and python-fastapi:1.0 (FastAPI, port 8002). Now you'll run both at the same time on one machine, together with the Nginx container on 8080, and test them all from the browser.

This is the everyday reality of microservices: several services, different languages and runtimes, side by side, without any of their dependencies installed on your computer. All outputs are from a real run on Docker Engine 29.

Step 1: Start both applications

docker run -d -p 8081:8080 --name dotnet-day1 dotnet-day1:1.0
docker run -d -p 8002:8000 --name python-fastapi python-fastapi:1.0

If containers with these names already exist from the earlier labs, start them instead with docker start dotnet-day1 python-fastapi (you can pass several names to one command).

Step 2: Check what is running

docker ps --format "table {{.Image}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
Output
IMAGE                STATUS          PORTS                                         NAMES
python-fastapi:1.0   Up 7 seconds    0.0.0.0:8002->8000/tcp, [::]:8002->8000/tcp   python-fastapi
dotnet-day1:1.0      Up 59 seconds   0.0.0.0:8081->8080/tcp, [::]:8081->8080/tcp   dotnet-day1
nginx                Up 5 minutes    0.0.0.0:8080->80/tcp, [::]:8080->80/tcp       my-nginx

--format picks the columns you want, which keeps the output readable when many containers run. Look at the PORTS column:

ContainerHost portContainer portRuntime inside
my-nginx808080Nginx on Debian
dotnet-day180818080.NET 10 on Ubuntu
python-fastapi80028000Python 3.13 on Debian

Host ports must be unique; container ports don't have to be. Nginx listens on 8080 of the host, and the .NET app listens on 8080 inside its own container, and they don't conflict, because every container has its own network namespace.

Step 3: Test both endpoints in the browser

Open each URL in a browser tab:

URLResponse
http://localhost:8081/Hello from ASP.NET Core running inside a Docker container!
http://localhost:8081/infoJSON with "framework":".NET 10.0.14" and "os":"Ubuntu 24.04.5 LTS"
http://localhost:8002/{"message":"Hello from Python FastAPI running inside a Docker container!"}
http://localhost:8002/docsFastAPI's Swagger UI
http://localhost:8080/Welcome to nginx!

A quick check of all three from the command line (PowerShell users can use curl.exe):

curl -s -o /dev/null -w "localhost:8080 -> HTTP %{http_code}\n" http://localhost:8080/
curl -s -o /dev/null -w "localhost:8081 -> HTTP %{http_code}\n" http://localhost:8081/
curl -s -o /dev/null -w "localhost:8002 -> HTTP %{http_code}\n" http://localhost:8002/
Output
localhost:8080 -> HTTP 200
localhost:8081 -> HTTP 200
localhost:8002 -> HTTP 200

Step 4: Watch resource usage with docker stats

docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
Output
NAME             CPU %     MEM USAGE / LIMIT     NET I/O
python-fastapi   0.14%     32.56MiB / 3.724GiB   2.83kB / 4.16kB
dotnet-day1      0.01%     21.89MiB / 3.724GiB   1.8kB / 693B
my-nginx         0.00%     10.48MiB / 3.724GiB   8.68kB / 2.26kB

Three complete services, including two language runtimes, use about 65 MB of RAM together. Three virtual machines would need several gigabytes. That's the "lightweight" advantage from the introduction, measured. Without --no-stream, docker stats refreshes live like Task Manager (Ctrl+C to quit). The LIMIT column is the memory available to Docker; you can cap a container with --memory 256m.

Step 5: Logs of each service

Each container keeps its own log, so you can debug one service without the noise of the others:

docker logs --tail 3 python-fastapi
Output
INFO:     172.17.0.1:44740 - "GET /docs HTTP/1.1" 200 OK
INFO:     172.17.0.1:44752 - "GET /docs HTTP/1.1" 200 OK
INFO:     172.17.0.1:44754 - "GET / HTTP/1.1" 200 OK

Step 6: Let the containers talk to each other

The browser reaches the services through published ports. But in a real system services also call each other, for example a FastAPI service calling the .NET API. Can the Python container reach the .NET container by its name?

docker exec python-fastapi python -c "import urllib.request; print(urllib.request.urlopen('http://dotnet-day1:8080/').read().decode())"
Output (last line)
urllib.error.URLError: <urlopen error [Errno -2] Name or service not known>

No. Both containers are on Docker's default bridge network, which doesn't provide name resolution between containers. Create a user-defined network and connect both containers to it:

docker network create day1-net
docker network connect day1-net dotnet-day1
docker network connect day1-net python-fastapi

Try again:

Output
Hello from ASP.NET Core running inside a Docker container!
docker network inspect day1-net --format "{{range .Containers}}{{.Name}} {{.IPv4Address}}{{println}}{{end}}"
Output
python-fastapi 172.19.0.3/16
dotnet-day1 172.19.0.2/16

On a user-defined network, Docker's built-in DNS resolves container names to their IPs. Two details matter:

  • Between containers you use the container port (dotnet-day1:8080), not the host port 8081. Published ports are only for traffic coming from outside.
  • Use names, never IPs: an IP can change when a container is recreated; the name stays.

In practice you create the network first and start containers on it with docker run --network day1-net .... Docker Compose does all of this automatically from one YAML file, which is the natural next step after this lab.

Step 7: Stop, start and clean up together

docker stop dotnet-day1 python-fastapi

Both URLs stop answering. Start them again in one command, and both return HTTP 200 within seconds:

docker start dotnet-day1 python-fastapi

When you've finished practising, remove the containers and the network (the images stay, so you can run them again any time):

docker rm -f dotnet-day1 python-fastapi my-nginx
docker network rm day1-net

Interview questions

Can multiple containers be created from one image, and can different images run together?

Yes to both. Each container is an isolated instance with its own writable layer, network namespace and processes, so any number of containers from any images can run on one host, as long as their published host ports are different.

How do two containers communicate?

Put them on the same user-defined network (docker network create) and call each other by container name and container port, for example http://dotnet-day1:8080. The default bridge network has no name resolution.

Why can two containers both use port 8080 inside?

Each container has its own network namespace, so container ports never conflict. Only host ports in -p must be unique.

Summary

  • Run each service in its own container with a unique host port: 8080, 8081 and 8002 here.
  • Test with the browser or curl; check resources with docker stats and logs per container.
  • Three services used about 65 MB of RAM together.
  • Containers talk to each other by name on a user-defined network, using container ports.

Next: review the Docker architecture with everything you've now seen in practice, and prepare with the Docker interview questions and answers.