TutorialsDocker › The Commands

Docker · Page 3 of 6

Building, Running, and Inspecting Containers

The Docker CLI is large, but day-to-day work uses a small, stable handful of commands: build an image, run it, look at what's running, read its logs, and clean up afterward. This page covers those, then the docker compose equivalents this project actually uses — and finishes with the exact commands for this repository.

Codey the Sr Developer, standing with a pointer, ready to walk through the guide
Build an image → docker build Start a container → docker run Whole stack → docker compose up Reclaim space → docker system prune

Purpose of this page

Know the handful of commands needed to build, run, debug, and tidy up containers — and which ones you'll actually type while working on this project.

Codey typing on a laptop
a small set, used constantly

Building an image docker build

Turns a Dockerfile plus a build context into an image stored locally.

docker build --tag metals-api:local --file metals_api/Dockerfile .
The three piecesevery build has them
  • --tag (or -t) names the result, as repository:tag. Without it you get an untagged image identified only by a hash.
  • --file (or -f) points at the Dockerfile. Omit it and Docker looks for Dockerfile in the context directory.
  • The trailing . is the build context — easy to forget, and the cause of the classic "docker build" requires exactly 1 argument error.
Useful flagsoccasionally

--no-cache forces every layer to rebuild, which is how you prove a caching problem rather than guess at one. --progress=plain prints full build output instead of the collapsing summary view — invaluable when a RUN step fails and you need to see why.

Running a container docker run

Creates and starts a container from an image. Nearly every flag exists to connect the container to something outside itself — a port, a variable, a folder.

FlagWhat it does
-dDetached — run in the background and return the prompt instead of streaming output.
-p 8080:8080Publishes a port: host:container. The left number is what you type in a browser; the right is what the app listens on inside.
-e KEY=valueSets an environment variable, overriding any ENV default baked into the image.
--name metals-ui-testGives the container a memorable name, so later commands don't need the generated hash.
--rmDeletes the container automatically when it stops — ideal for one-off test runs.
-v pgdata:/var/lib/postgresql/dataMounts a volume, so data written to that path survives the container being removed.

Port mapping trips everyone up once: -p 8888:8080 does not change what the app listens on. Nginx inside the UI container is still bound to 8080; you've only chosen which door on your own machine leads to it. That's precisely the mapping docker-compose.yml uses for the UI.

Seeing what's happening debugging

When something doesn't work, these four answer “is it running?”, “what did it say?”, and “what does it look like in there?”

Codey holding a magnifying glass
look before guessing
CommandWhat it tells you
docker psContainers currently running, with their ports and health status. Add -a to include stopped ones — essential when a container exited immediately and has vanished from the default list.
docker logs -f <name>Everything the container wrote to standard output. -f follows live. This is the first place to look for a crash on startup.
docker exec -it <name> shOpens a shell inside a running container, to check that files landed where you expect or that a config rendered correctly.
docker imagesImages stored locally, with their tags and sizes — useful for confirming a build actually produced what you meant.

docker exec only works on a running container. If the container keeps exiting, there's nothing to exec into — read docker logs instead, which still works on stopped containers.

Cleaning up reclaiming disk

Docker never deletes anything on its own. Old images, stopped containers, and orphaned build cache accumulate quietly until a disk fills up.

Codey holding a bug-hunting net
careful with prune
CommandWhat it removes
docker stop <name>Stops a running container, leaving it around to inspect or restart.
docker rm <name>Deletes a stopped container. Add -f to stop and delete in one step.
docker rmi <image>Deletes an image, provided no container still references it.
docker system pruneBulk cleanup: stopped containers, unused networks, dangling images, and build cache.

Read the prompt before confirming a prune. Plain docker system prune leaves named volumes alone, but adding --volumes deletes them too — which would take this project's pgdata_metals volume, and every row in your local database, with it.

The Compose equivalents several containers at once

docker compose reads docker-compose.yml and applies the same operations across every service defined in it, so you don't hand-manage three containers and a network. Run these from the folder containing the compose file.

CommandWhat it does
docker compose up -dCreates the network, then builds anything missing and starts every service in the background.
docker compose up -d --buildSame, but rebuilds images first — the one to use after editing application code or a Dockerfile.
docker compose psStatus and health of just this project's services.
docker compose logs -f apiFollows one service's logs; drop the name to interleave all of them.
docker compose downStops and removes the containers and network, keeping named volumes.
docker compose down -vThe same, plus deletes the volumes — the full database reset covered in the database tutorial.

The most common Compose confusion: up without --build reuses an existing image, so your code edit appears to have done nothing. If a change isn't showing up, rebuild before debugging anything else.

The commands for this project copy these

Everything above, applied to this repository. All commands run from the project root.

#GoalCommand
1Start the whole stack (db + API + UI)docker compose up -d --build
2Check everything came up healthydocker compose ps
3Watch the API's logsdocker compose logs -f api
4Build just the API image by handdocker build -t metals-api:local -f metals_api/Dockerfile .
5Build just the UI image by handdocker build -t metals-ui:local metals_ui
6Stop everything, keep the datadocker compose down
7Stop everything and wipe the databasedocker compose down -v

Note the difference between rows 4 and 5: the API build passes . (the repo root) as its context with an explicit -f, while the UI build passes metals_ui and lets Docker find the Dockerfile inside it. That asymmetry is deliberate, and it comes back in Deployment when these same images get built in Azure.

Codey giving a thumbs up

Commands covered — time to read the two real Dockerfiles in this repository, starting with the API.