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}}"
--format picks the columns you want, which keeps the output readable when many containers run. Look at the PORTS column:
| Container | Host port | Container port | Runtime inside |
|---|---|---|---|
| my-nginx | 8080 | 80 | Nginx on Debian |
| dotnet-day1 | 8081 | 8080 | .NET 10 on Ubuntu |
| python-fastapi | 8002 | 8000 | Python 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:
| URL | Response |
|---|---|
http://localhost:8081/ | Hello from ASP.NET Core running inside a Docker container! |
http://localhost:8081/info | JSON 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/docs | FastAPI'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/
Step 4: Watch resource usage with docker stats
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
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
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())"
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:
docker network inspect day1-net --format "{{range .Containers}}{{.Name}} {{.IPv4Address}}{{println}}{{end}}"
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 statsand 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.