21.4. Containers
Table of Contents
What Is a Container?
A container is a lightweight, isolated environment that runs your application and everything it needs, such as libraries and runtime, on top of a shared operating system kernel.
You can think of it as a more efficient and portable alternative to a full virtual machine. Instead of emulating an entire computer, a container shares the host OS, but keeps processes, files, and network separated from other containers.
Key points:
- Containers package the application and its dependencies together.
- Multiple containers can run on the same machine, isolated from each other.
- Containers start and stop much faster than virtual machines.
- Containers are created from images, which are like templates.
Important: A container is a running instance of an image.
Image = template. Container = process created from that template.
Example analogy:
- Image: a class definition in code.
- Container: an object created from that class.
Or:
- Image: a baking mold with dough recipe.
- Container: a baked cake that came from that mold.
You can run many containers from the same image, just like you can create many objects from the same class.
Containers vs Virtual Machines
Containers and virtual machines solve a similar problem, but in different ways.
| Feature | Container | Virtual Machine |
|---|---|---|
| Boot time | Very fast (seconds or less) | Slow (tens of seconds to minutes) |
| Resource usage | Light, shares host OS kernel | Heavy, full OS per VM |
| Isolation | Process level, via namespaces and cgroups | Strong, full OS boundary |
| Image size | Small to medium | Large (many GB) |
| Portability | High | Medium |
| Typical use | Microservices, APIs, small services | Full OS environments, legacy systems |
For backend development, containers are ideal for:
- Running your app in a reproducible way on any machine.
- Creating separate services, for example API, database, Redis.
- Testing different versions of dependencies without polluting your host.
You should still understand that containers use the host kernel. They are not a full OS, although they can look like one from the inside.
Basic Container Lifecycle
A container has a simple lifecycle:
- Pull image
docker pull python:3.12-slim - Create and start container
docker run python:3.12-slim python -V - Run main process
The container runs its main process (for examplepython,uvicorn,postgres). - Stop
When the main process exits, the container stops. - Remove (optional)
You can delete stopped containers to free space.
Rule: A container exists only as long as its main process is running.
If that process exits, the container stops automatically.
Example:
# Start an interactive container
docker run -it --name demo python:3.12-slim bash
# Inside the container:
exit # or press Ctrl+D
As soon as bash exits, the container stops. docker ps will not show it anymore, but docker ps -a will.
Running Your First Containers
Running a Simple Container
Try a very simple example:
docker run hello-worldWhat happens:
- Docker checks if the
hello-worldimage exists locally. - If not, it downloads it from Docker Hub.
- It creates a container from that image.
- It runs the container. The container prints some text and then exits.
If you run:
docker ps -a
you will see a stopped container with hello-world as the image.
Interactive Containers
You can start a container and get a shell inside it.
docker run -it --name py-shell python:3.12-slim bashFlags:
-ikeeps STDIN open.-tallocates a pseudo-TTY, so you get a normal terminal.--name py-shellassigns a human-friendly name.
Inside the container:
python -V
touch example.txt
ls
You will see example.txt inside the container file system, but not on your host.
Type exit to stop the container.
Outside again:
docker ps -a
Look for py-shell. Status should be "Exited".
Detached vs Attached Containers
Containers can run attached to your terminal, or in the background.
Attached Mode
Example:
docker run --name counter alpine:3.18 sh -c "i=0; while true; do echo $i; i=$((i+1)); sleep 1; done"You will see numbers printed every second. To stop:
- Press
Ctrl + Cto stop the container and kill the process, or - From another terminal, run
docker stop counter.
Detached Mode
To run in the background, use -d.
docker run -d --name counter-bg alpine:3.18 sh -c "i=0; while true; do echo $i; i=$((i+1)); sleep 1; done"Now:
- The command returns immediately.
- The container continues running.
You can see it:
docker psTo see logs:
docker logs counter-bgTo stop:
docker stop counter-bg
Rule:
Use -d to run a container in the background.
Use docker logs <name> to see its output.
Naming and Inspecting Containers
Naming Containers
You can let Docker auto generate names or specify your own.
# Auto generated name
docker run alpine:3.18 echo "hello"
# Custom name
docker run --name my-alpine alpine:3.18 echo "hello"Custom names are very useful when you have many containers.
Listing Containers
Common commands:
# Running containers
docker ps
# All containers (including stopped)
docker ps -aTypical columns:
| Column | Meaning |
|---|---|
| CONTAINER ID | Short ID of the container |
| IMAGE | Image used to create the container |
| COMMAND | Main command/process |
| STATUS | Running, Exited, etc |
| PORTS | Port mappings (if any) |
| NAMES | Container name |
Inspecting Containers
You can see detailed information about a container.
docker inspect my-alpineThis prints JSON with details such as:
- Environment variables
- Mounts
- Network settings
- Image used
- Commands
You usually do not need all of it, but it is useful when you debug.
Container File System and Persistence
Each container has its own file system, isolated from the host.
Example:
docker run -it --name files-test alpine:3.18 sh
# Inside the container:
touch /tmp/inside.txt
ls /tmp
exitOn your host:
ls /tmp
You will not see inside.txt. It exists only inside the container.
If you remove the container, its file system is removed too.
docker rm files-test
Important: Files created inside a container are ephemeral.
If the container is removed, those files are gone, unless you used volumes or bind mounts.
Volumes and persistent storage are covered in another chapter. For now, remember that you should not store important data only in a container file system.
Ports and Networking Basics for Containers
Backend services need to listen on a port inside the container and be reachable from the host or other containers.
Exposing a Port to the Host
Assume you have an image that runs a server on port 8000 inside the container.
To map it to port 8000 on your host:
docker run -d --name web -p 8000:8000 my-backend-imageFormat:
-p <HOST_PORT>:<CONTAINER_PORT>Examples:
-p 8000:8000host port 8000 → container port 8000.-p 80:8000host port 80 → container port 8000.
Now you can access the service at http://localhost:8000.
If you do not add -p, the service inside the container will not be reachable from the host.
Multiple Containers from the Same Image
You can run multiple containers from the same image with different names and ports.
docker run -d --name api-v1 -p 8001:8000 my-backend-image
docker run -d --name api-v2 -p 8002:8000 my-backend-imageBoth containers run the same code, but you access them:
http://localhost:8001forapi-v1.http://localhost:8002forapi-v2.
Managing Containers: Start, Stop, Remove
You will create, stop, and remove containers all the time while developing.
Stopping Containers
# Graceful stop (send SIGTERM then SIGKILL if needed)
docker stop my-container
# Force stop (SIGKILL)
docker kill my-container
You normally use docker stop. Use docker kill only if the process does not respond.
Starting Containers
If a container is stopped, you can start it again.
docker start my-containerThis resumes the container with the same process command, environment, and configuration that were used originally.
Removing Containers
To remove a container:
docker rm my-containerIf the container is still running, you will get an error.
To stop and remove in one step:
docker rm -f my-containerCleaning Up
Over time you will collect many stopped containers. You can clean them up:
# Remove all stopped containers
docker container pruneDocker will ask for confirmation.
Rule:
Do not store important data in containers that you plan to prune or remove.
Use volumes or an external database for persistence.
Common Practical Examples
Running a Temporary Python Script
You might want to test Python code without installing Python on your host.
docker run --rm python:3.12-slim python -c "print('Hello from container')"Here:
--rmautomatically removes the container when it exits.- The container starts, runs the one-line Python script, and stops.
Testing a Simple HTTP Server
Run a tiny HTTP server in a container:
docker run -d --name http-test -p 8080:80 nginx:alpine
Now open http://localhost:8080 in your browser. You will see the default Nginx welcome page.
To stop and remove:
docker stop http-test
docker rm http-testAttaching to a Running Container
If you want to get a shell inside a running container, use exec:
docker exec -it http-test sh
You are now inside the container. To exit, type exit. The container keeps running because the main process (Nginx) is still alive.
Typical Mistakes New Developers Make
Here are some common issues and how to avoid them.
| Mistake | Explanation | Fix |
|---|---|---|
Forgetting -p when running APIs | Container runs, but service is not reachable from host | Always add -p HOST:CONTAINER when you need external access |
| Expecting data to persist inside container | Data written to container filesystem is removed with it | Use volumes or external databases |
| Confusing image and container | Trying docker start on an image, not a container | Remember you start containers, not images |
| Not naming containers | Hard to remember or manage them | Use --name with a clear name |
Forgetting --rm for one-off commands | Many stopped containers piling up | Use --rm when running temporary commands |
Understanding these basics of containers will make it much easier to build, run, and deploy backend services consistently across your development, testing, and production environments.
Views: 10
KAHIBARO