I used to think of Docker as simply a convenient tool for setting up Linux servers. However, since I expect to use Docker frequently in the future, I decided to take a step back and understand its core concepts properly.
When using Docker, you encounter several related files, such as compose.yaml and backend.Dockerfile. Although their contents are written in relatively readable terms, concepts such as images and containers can still feel confusing.
The information is also often explained in fragments, so I wanted to connect everything and explain the full process in one place.
Containers and Images
At first, I thought Docker created a small Linux computer called a container and then downloaded an image into it. The actual order is the opposite.
An image is a read-only template containing the Linux files, libraries, project code, and default commands required to run a program. A container is not so much a small computer as an isolated group of processes created from that image—typically one main process and its child processes.
1Prepare a filesystem from an image 2→ Configure networking, volumes, and isolation 3→ Run the main process in that isolated environment 4= A running container
That is the basic relationship between an image and a container.
In compose.yaml, we declare the services required by the project.
- If a service only has
image:, Docker first looks for that image locally. If it is not available, Docker downloads the prebuilt image from a registry such as Docker Hub. - If a service has
build:, Docker reads the specified Dockerfile and combines a base image, required packages, and project code to create a project-specific image.
This means services such as Redis and PostgreSQL use prebuilt images that have already been published, while project-specific applications such as the backend and web frontend are built from Dockerfiles that we define ourselves.
What Is a Dockerfile?
A Dockerfile defines the internal composition of a project image.
For example, a backend image is roughly assembled like this:
1External Python base image 2+ Required Python packages 3+ Project code 4+ Default startup command 5= Project backend image
The FROM instruction specifies the external base image to use.
1FROM python:3.12-slim
Docker first looks for this Python image locally. If it is unavailable, Docker downloads it from Docker Hub. It then installs packages with RUN and adds project code with COPY.
1COPY app ./app 2RUN pip install ... 3CMD ["uvicorn", "app.main:app"]
The results of these operations are built up as read-only image layers.
At first, I thought that multiple FROM instructions inside a Dockerfile meant that it was creating multiple final images. In most cases, however, they represent separate build stages used to create one final runtime image.
Conclusion
Once all required images are ready, Docker Engine creates the networks and volumes and runs the API, web server, database, Redis, and other services as separate containerized process groups.
Windows cannot run Linux containers directly, so Docker Desktop uses a WSL2 Linux VM. That VM provides a single Linux kernel shared by multiple containers.
1Windows 2└─ WSL2 Linux VM 3 └─ Linux kernel 4 ├─ API container 5 ├─ Web container 6 ├─ PostgreSQL container 7 └─ Redis container
In other words, containers are isolated process groups, and multiple containers share the Linux VM and its kernel provided by Docker Desktop.
Ultimately, the command docker compose up --build means:
Read the Compose configuration, download or build the required images, create the networks, volumes, and containers, and then start all the processes.
Here, up means to create and start the containers, while --build tells Docker Compose to build the project images before starting them.