đ Series: AWS Cost Optimization
- Part 1: Local Manual Setup â
- Part 2: Docker Setup (You are here)
- Part 3: Automated CI Pipeline (GitHub Actions & ECR) â
- Part 4: Kubernetes Deployment (EKS) â
I HIGHLY RECOMMEND WATCHING TUTORIALS ON WHAT DOCKER IS AND UNDERSTANDING ITS CAPABILITY before proceeding, these easily can be found on youtube. I will provide some useful links below:
The âWhyâ: Solving Our Manual Mess
In Part 1, we got our Python chat app running locally. While it worked, the process was a mess:
- Dependency Hell: We hit a wall with C-compilation errors (
greenlet) just because I was on a newer version of Python. - Polluted Host: We had to install Redis directly on our host machine, where it runs all the time.
- Fragile & Not Portable: The only reason it worked was because our appâs default database host (
localhost) happened to match our manual Redis install. This would break on any other machine.
Now, we will solve every one of these problems with Docker. Weâll package our app and its database into isolated, portable âcontainersâ that will run identically on any machine.
Step 1: The Blueprint (The Dockerfile)
First, we need to create a âblueprintâ for our Python application. This is a file called Dockerfile. It contains the step-by-step instructions Docker will follow to build a self-contained image of our app. (Make sure youâre in the directory of the project)
The repository already includes a Dockerfile, which is perfect, but I think itâs a bit useless if we donât understand it. So let me break down what it does:
# Use Python37
FROM python:3.7
# Copy requirements.txt to the docker image and install packages
COPY requirements.txt /
RUN pip install -r requirements.txt
# Set the WORKDIR to be the folder
WORKDIR /app
COPY . .
# Make entrypoint script executable
RUN chmod +x docker-entrypoint.sh
# Expose port 8080
EXPOSE 8080
ENV PORT 8080
# Use entrypoint script
ENTRYPOINT ["./docker-entrypoint.sh"]
-
FROM python:3.7This is our âfixâ for thegreenleterror. It doesnât matter that my Mac is running Python 3.13. The container will be built using a clean, official Python 3.7 image, guaranteeing a stable and identical environment every time. -
COPY requirements.txt /&RUN pip install -r requirements.txtThis is the most important optimisation. Notice we are not copying the whole app first. We only copyrequirements.txtand then ranpip install. This creates a cached layer. When we change our Python code (app.py) and rebuild, Docker sees thatrequirements.txthasnât changed, so it skips this entire slowpip installstep and uses a cached version. This makes our builds incredibly fast. -
WORKDIR /app© . .Sets up our working directory and copies our application code into it. -
RUN chmod +x docker-entrypoint.shThis allows the script to be executed as an executable file. -
EXPOSE 8080&ENV PORT 8080:ENV PORT 8080: Sets the port environment variableEXPOSE 8080: Documents that the container listens on port 8080
-
ENTRYPOINTInstead of the typicalCMD, we useENTRYPOINTwith our script to:- Redis Readiness: Waits for Redis to be available before starting
- Data Initialization: Ensures demo data is properly loaded into Redis
- Proper Startup: Configures and starts gunicorn with the right settings
Summary of what we learned from the file:
- The âit works on my machineâ problem is solved by
python:3.7. - Builds will be fast because of layer caching.
- The app will be running on port 8080 inside the container.
Step 2: Running the System (The âDocker Wayâ)
Now weâll run the exact same two-component system as Part 1, but this time, everything will be in containers.
ENSURE DOCKER IS RUNNING, for mac open Docker Desktop and it should start, for Linux worth researching command on how to start it up, its a good lesson in understanding
2a: Create a Private Network
First, weâll create a private virtual network for our containers. This allows them to find and talk to each other securely, isolated from our host machine.
docker network create chat-net
docker network list will prove its existing

2b: Run the Redis Database
Instead of brew install redis, we just run the official Redis image from Docker Hub. Weâll attach it to our network and give it a name.
docker run -d --name redis-db --network chat-net redis:latest
--name redis-db: Weâve named this containerredis-db. This is now its hostname on thechat-netnetwork.--network chat-net: Attaches it to our private network.
My host machine is still clean. Redis is running neatly inside its box.
docker ps will show that your container is now running

2c: Building and Running the Chat App
If you have been watching and learning about Docker, you will now understand that docker images are built using Dockerfiles and in our directory we have that Dockerfile. We will now build the image, then run it to stick with the redis container running in that chat-net network.
First, we build our app image using the Dockerfile:
docker build -t my-chat-app .
This command looks for the Dockerfile in the directory we are in and uses it to create our image.
-t my-chat-app is a tag which gives our application a name otherwise itll give us some random cryptic ID, and . says where the applications source code will be (which is the current directory)

Your image should now be built and using docker images, you should see the image available.

Now, letâs run our app. This command is the most important part:
docker run -d -p 8080:8080 --network chat-net -e REDIS_ENDPOINT_URL=redis-db:6379 --name chat-app my-chat-app
Letâs break down the docker flags:
-p 8080:8080: This connects our host machineâs port 8080 to the containerâs port 8080 (whichgunicornis using).--network chat-net: Connects our app to the same network as the Redis container.-e REDIS_ENDPOINT_URL=redis-db:6379: This is the key. We are setting the environment variable that tells the app where to find Redis. The valueredis-db:6379specifies both the hostname (redis-db) and port (6379). (every app is different and may need additional environment variables, this app only needs to know where Redis is (read documentation for each app))
(at the bottom of the project below ill explain in detail how these connect each other)

Congrats!
Now, you can open your browser to http://localhost:8080, and the app works perfectly. This is because we exposed the containerâs port 8080 to our host machineâs port 8080 using the -p flag, otherwise we would not be able to access the app from our host machine.
Whats made this easier and what we have solved is:
- No Dependency Hell: The app is built in a clean Python 3.7 environment. The
greenleterror is gone. (The image just exists so you can just run the container and redis and it just works) - No Host Pollution: My laptop isnât running Redis. Itâs just running two containers. When Iâm done, I can run
docker stop chat-app redis-dband theyâre gone. - 100% Portable: This exact setup will work on any machine with Docker. I can send this to a teammate, and it will run for them in minutes, not hours.
Weâve successfully containerised a multi-service application. Weâve replaced a fragile, manual setup with a reliable, portable, and isolated one.
In our next post Part 3, weâll take this a step further. Weâll build an automated CI pipeline with GitHub Actions so that every time we push new code, our my-chat-app image is built and pushed to a central registry (ECR) automatically.
Deeper Explanation: âWhyâ and âHowâ
Some people may be confused how microservices talk to each other so heres a bit of detail:
The command we done above -e REDIS_ENDPOINT_URL=redis-db:6379, tells your app container where to find your database container inside Dockerâs private, isolated network.
This command is the âglueâ that connects your two microservices. It solves the problem of service discovery.
âWhyâ: Containers are Isolated
By default, a Docker container is a completely isolated âblack box.â
- Your
chat-appcontainer has no idea that yourredis-dbcontainer exists. - It has its own private
localhost, its own file system, and its own network.
If you just ran the chat-app container, its code would try to connect to the database at 127.0.0.1:6379 (the default) and fail, because nothing is running on port 6379 inside its own box.
You need a way to tell the chat-app container: âHey, the database youâre looking for is not inside your box. Itâs in that other box over there.â
âHowâ: A Two-Step Solution
This solution has two parts that work together perfectly:
Part 1: The Network (--network chat-net)
This is the âphysicalâ connection. When you run both containers with the --network chat-net flag, you are plugging them both into the same private, virtual network.
The most important feature of a custom Docker network is its built-in DNS service.
This means that from inside that network:
- Any container can find any other container by its name.
- When your
chat-apptries to connect to the hostnameredis-db, the Docker networkâs DNS says, âOh, I knowredis-db! Thatâs the container at the internal IP172.19.0.2(or something like that).â
This creates the ability for the containers to find each other.
Part 2: The Appâs Configuration
This is the âlogicalâ connection. Just because the containers can find each other doesnât mean your Python code knows to look for a server named redis-db.
If you look at the appâs code in chat/config.py, youâll see this line:
redis_endpoint_url = os.environ.get("REDIS_ENDPOINT_URL", "127.0.0.1:6379")
REDIS_HOST, REDIS_PORT = tuple(redis_endpoint_url.split(":"))
This code means:
- Look for an environment variable named
REDIS_ENDPOINT_URL - If found, use that value (which should be in the format âhost:portâ)
- If not found, default to â127.0.0.1:6379â
- Split the value on â:â to get the host and port separately
The -e flag in your Docker command stands for âenvironment variable.â
When you run docker run ... -e REDIS_ENDPOINT_URL=redis-db:6379 ..., you are injecting a variable into the container.
So summarising this:
- You run the
docker runcommand for yourchat-app. - The
-eflag setsREDIS_ENDPOINT_URL="redis-db:6379"inside the container. - Your application starts.
- It reads
REDIS_ENDPOINT_URLand splits it intoREDIS_HOST="redis-db"andREDIS_PORT="6379". - The app tries to connect to Redis using these values.
- Dockerâs DNS resolves
redis-dbto the correct containerâs IP. - The connection is successful!
The code may be different from original and thats because we had to make a few modifications in order to make sure the app worked not just locally.