docker run in depth
Detach, names, ports, and tags.
docker run is the command you will use most. It has many flags; five cover most daily work: -d, --name, -p, -e, and the image name at the end. An image is a read-only template. A container is one running copy of that image. docker run starts a container from an image. One image, as many containers as you need.
$ docker run -d --name web -p 8080:80 nginx:1.27Unable to find image 'nginx:1.27' locally1.27: Pulling from library/nginxa2abf6c4d29d: Pull completee7f9e5e6f1a1: Pull completeDigest: sha256:2fd9a1c0b7e4d3f6a8b2c5e9d0f1a3b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2Status: Downloaded newer image for nginx:1.27c3f279d17e0a5f8b6a1d4e9c2b7a0f3e5d8c1b6a4f2e9d0c7b3a1f6e4d2c9b8a
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.
-d is easy to skip and then miss. Without it, the terminal stays attached: logs stream live, and the prompt returns only when the container stops (usually Ctrl+C). Useful for a short foreground job. Wrong for a web server you want in the background. With -d, Docker prints the container ID and returns the prompt. Detached is not silent: docker logs web still shows output.
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.
$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESc3f279d17e0a nginx:1.27 "/docker-entrypoint.…" 6 seconds ago Up 4 seconds 0.0.0.0:8080->80/tcp web$ curl -I localhost:8080HTTP/1.1 200 OKServer: nginx/1.27.4Content-Type: text/htmlContent-Length: 615
-e: passing settings in at startup
Most images take runtime settings without a rebuild. Environment variables are NAME=value pairs the process sees at start. The official Postgres image will not start without a password passed this way. Each setting is -e NAME=value; you can pass as many as the image documents. To confirm a value landed, docker exec runs a command inside the already-running container.
$ docker run -d --name db \-e POSTGRES_PASSWORD=devsecret \-e POSTGRES_DB=payments \postgres:165e1c9a7b3f2d8c604a19e7d2b8f3c1a6d904e2f7b1c8a3d6e0f4b7a2c9d1e6f30$ docker exec db env | grep POSTGRES_DBPOSTGRES_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.
$ docker run --rm --name ping alpine:3.20 echo "hello from a throwaway"hello from a throwaway$ docker ps -a --filter name=pingCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
When the port is already taken
This is the usual first port clash. A container is already published on 8080; a second docker run -p 8080:… is refused. One host port can be bound once. The message is long because it comes from the Docker daemon; the last line is the one that matters: bind: address already in use.
$ docker run -d --name web2 -p 8080:80 nginx:1.27docker: Error response from daemon: driver failed programming externalconnectivity on endpoint web2 (a1b2c3d4e5f6): Error starting userland proxy:listen tcp4 0.0.0.0:8080: bind: address already in use.$ docker rm web2web2$ docker run -d --name web2 -p 8081:80 nginx:1.27b7d2e9f0c1a3d5e8f4a0b6c2d9e3f7a1c5b8d4e6f0a2c3b9d7e1f5a8c4b0d6e2
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.
docker run -d --name webdemo -p 8080:80 nginx:1.27-alpinecurl -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 || truedocker rm -f webdemo
<container-id>200docker: 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.