
Week 1 Dockerise App
0. Learning Materials
Andrew's repo:
My branch repo:
01-00-app-containerisation
Task List
1.1 Workflow - Backend
We create a Dockerfile
in the folder backend-flask
.
FROM python:3.10-slim-buster
WORKDIR /backend-flask
COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
COPY . .
ENV FLASK_ENV=development
EXPOSE ${PORT}
CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=4567"]
line 1: pull the
puthon slim-buster image version 3.10
from the Docker Hub.line 3: set the working directory inside the container
line 5: copy the file
requirements.txt
inside the container's working directory.line 7: run the command
pip3 install -r requirements.txt
. This will install all the dependencies listed in the requirements file.line 9: copy everything from the root folder (where Dockerfile is located on the host machine) into the working directory inside the container.
line 11: set an environmental variable called
FLASK_ENV
inside the container.In a Flask application,
FLASK_ENV
indivates the environment in which the Flask app is running. This variable accepts there valuesdevelopment
,production
andtesting
. WhenFLASK_ENV=development
, Flask providers features like debug mode, auto-reloading on code changes, and detailed error messages.
line 13: expose the Docker container's port to the host machine.
line 14: the command to run flask inside the container.
--host=0.0.0.0
is our local machine (your desktop, laptop, etc.)--port
is the port for our flask server.
Inside or Outside the container?
FROM python:3.10-slim-buster
WORKDIR /backend-flask
COPY requirements.txt
requirements.txt
RUN pip3 install -r requirements.txt
COPY .
.
ENV FLASK_ENV=development
EXPOSE ${PORT}
CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=4567"]
What happens inside the container?
If you wonder how the docker container will be furnished (in other words, how the "image will be built"), we can visualise what will happen inside the Docker container by running the commands in the Dockerfile
in our local machine's terminal.
line 7: cd into
backend-flask
, then installrequirements.txt
on terminal.pip install -r requirements.txt

Line 14: run a flask server.
python3 -m flaks run --host=0.0.0.0 --port=4567
Then go visit the backend's home endpoint (
/api/activities/home
)


The endpoint is inaccessible because the variables are missing. We need to set the env variables.
from flask import Flask
from flask import request
from flask_cors import CORS, cross_origin
...
app = Flask(__name__)
frontend = os.getenv('FRONTEND_URL') # these URL values have to be configured.
backend = os.getenv('BACKEND_URL')
origins = [frontend, backend]
Set env variables:
export FRONTEND_URL="*"
export BACKEND_URL="*"
Then run the backend-flask container again. Hit the endpoint again.
Make sure to unlock the port on the port tab (in Gitpod workspace)
Open the link for
4567
in your browserAppend
/api/activities/home
to URL4567-${GITPOD_WORKSPACE_ID}.${GITPOD_WORKSPACE_CLUSTER_HOST}/api/activities/home

Build a Docker image
We completed our Docker container simulation. Now we will build a Docker image for the backend. The above simulation will actually happen inside the container once we run a container based on this image.
Run:
docker build -t backend-flask ./backend-flask

These are various Docker commands that runs a backend-flask
container from the image we built (the image ID is 12a857404d45
in the above image for example).
docker run --rm -p 4567:4567 -it backend-flask
--rm
: remove the container when exiting the container.-p
: short for--port
. Maps the host port to the container port.-it
: short for "interactive". When the container starts running, it opens the bash terminal inside the container so you can interact with your container through bash commands.This is useful for checking if env variables are correctly set inside the container.
backend-flask
at the end: this is the name of the Docker image. The image (name or ID) has to come always at the end of the command.
FRONTEND_URL="*" BACKEND_URL="*" docker run --rm -p 4567:4567 -it backend-flask
: this sets the env variables for this single run of this container. You can use this when yourDockerfile
doesn't have env variables set.docker run --rm -p 4567:4567 -e FRONTEND_URL='*' -e BACKEND_URL='*' -it backend-flask
-e
: the tag for environment variable. You can provide its value directly in the command.
docker run --rm -p 4567:4567 -e FRONTEND_URL -e BACKEND_URL -it backend-flask
: you can just assign env variables in case you have these variables' values set in your local machine.set FRONTEND_URL="*"
set BACKEND_URL="*"

Troubleshooting Docker containers
Check the logs
In Gitpods, there are two ways to open view the logs. The below image shows both ways. First, in the Docker tab on the far-left column, menu-click on the container, then on the "View Logs" option. Otherwise, you can run the following command:
docker logs --tail 1000 -f <CONTAINER_ID>
--tail
: will show at least 1000 lines. Change the value for a different amount of lines.-f
: stands for "follow". With this tag, the command will keep the log stream open, continuously showing new log entries as they occur in real-time.

Maybe something is wrong with the env variables. Let's check if the values are correctly set. Run the following command:
docker exec -it <CONTAINER_ID>
exec
: execute-it
: interactive mode

In the above image, the result of the logging command (env | grep URL
) shows that the env variables are correctly set. So what's the problem?
Well, it turns out, I didn't append the desired endpoint (/api/activities/home
) correctly. Appending the endpoint returns the correct result.

Now that the backend container is running and the endpoints are responding correctly, we conclude building the docker image for the backend.
1.2 Workflow - Frontend
In our local machine, cd into the frontend folder, install npm
and create a Dockerfile
.
cd frontend-react-js
npm i
code Dockerfile
The Dockerfile
code for the frontend is as below:
FROM node:16.18
ENV PORT=3000
COPY . /frontend-react-js
WORKDIR /frontend-react-js
RUN npm install
EXPOSE ${PORT}
CMD [ "npm", "start" ]
line 1: pull
node of version 16.18
from the Docker hub.line 3: set env variable. Our react app for the frontend will use the port number 3000.
line 5: we are currently in the
frontend-react-js
folder. This is the root directory for the frontendDockerfile
. Copy everything in this root folder into the frontend Docker container.line 6: set the working directory inside the container as
/frontend-reactp-js
.line 7: install
npm
inside the container.line 10: now it's all set in the container. Run
npm start
inside the container.
Now that the Dockerfile
is ready, build it into an image.
docker build -t frontend-react-js ./frontend-react-js
Once the image is built, we double-check it by listing the images.
docker images
gitpod /workspace/aws-bootcamp-cruddur-2023-again (01-app-containerisation) $ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
aws-bootcamp-cruddur-2023-again-frontend-react-js latest 9a76f2ba97 7 min ago 1.18GB
aws-bootcamp-cruddur-2023-again-backend-flask latest 464297df4e 1 hr ago 130MB
backend-flask latest 12a857404d 1 hr ago 130MB
1.3 Workflow - Docker-compose
We now have 2 different images - one for the backend and the other for the frontend. We want to run one container for each, so there will a total of two containers running. As we continue to develop the app, we will have more containers in our app stack. When we have to manage multiple containers at the same time, manually managing them can be painful and time-consuming. This is where docker-compose
comes in to our rescue.
We can compose multiple container definitions into a single yaml file and run it in one go. Our docker-compose.yml
looks like below. The complete source code is here:
Once the file is ready, run:
docker compose -f "docker-compose.yml" up -d --build
-f
tag here means--file
. Provide a file path after the tag.up
is the command to rundocker-compose
using the specified file.-d
is detached mode. The terminal in which you rundocker-compose
will be available for use again once thedocker-compose up
process is complete.--build
: build the image again in case the image for a container is not available or changes have been made toDockerfile
s.

The image below is the result of docker-compose up
. The two containers for the backend and the frontend are running at the same time. The endpoint url also returns the correct Cruddur page.


In the Cruddur portal above:
The API delivers the mock data.
There are no functions in the frontend UI.
No buttons work because no feature has been implemented.
These are useful commands for docker-compose
operations from now on.
docker compose -f "docker-compose.yml" up -d --build
docker compose -f "docker-compose.yml" down
This marks the end of our activity for the Week 1 live stream.
2. Discussion
Containers vs VMs (Virtual Machines)
Virtual Machine is a smart way to improve the use of your physical hardware resources. Some genius computer experts developed the concept and technologies from around 1960. Then about 20 years later, the container technology came into being. Both are technologies used to isolate environments, but how are they different?
VM: creates another whole computer inside your computer (which is the underlying host machine) using your computer's resources (memory, CPU, etc.). So this consume more resources of the host machine than containers do because every time a VM is created, it has to be equipped with everything including the OS (which is called a guest OS).
This could be pretty wasteful. If you travel to a different city or a country and stay there for a few nights, paying for a single room at a lodging is much cheaper and faster than renting the entire lodging service or building whole.
Container: the docker containers are just isolated environments, but without replicating the existing key resources repeatedly. It shares the host machine's OS, so it's much lighter and faster than the VM.

🍁 Containerising your application
Putting your app into a container offers the following benefits:
Portability: you can hand over your entire environment along with the configurations all set, so your team mates can jump in right away without going through the hassle of replicating your environment.
Compatibility: dependencies such as Python libraries or Go packages, they get updated frequently. Let's say our project has a lot of dependencies and you forgot to specify the version for each dependency. Now it's our team mates' nightmare to deal with the version conflict to figure out which version of dependencies to install.
🍁 Guest instructors' insights
Edith: Makes testing easy - you can easily kill the container and start it over again, and again, and again.
James: as your team grows, the variety of dev environment will also grow - different OS, dependencies, if your app requires a compiler or not. Containerising the work environment reduces the pain stemming from dev env differences/incompatibilities

More on --rm
--rm
is really convenient because the container always cleans up after themselves. However..:
sometimes we want to stop the docker container only without not removing it so we can rerun it and pick up something from it (such as logs).
On the other hand, if you stop a running the container but don't remove it, it will clog your machine, meaning it occupies your storage space. Below is what happens under the hood when you keep (re)building, running, and stopping containers. This happens often during the development & testing process.

<none>
are fragments of images. They are also called 'dangling' images or 'untagged' images. These are generated from orphaned images (that lose their tags) or multi-stage docker builds. So what to do about it?
docker system prune -f
You can clean all the dangling images by running this command.
-f
here means--force
. Be sure to know what you are doing. You don't necessarily always want to clean up your storage with this force command.

Last updated