What Is a Container
One container is a sealed box that carries your program together with everything it needs to run: the code, libraries, the right language version, even a small part of the operating system. You send this box to any computer, and it runs exactly the same. No “installing dependencies,” no “configuring the environment,” no surprises.
🧠 Analogy: The Fully Packed Suitcase
Imagine you’re traveling and don’t trust yourself to find anything at your destination. So you pack a suitcase with EVERYTHING: clothes, a toothbrush, a pillow, even a power adapter. Wherever you arrive, you open the suitcase and have exactly the same comforts as at home. A container is that suitcase for your program.
- •The suitcase: the container (the closed box)
- •What goes inside: your app, the libraries, the right version of everything
- •The destination: any computer with Docker (your PC, the cloud, the server)
- •The result: runs the same everywhere, without “it works on my machine”
💡 A container is not a virtual machine
You might think of it as a virtual machine (VM), but it’s much lighter. A VM loads an entire operating system from scratch (takes up gigabytes and takes minutes to start). A container shares the computer’s operating system and packages only what your app needs. That’s why it starts in seconds and you can run dozens of them on the same computer without slowing it down.
Installing Docker
Before running any container, you need to install Docker. On Windows and Mac you install Docker Desktop (a program with a window). In Linux, you install Docker Engine directly from the terminal. We’ll go through each operating system.
Windows
Docker Desktop (WSL2 must be enabled)
# 1. Download the installer
docker.com/products/docker-desktop
# 2. Run the installer, select "Use WSL 2"
Docker Desktop Installer.exe
# 3. Restart and open Docker Desktop
macOS
Docker Desktop (Intel or Apple Silicon version)
# Option 1: official site
Download the .dmg and drag it to Applications
# Option 2: Homebrew (if you have it)
$ brew install --cask docker
Linux (Ubuntu/Debian)
Docker Engine, installed using the official script
# Official installation script
$ curl -fsSL https://get.docker.com | sh
# Run Docker without sudo (log in again afterward)
$ sudo usermod -aG docker $USER
👁 How to confirm it installed
Open the terminal and ask for the version. If a number appears, it’s working:
$ docker --version
Docker version 27.3.1, build ce12230
# More thorough check (client + server):
$ docker info
Server Version: 27.3.1
Containers: 0 Images: 0
If the version appears, Docker is installed and ready. You can skip to the next topic and run your first container.
⚠️ Common Error
Problem: runs docker info and you see "Cannot connect to the Docker daemon".
Solution: Docker is installed, but the engine (daemon) isn’t running. On Windows/Mac, open Docker Desktop and wait for the icon to turn green. On Linux, run sudo systemctl start docker.
docker run: Your First Container
The command docker run is the heart of Docker. It does everything at once: downloads the image (if it isn’t there), creates a container from it, and starts it. We’ll go from Docker’s "hello world" to running a real server.
docker run hello-world - The test
This command downloads a tiny image, runs it, and prints a message. It’s the official way to confirm that everything works.
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
Hello from Docker!
This message shows that your installation
appears to be working correctly.
docker run -p - Opening the door
The container is closed off. To access a site inside it from your browser, you connect one of your ports to one of its ports with -p porta-sua:porta-do-container.
# Run nginx (web server) on port 8080
$ docker run -p 8080:80 nginx
# -d = runs in the background (detached)
$ docker run -d -p 8080:80 nginx
a1b2c3d4e5f6...
# Now open it in your browser:
http://localhost:8080
To the left of : and your computer's port; the one on the right is the port inside the container.
docker ps - What's running
Lists the containers running now. With -a also shows the ones that have already stopped.
$ docker ps
CONTAINER ID IMAGE STATUS PORTS
a1b2c3d4e5f6 nginx Up 2 minutes 0.0.0.0:8080->80/tcp
# Stop a container by ID
$ docker stop a1b2c3d4e5f6
# View all, including stopped ones
$ docker ps -a
💡 Tip: you don’t need to memorize the entire ID
The container ID is a long code, but you only need to type the first characters (usually 3 or 4 are enough). Instead of docker stop a1b2c3d4e5f6, just docker stop a1b2. Docker understands.
✓ What TO DO
- ✓Use
-dto run servers in the background - ✓Check with
docker pswhat’s live - ✓Stop containers you no longer use (frees up ports and memory)
✗ What NOT to do
- ✗Change the port (
-p 80:8080when the app uses 80) - ✗Forget about containers left running and consuming resources
- ✗Use the same port on two containers at the same time
Images vs. Containers
This is the number one source of confusion for beginners. The rule is simple: the image and the template (stopped, saved on disk) and the container and the running instance (running, active). From a single image, you can create as many containers as you want.
🧠 Analogy: The Recipe and the Cake
A image and the recipe on paper: it only describes; you can't eat it. The container is the cake actually baked and ready on the table. With one recipe, you can bake as many cakes as you want, all the same. Once you’ve eaten one, make another with the same recipe.
- •Image (recipe): file sitting on disk; it doesn't use memory
- •Container (cake): running process that uses CPU and memory
- •1 image -> N containers: one recipe, several cakes
IMAGE commands
# List images on disk
$ docker images
REPOSITORY TAG SIZE
nginx latest 187MB
# Download an image
$ docker pull node:20
# Delete an image
$ docker rmi nginx
CONTAINER commands
# List running containers
$ docker ps
# Stop a container
$ docker stop a1b2
# Delete a stopped container
$ docker rm a1b2
# View its logs (output)
$ docker logs a1b2
⚠️ Common Error
Problem: try docker rmi nginx and you see "image is being used by running container".
Solution: you can't remove the recipe while there's a cake on the table. Stop and remove the containers that use the image first (docker stop + docker rm), then delete the image.
images
List Templates
pull
Download template
ps
Live containers
rm / rmi
Delete
Dockerfile: The Container Recipe
So far, you’ve used ready-made images (nginx, hello-world). But what about YOUR program? To package your code into an image, you write a Dockerfile: a text file with the instructions, line by line, for how to build the image.
📝 An example Dockerfile (Node app)
# starts from a base image with Node already installed
FROM node:20
# working directory inside the container
WORKDIR /app
# copies your project files into
COPY . .
# runs a command when building the image
RUN npm install
# command that runs when the container starts
CMD ["node", "server.js"]
FROM - where to start
Every recipe starts from a ready-made base image (Node, Python, Ubuntu...). You don’t build everything from scratch; you start with something that already exists.
COPY - bring your code in
Copies files from your computer into the image. The COPY . . takes the entire current folder to the container's working directory.
RUN - run during setup
Runs a command while the image is being built (e.g., installing dependencies). The result is saved in the image.
CMD - what to do when it starts
Defines the command that runs when the container starts. Unlike RUN (during the build), CMD runs at execution time.
💡 Tip: RUN x CMD
The most common confusion: RUN happens when you builds the image (once, then freeze it in the template). CMD happens when you runs the container (every time it starts). Think: RUN = packing the suitcase; CMD = what you do when you arrive at your destination.
Build and Push: From Dockerfile to the World
You have the Dockerfile (the recipe). Now comes the final cycle: build turns the recipe into an image, run starts a container from it, and push pushes the image to a cloud registry so any server can download and run it.
Build: create the image
O -t give the image a name (tag). The period at the end means the current folder (where the Dockerfile is).
$ docker build -t meu-app .
[+] Building 12.3s
=> [1/4] FROM node:20
=> [4/4] RUN npm install
=> naming to docker.io/library/meu-app
Run: test locally
Before sharing it with the world, run the image you just put together.
$ docker run -p 3000:3000 meu-app
Server running on port 3000
Push: upload to the registry
The registry is the “image cloud” (Docker Hub is the most common). You log in, tag the image with your username, and push it.
# 1. log in to Docker Hub
$ docker login
# 2. mark it with your username
$ docker tag meu-app seuuser/meu-app:1.0
# 3. send it to the cloud
$ docker push seuuser/meu-app:1.0
The push refers to repository [docker.io/seuuser/meu-app]
1.0: pushed
👁 The complete cycle, from zero to deploy
# 1. write the recipe (Dockerfile)
# 2. build the image
$ docker build -t seuuser/meu-app:1.0 .
# 3. send it to the registry
$ docker push seuuser/meu-app:1.0
# 4. on the server, download and run it
$ docker run -d -p 80:3000 seuuser/meu-app:1.0
✓ What TO DO
- ✓Version the tag (
:1.0,:1.1) instead of justlatest - ✓Test with
docker runbefore pushing - ✓Create one
.dockerignoreto avoid copying junk
✗ What NOT to do
- ✗Putting passwords/tokens inside the image
- ✗Push an image that has never run locally
- ✗Push the folder
node_modulesinside the image
🏆 Congratulations!
You now understand the entire Docker cycle: image, container, Dockerfile, build, and push. This means your program runs the same on your PC and on any server in the world. In the next module, you’ll learn to orchestrate multiple containers at once with Docker Compose.
📚 Module Summary
Next Module:
4.2 - Docker Compose: orchestrating multiple containers (app + database + cache) with a single file