Overview
An image is a ready-made package, a container is a running copy. docker run starts one, -p connects a port, -e sets a variable, -v keeps data. Compose starts several from one file.
On this page7 sections
Goal
Project #1 in the 45-day plan needs a PostgreSQL database and other services. You could install each one by hand, and it would take hours and break in different ways on every laptop. Docker solves this. It runs a program inside a ready-made box, so it works the same on your laptop and on a server.
By the end of this lesson you can run a container, see what is running, connect to a database inside a container, stop it, and start several services with one file using Docker Compose. You need Docker Desktop installed. The setup runbook of the 45-day plan shows how.
Docker Desktop must be running
Almost every Docker error for beginners has one cause: Docker Desktop is not open. Start it, wait until it says it is running, then try your command again. This tab cannot run Docker, so the exercise reads a command as text.
Why this order
Two words come first. An image is a ready-made package: a program plus everything it needs to run. A container is one running copy of an image. It is like a recipe and a cooked meal. The image is the recipe, and you can cook as many meals (containers) from it as you like.
You start with one container, because the options (ports, passwords, saved data) make sense one at a time. Compose comes last. A Compose file is just those same options written in a file, so you do not retype a long command every day.
Five words you need. Everything else is detail.
| Word | Meaning | Example |
|---|---|---|
| Image | A ready-made package of a program | postgres:16 |
| Container | A running copy of an image | The database running now |
| Port | A numbered door into the container | 5432 is the usual Postgres door |
| Volume | A folder that keeps data after the container stops | pgdata |
| Compose | A file that starts several containers together | docker-compose.yml |
The checklist
- docker run hello-world. Docker downloads a tiny image and runs it once. If you see 'Hello from Docker!', everything works.
- docker run -d --name shop-db -e POSTGRES_PASSWORD=devpass -p 5433:5432 postgres:16. Start a PostgreSQL database in the background (-d).
- docker ps. List the containers that are running. You should see shop-db.
- docker logs shop-db. Read what the database printed. This is the first place to look when something fails.
- docker exec -it shop-db psql -U postgres. Open a SQL prompt inside the container. Type \q to leave.
- docker stop shop-db, then docker rm shop-db. Stop it and remove it when you are done.
Look closely at step 2, because it holds the three options you will use most. -e sets an environment variable inside the container. Here it sets the database password. -p 5433:5432 connects a port on your laptop to a port inside the container. The first number is YOUR side (5433), the second is the container side (5432). So a tool on your laptop connects to localhost:5433 and Docker passes it to Postgres inside. --name gives the container a name you can remember.
The two port numbers are not the same thing
In -p 5433:5432 the left number is the port on your laptop, and the right number is the port inside the container. If you swap them, your tool connects to the wrong door and gets Connection refused. Left is outside, right is inside.
By default, a container forgets everything when you remove it. To keep the database files, add a volume: -v pgdata:/var/lib/postgresql/data. A volume is a folder managed by Docker that stays when the container goes away.
Worked pass
Here is the session for a throwaway database. Lines that start with $ are what you type.
$ docker run -d --name shop-db -e POSTGRES_PASSWORD=devpass -p 5433:5432 postgres:16
8f3c1d2a9b7e...
$ docker ps
CONTAINER ID IMAGE STATUS PORTS NAMES
8f3c1d2a9b7e postgres:16 Up 5 seconds 0.0.0.0:5433->5432/tcp shop-db
$ docker exec -it shop-db psql -U postgres -c "select 1"
?column?
----------
1
$ docker stop shop-db && docker rm shop-db
shop-dbIn the PORTS column, 0.0.0.0:5433->5432 reads as 'port 5433 on my laptop goes to port 5432 in the container'. That is the same -p option you typed.
The same thing with Docker Compose
Typing that long command every day is tiring, and a project often needs more than one service. Compose lets you write the options once in a file called docker-compose.yml.
services:
shop-db:
image: postgres:16
environment:
POSTGRES_PASSWORD: devpass
ports:
- "5433:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Each line matches something you typed before. image is the image name. environment is the -e option. ports is the -p option. volumes is the -v option. Now two commands do all the work.
$ docker compose up -d $ docker compose ps $ docker compose down
docker compose down stops and removes the containers but keeps the volume, so your data is still there next time. Add -v only if you want to delete the data as well.
Never put real passwords in a Compose file you share
devpass is fine for a database that lives on your laptop. For anything real, the password comes from an environment variable or a secret store, and the file is not committed with the password inside.
Common beginner questions
What is the difference between Docker and a virtual machine?
A virtual machine carries a whole operating system, so it is big and slow to start. A container shares your computer's system and only carries the program and its files, so it starts in seconds.
Why does my container exit right away?
Run docker logs name to read the error. The most common reasons are a missing required setting (such as POSTGRES_PASSWORD) or a port that another program already uses.
Where is my data when I remove a container?
If you used a volume, it is still there. If you did not, it is gone. This is why you add -v for any container whose data you care about.
What comes next
The next lessons read a Dockerfile (the recipe that makes an image) and find bugs in one. You now know what an image and a container are, so those lessons will make sense.
Practice
The exercise gives you docker run commands as text. Write published_ports() to read the -p options and return the pairs of ports. This is the skill you use when you debug a 'connection refused' error.
Practicals · load into the editor
After you read the theory, run these in the pane on the right. They execute in this tab, no cluster.