docker run in depth
Names, detached runs, ports, environment, cleanup and restart policies.
docker ps says the container is Up, the PORTS column shows 127.0.0.1:8083->8080/tcp, and curl answers Recv failure: Connection reset by peer. The container is healthy. The command that started it published a port the program inside never listens on. Most docker run mistakes are of this kind: the flags were accepted, and they did something other than what you meant. This lesson goes through the flags you will use every day and shows what each one does, including when it goes wrong.
The shape of the command
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]. Options for Docker go before the image name. Everything after the image name is the command to run inside the container, replacing the image's default command. Docker does not read past the image name, which this run makes obvious:
--name lab-x -p 9999:80 came after the image, so Docker passed it to echo as plain arguments: no name was set and no port was published. When a flag "does nothing", check which side of the image name it is on.
-d and --name
Without -d, docker run stays attached to the container: its output streams to your terminal and the command returns when the process exits. That suits a one-off command. For a service, -d (detach) starts the container in the background and prints its ID. --name gives the container a name you choose; without it Docker generates one such as eager_lovelace. A name is unique on the daemon, as "Images, containers and the core commands" showed, so scripts that run the same container twice need to remove the old one first.
-p publishes a container port
A container on the default network has its own IP address on a private bridge inside the host, and nothing outside the host can reach it. -p publishes a port: -p 127.0.0.1:8080:80 means "accept connections on the host's 127.0.0.1, port 8080, and forward them to port 80 in the container". The right-hand number must be the port the program inside actually listens on; nginx listens on 80.
curl printed the HTTP status and the Server header of the response: the request went to the host port and was answered by nginx 1.30.5 inside the container. The address in front of the ports matters. Leave it out and Docker publishes on every address of the host, IPv4 and IPv6:
0.0.0.0:8081 and [::]:8081 mean any machine that can reach this host can reach the container. On a laptop or a shared server, publish on 127.0.0.1 unless other machines really need the service. Docker writes its own firewall rules for published ports, and they take effect before rules you may have added with tools such as ufw, so a host firewall does not necessarily protect a port published on all addresses. "Publishing ports and the packet path" in Docker in depth explains the rules and how to restrict them.
When the host port is taken
One host address and port can be published once. Try to publish 127.0.0.1:8080 again while lab-web holds it:
Read the error from the end: Bind for 127.0.0.1:8080 failed: port is already allocated means another container already publishes that address and port. Notice the container ID printed above the error. Docker created lab-web2, failed while setting up its networking, and left it in the Created state, holding the name. Remove it, then run it again with a free host port (8082 here). Only the left-hand number changes; the container still listens on 80.
If the port is held by a program outside Docker, the message ends differently. Start a small Python web server on 127.0.0.1:8090 (Python 3 is installed in the lab VM); nohup and the redirects keep it running quietly in the background, and ss -ltn confirms that something now listens on the port:
Now publish the same address and port from a container:
address already in use comes from the kernel refusing the bind, so look for the process with sudo ss -ltnp 'sport = :8090' (with sudo, -p can show processes of every user). port is already allocated comes from Docker's own bookkeeping, so look at docker ps. Stop the Python server again; the pattern http[.]server matches the server's command line but not the pkill command itself:
When nothing listens on the container port
This container publishes host port 8083 to container port 8080, where nginx is not listening:
The connection to the host port succeeded, the forward into the container found nothing on 8080, and the client got a reset. The container is fine and docker ps shows it as Up. Find the real port in the image's documentation, or in the ports the image declares (the 80/tcp that docker ps shows for an unpublished nginx), and fix the right-hand number.
-e sets environment variables
Most images read their settings from environment variables, so the same image runs with different configuration per environment. Each -e NAME=value adds one:
Some images refuse to start without certain variables. The official postgres image, for example, exits unless you set POSTGRES_PASSWORD (or POSTGRES_PASSWORD_FILE, which reads it from a file). Environment variables are visible to anyone who can run docker inspect on the container, so they are not a safe place for production secrets. "Environment variables and configuration" covers --env-file and its rules, and Advanced container security covers secrets.
--rm for throwaway containers
Every container you start stays on disk after it exits, unless you pass --rm, which removes it when the process ends:
Nothing named lab-once is left. Use --rm for one-off commands and experiments. It cannot be combined with a restart policy, as the last command shows: a container that is deleted on exit cannot also be restarted on exit.
--restart policies
A restart policy tells the daemon what to do when the container's process exits. no is the default. on-failure[:N] restarts after a non-zero exit, at most N times. always restarts whatever the exit code and also starts the container when the daemon starts, for example after a reboot. unless-stopped behaves like always, except that a container you stopped yourself stays stopped across daemon restarts. A container that always exits with status 1 shows the policy at work:
The daemon started it once and restarted it three times, then gave up, which is why the log has four started lines and the container is exited with status 1. Between attempts the daemon waits, starting at 100 ms and doubling each time, so a crashing container does not spin the CPU. A restart policy keeps a service running through crashes and reboots; it does not fix the crash. If the restart count keeps climbing, read docker logs. Compose files set the same policies with the restart: key.
docker run -d nginx:1.30-alpine -p 8080:80 --name web. What happens?-p 8080:80 on a cloud VM is reachable from the internet although the VM's host firewall allows only SSH. Which change keeps it local to the VM?0.0.0.0 and [::]), and its own firewall rules apply before many host firewall rules.docker run -d --name api -p 127.0.0.1:9000:9000 my-api:1 prints a container ID and then failed to bind host port 127.0.0.1:9000/tcp: address already in use. What is the best next step?port is already allocated.api is now taken by the failed container.api that must be removed.Try this
Work through “--restart policies” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.
Takeaway
If you keep one thing from docker run in depth, keep “--restart policies”. Decide now which check you will run when this shows up on a live system, and write it somewhere your team will find it.