CoursesDocker for beginnersdocker run in depth

docker run in depth

Detach, names, ports, and tags.

Beginner12 min · lesson 6 of 16
In plain terms
docker run with flags is like ordering a coffee with options: the size and blend (the image and tag), a name on the cup so you can find it (--name), and a to-go lid so you can carry it outside (-p, publishing a port to the host).

Of every Docker command, docker run is the one you will wear out. It accepts a frankly silly number of options, but five of them cover almost everything you do in a normal week: -d, --name, -p, -e, and the image name you park at the end. Two words first, because the rest of this rests on them. An image is a read-only template. It is a frozen meal sealed in its box: you cannot change what is inside, you heat it up. A container is one running copy of that image, the meal actually cooking in the oven. docker run takes an image and starts a container from it. One image, as many containers as you want.

terminal
$ docker run -d --name web -p 8080:80 nginx:1.27
Unable to find image 'nginx:1.27' locally
1.27: Pulling from library/nginx
a2abf6c4d29d: Pull complete
e7f9e5e6f1a1: Pull complete
Digest: sha256:2fd9a1c0b7e4d3f6a8b2c5e9d0f1a3b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2
Status: Downloaded newer image for nginx:1.27
c3f279d17e0a5f8b6a1d4e9c2b7a0f3e5d8c1b6a4f2e9d0c7b3a1f6e4d2c9b8a

Read that line right to left. nginx:1.27 is the image (nginx is a very widely used web server), and the bit after the colon, 1.27, is its tag. A tag is a version label stuck on an image, like the edition printed on the spine of a book. Leave the tag off and Docker fills in latest for you. That word sounds like a promise. It is not. It means whatever the publisher stamped as latest most recently, and that can move while you sleep. When you care which version runs, write a real number. Keep going left. --name web hands the container a label you chose, and if you skip it Docker invents one like gifted_bell. Then -p 8080:80 publishes a port. A port is a numbered door on a machine, and this wires door 8080 on your computer to door 80 inside the container. Last, -d runs the container detached, which means in the background.

That -d deserves a second look. Leave it off and your terminal is tied to the container like a dog on a leash. Logs scroll past live, and your prompt does not come back until the container stops, usually because you hit Ctrl+C. Handy when you are watching a short job run to the end. Maddening for a web server you want humming quietly for hours. So for anything long-lived, reach for -d. You already saw the payoff above: Docker printed the container's long ID and gave you your prompt straight back. Detached does not mean blind, though. docker logs web prints everything that container has written so far, any time you feel like looking.

What one docker run actually does
1Look locally
is nginx:1.27 already here?
2Pull if missing
download it from the registry
3Create container
a fresh writable copy of the image
4Wire it up
apply --name, -p ports, -e env vars
5Start the process
-d hands your prompt back
A registry is an app store for images. Docker Hub is the default one, and it is where this nginx image came from.

Did it actually start?

Your prompt came back, which proves Docker accepted the command. It does not prove the server is answering. Two quick checks settle it. First, docker ps lists the containers running right now, and yours turns up under the name you picked with its port mapping spelled out. Second, knock on the door yourself with curl, a small tool that fires a web request straight from the terminal. A 200 response code is the server saying hello, all good here. No curl on your machine? Opening http://localhost:8080 in a browser tells you exactly the same thing.

terminal
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c3f279d17e0a nginx:1.27 "/docker-entrypoint.…" 6 seconds ago Up 4 seconds 0.0.0.0:8080->80/tcp web
$ curl -I localhost:8080
HTTP/1.1 200 OK
Server: nginx/1.27.4
Content-Type: text/html
Content-Length: 615

-e: passing settings in at startup

Most images can be adjusted as they boot without rebuilding anything, and environment variables (named settings the operating system hands to a program when it starts) are the usual way in. They behave like sticky notes you slap on the program on its way out the door: it reads the ones it recognises and ignores the rest. The official Postgres database image is the famous case. It flatly refuses to start until you hand it a password this way. Each setting goes in as -e NAME=value, and you can stack up as many as the image understands. Want to confirm a value landed? docker exec runs a command inside a container that is already running, so you can ask the container to read its own sticky notes back to you.

terminal
$ docker run -d --name db \
-e POSTGRES_PASSWORD=devsecret \
-e POSTGRES_DB=payments \
postgres:16
5e1c9a7b3f2d8c604a19e7d2b8f3c1a6d904e2f7b1c8a3d6e0f4b7a2c9d1e6f30
$ docker exec db env | grep POSTGRES_DB
POSTGRES_DB=payments

--rm: clean up on the way out

Every container you start sticks around after it stops. It does not vanish. It sits there in a stopped state, still holding its name and a little disk space. Plain docker ps shows only what is running, so add -a (short for all) and the stopped ones appear too. Fire off a dozen throwaway commands in an afternoon and you will have a dozen leftovers by dinner. --rm settles that: the container deletes itself the instant it exits. Perfect for one-off jobs you never want to keep, like running a single command inside a small image to see what it prints.

terminal
$ docker run --rm --name ping alpine:3.20 echo "hello from a throwaway"
hello from a throwaway
$ docker ps -a --filter name=ping
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

When the port is already taken

Here is the wall almost everyone walks into in week one. You have a container serving on port 8080. You start a second one on that same 8080, and this time docker run refuses. One door fits one occupant. The error that comes back is long and a little alarming, so it helps to know who is talking: the Docker daemon, the background service that does the real work of running your containers every time you type a docker command. Skip to the very last line, which is the only part that matters: bind: address already in use.

terminal
$ docker run -d --name web2 -p 8080:80 nginx:1.27
docker: Error response from daemon: driver failed programming external
connectivity on endpoint web2 (a1b2c3d4e5f6): Error starting userland proxy:
listen tcp4 0.0.0.0:8080: bind: address already in use.
$ docker rm web2
web2
$ docker run -d --name web2 -p 8081:80 nginx:1.27
b7d2e9f0c1a3d5e8f4a0b6c2d9e3f7a1c5b8d4e6f0a2c3b9d7e1f5a8c4b0d6e2
A name belongs to one container at a time
See the docker rm web2 sitting in the middle there? The failed run still created a container, and that dead container kept hold of the name, so a second attempt at web2 fails for a brand new reason: The container name "/web2" is already in use. Nearly everybody gets caught by this once. Remove the old container first, the way we did, or start containers with --rm so they never linger to squat on the name. The port clash has the easier fix of the two: change the left-hand host number from 8080 to 8081, and both servers run side by side.

docker run ships with dozens of flags and you will reach for about six. -d frees your terminal. --name gives you a handle to type instead of a long ID. -p opens a door from the host into the container. -e passes settings in. --rm takes out the rubbish. The tag decides which version of the image you get. One layout rule holds them together: flags go before the image name, and anything you type after the image name is the command that runs inside the container. Get comfortable with that shape before you go hunting for the rare options.

Port publishing catches everyone exactly once. -p 8080:80 reads as "host port 8080 forwards to container port 80". The number on the right has to match the port the program inside genuinely listens on. nginx listens on 80 by default. Plenty of Node.js apps (JavaScript running on a server rather than in a browser) listen on 3000 instead. Get that right-hand number wrong and the symptom is misleading: docker ps swears the container is up, while curl sits there and hangs. Check the image's documentation for its listening port.

Detached containers still need watching. If the main process inside dies, a -d container drops off docker ps without a sound, because nothing was attached to tell you. Worth making a habit: after every detached run, check docker ps. If your container is not listed, run docker ps -a to find the stopped one, then docker logs to read its last words. Detaching does not make a container immortal. It only means your terminal is not wired to its output.

Names have to be unique on a single host machine. Restart a tutorial without clearing the old container and you will meet: Conflict. The container name is already in use. Treat that message as a favour. It is stopping you from quietly stacking up duplicates you have already forgotten about. Either remove the old container with docker rm -f oldname, or pick a different name for the new one.

Try this

Start nginx detached with a clear name and a published port, prove it is answering with curl, then crash into the port clash on purpose so the error looks familiar the day it happens for real.

terminal
docker run -d --name webdemo -p 8080:80 nginx:1.27-alpine
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/
docker run -d --name webdemo2 -p 8080:80 nginx:1.27-alpine || true
docker rm -f webdemo
output
<container-id>
200
docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint webdemo2 (...): Bind for 0.0.0.0:8080 failed: port is already allocated

Takeaway

Day to day, docker run comes down to five moves: detach, name, publish, set env, pin a tag. The one that bites hardest is publishing. Docker will happily wire host port 8080 to whatever container port you name, including a port nothing is listening on, and then hand you a container that looks perfectly healthy in docker ps while curl hangs forever. Find out what the process inside listens on before you type -p.

Quick check
01One container already holds host port 8080. You fire off docker run -d -p 8080:80 nginx:1.27 a second time and it dies with bind: address already in use. What gets a second web server running fastest?
Correct — One door, one occupant. Give the second container a free host port and both stay reachable.
Incorrect — The image was never the problem. The fight is over the host port, so pulling it again changes nothing.
Incorrect — --rm only removes a container after it exits. It has nothing to say about a port that a running container is still holding.
Incorrect — That takes your first container down with it. Picking a free host port for the second one is the real fix.
02The lesson tells you to write a real tag like nginx:1.27 instead of leaving the tag off. If you leave it off and Docker fills in latest, what does latest actually point at?
Incorrect — Docker certifies nothing here. The word latest carries no promise about stability or safety.
Correct — Yes. It is a label that moves, so a rebuild next month can quietly pull a different image.
Incorrect — The publisher sets the tag, not whatever image happens to be cached locally.
Incorrect — Tag names say nothing about size. latest is about when something was tagged, not how many bytes it weighs.
03docker run -d --name web -p 8080:80 nginx:1.27 starts fine. Later you try docker run -d --name web -p 9090:80 nginx:1.27 and Docker replies: The container name "/web" is already in use. The host ports are different, so what is really wrong?
Incorrect — The error names the container name, not a port, and 9090 was sitting free.
Incorrect — One image can start as many containers as you like, so that is not the limit here.
Correct — Give the new container a different name, or remove the first one to free web up.
Incorrect — Pulling again changes nothing. The existing container is holding the name, not the image.

Related