Week 10: Dockerfile & DockerHub




We have to make sure permisions and users are properly set up to avoid one app being able to access another app’s files.
What if our Java app requries OpenSSL 1.1.x while Python app now requires OpenSSL 3.x?
We need isolation!

A container runs from an OCI image, which contains:
A root filesystem (the files the app needs)
A config (environment variables, network, entrypoint…)
Layers (incremental filesystem changes: adding files, installing packages, etc)
The image provides:
What will run
How it should run (metadata)
The runtime is what interprets the image and creates the container process.
It is the one responsible for executing the app inside isolation.
We’ll be using Docker Engine.
ubuntu imageThe ubuntu image is extremely simple, built with only one layer.
The Ubuntu developers pack all the file system into an archive and that is the first layer.
Tip
We can use docker inspect <image-name> to see details of the image, including which layers it has.
If we do docker image ls ubuntu, we’ll see it is only 78MB. That is because it includes only the userspace filesystem and essential packages.
It does not include:
A container is not a VM, it’s just a process isolated with its own filesystem.
ubuntuDockerfile makes it possible to add layers and customize image config.DockerfileDockerfile.The resulting image still has only one layer, which makes sense since there are no changes made to the filesystem.
DockerfileLet’s see an example that makes more sense.
RUN keyword lets us execute commands on the base image we are using.CMD keyword is used to define the default command to be executed when we run a container from this image.This time, the resulting image will have 2 layers:
ubuntu image.Note
Why am I not using sudo?
Once we’ve built an image, we can use docker run command to make Docker Engine execute it.
$ docker run amsa
__ _ _ __ ___ ___ __ _
/ _` | '_ ` _ \/ __|/ _` |
| (_| | | | | | \__ \ (_| |
\__,_|_| |_| |_|___/\__,_|Important
Adding -it to docker run command gives the container an interactive terminal by keeping STDIN open (-i) and allocating a TTY (-t). This is why docker run -it ubuntu gives us a bash terminal while docker run ubuntu doesn’t.
Let’s make figlet print the contents of a file instead, a file that resides in our host machine:
COPY <src-file> <dst-file> is used to make our files available to the container.Warning
Every time we change the contents of message.txt, we’ll have to rebuild the image.
What is the difference between the Dockerfiles below?
FROM ubuntu
COPY message.txt my-message.txt
RUN apt update && apt install -y figlet
CMD cat my-message.txt | figletFROM ubuntu
RUN apt update && apt install -y figlet
COPY message.txt my-message.txt
CMD cat my-message.txt | figletTip
The fact that layers are incremental allows for caching. If only layer 3 has changed, layer 1 and 2 do not have to be rebuilt. This is something we must keep in mind when writing Dockerfiles.
Imagine I have Python project with the following files:
├── main.py
├── message.txt # The message we want pfiglet to print
├── requirements.txt # pyfiglet declared as a dependency
└── .venv # The python virtual env
To run my project, I would need to:
If I want this to happen inside a container, I could create the following Dockerfile:
Note
Why didn’t I create a python virtual environment inside the docker container?
COPY . .COPY . . will copy all files on the context directory inside the container.COPY . . we’re also copying .venv and Dockerfile, which are useless inside the container..gitignore works, we can create a file named .dockerignore..dockerignore won’t exist..dockerignore:message.txt file?ubuntu imageIf we had a look at how could the Dockerfile of the ubuntu image look like, it could be something like:
FROM scratch means we start with an empty image.ADD works like COPY but with added functions, like being able to decompress an archive.CMD ["/bin/bash"] is very similar to CMD /bin/bash read more here.So far we’ve seen various keywords we can use in Dockerfiles:
FROM – Defines the base image for the build.
RUN – Executes build-time commands.
CMD – Specifies the default runtime command.
COPY – Copies files from the build context into the image.
ADD – Like COPY, but also supports URLs and auto-extracting archives.
Tip
Any line in the Dockerfile that modifies the filesystem of the resulting image creates a new layer. Thus, all the above keywords can create layers except for CMD which is not executed during build-time.

A simple Dockerfile that runs the python webserver could be:
We can now build and run the container:
Note
But how do we access the web server? Visiting http://localhost:8080/ doesn’t work..
docker run command to do so by adding the option -p:-p HOST_PORT:CONTAINER_PORT, so with the above example, we would be able to access the webserver on http://localhost:8085/A container registry is a service that stores and distributes container images. Think of it as GitHub but for container images.
Common registries include:
Tip
When we do docker run ubuntu, Docker is first looking if we already have an image named ubuntu on our system, if we do not, it will download it from DockerHub.
Tip
docker pull ubuntu will do the same but without running the container.
To upload an image to DockerHub:
docker login.A tag is the version label of an image. For example:
Tip
If no tag is specified, Docker assumes latest tag.
We can use specific versions of images with tags. To do so, add :<tag> at the end of the image name when using docker pull or docker run.
Important
You should always use explicit tags in production. Never rely on latest. In fact, we should rely on hashes for maximum reproducibility.
v1, v2, latest).latest).Why is the Ubuntu image only ~78MB even though a full Ubuntu installation is several gigabytes?
Why should heavy steps like pip install -r requirements.txt come before copying the full project code?
How can we ensure specific files and folders are not copied into the container?
What is the difference between COPY and ADD in a Dockerfile?
What is the purpose of the CMD instruction, and why does it not create a layer?
What does the -p 8085:8080 option in docker run actually mean?
Why can’t you access a container’s webserver on localhost:8080 unless you map a port?
Why is relying on the latest tag dangerous in production environments?
Youtube playlist on containers: https://www.youtube.com/playlist?list=PLozcbFx8FoPH30kYPbPuPsvxASWoLo9XB
Docker docs on CMD shell vs exec: https://docs.docker.com/reference/dockerfile#shell-and-exec-form
Why is it dangerous to run docker run -v /:/host alpine?
Why is docker run --network host considered dangerous?
Build an image using multi-stage builds.
Ready to have some fun? Check out the fourth AMSA activity here!

AMSA 2025-2026 - 🏠 Home