Running and Managing Docker Containers
Run a throwaway container, then a long-running web server, and drive it through its whole lifecycle - start, stop, restart, remove. Every command is paired with the output it actually produced, including the three errors you are most likely to hit.
Containers and Images Guide 5 of 46 Beginner
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- Shellbash
- Architectureamd64
- TimeAbout 12 min
- Reviewed20 August 2026
Tested on the versions above. Container IDs, image digests, layer IDs and timings are different on every run and every host. Match the shape of the output, not the exact strings.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- Docker Engine installed and running.
systemctl is-active dockershould printactive. - Your user in the
dockergroup, or prefix every command withsudo. - Outbound HTTPS to registry-1.docker.io so images can be pulled.
- TCP port 8080 free on the host.
ss -ltn | grep 8080should print nothing.
-
Run a container that proves the installation works
hello-worldis the smallest useful test there is: it pulls an image, creates a container, prints a message and exits. If this works, the client, the daemon, the registry connection and the runtime are all functioning. Note the first line - the image is not on the machine yet, so Docker fetches it before running anything.bash Example session docker run hello-worldUnable to find image 'hello-world:latest' locallylatest: Pulling from library/hello-world4f55086f7dd0: Pull completeDigest: sha256:5dd0d3e6e255913fc30f90b9f2b1d359cc2cbdb48090cc4b65f1676e203243ccStatus: Downloaded newer image for hello-world:latest Hello from Docker!This message shows that your installation appears to be working correctly.Expected resultThe pull lines, then the greeting. The container exits immediately afterwards - that is correct, not a crash.
Success condition
Hello from Docker!appears. If you instead see a permission error on /var/run/docker.sock, your user is not in the docker group. -
Run something that keeps running
hello-worldexits, so there is nothing to manage. A web server is the opposite: it stays up until told otherwise.-ddetaches so you get your prompt back,--namegives the container a stable handle instead of a random one, and-p 8080:80publishes container port 80 on host port 8080. The long hex string returned is the full container ID.bash Example session docker run -d --name cg-web -p 8080:80 nginx:alpineUnable to find image 'nginx:alpine' locallyalpine: Pulling from library/nginxfb597529c916: Pull completed94291c26261: Pull completeDigest: sha256:db35bfc6b2951e7f8a72db5db120288c127ffaeeb4a6d4b95a26fead017d5913Status: Downloaded newer image for nginx:alpinee7cca8cbc07d53df511ddca22b6a143f300148e6937875be0ccae52f429f580cExpected resultA 64-character container ID on the last line and your prompt back immediately.
Success conditionYou get a container ID rather than an error.
-dmeans no application output appears here - that is expected, and step 5 shows where it went. -
See what is running
docker pslists running containers only. The--formatflag turns the very wide default table into just the columns worth reading. Note the PORTS column: it shows the host side first, so0.0.0.0:8080->80/tcpmeans host 8080 forwards to container 80.bash Example session docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"NAMES IMAGE STATUS PORTScg-web nginx:alpine Up Less than a second 0.0.0.0:8080->80/tcp, [::]:8080->80/tcpExpected resultOne row for cg-web with status starting
Upand both an IPv4 and IPv6 port mapping.Success conditionStatus begins with
Up. If the container is missing here, rundocker ps -a- it may have started and exited, and the logs in step 5 will say why. -
Prove the service actually answers
Upmeans the container process is alive. It does not mean the application inside is ready to serve. This is the single most common source of confusion for beginners, so verify from outside rather than trusting the status column.bash curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8080HTTP 200Expected resultHTTP 200.
Success conditionHTTP 200. If you get
HTTP 000with exit code 7, nothing is listening yet - see the first troubleshooting entry, this is a race not a failure. -
Read the container's logs
A detached container's stdout and stderr go to the log, not your terminal. This is where a container that exited immediately explains itself. The last line here is the request curl just made, which is a neat way to confirm traffic is reaching the container rather than being answered by something else on the host.
bash Example session docker logs cg-web/docker-entrypoint.sh: Configuration complete; ready for start up2026/08/20 05:31:09 [notice] 1#1: using the "epoll" event method2026/08/20 05:31:09 [notice] 1#1: nginx/1.31.42026/08/20 05:31:09 [notice] 1#1: start worker processes172.17.0.1 - - [20/Aug/2026:05:31:09 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.18.0" "-"Expected resultnginx startup notices, then an access-log line for your curl request. Output is abridged here - the real log also lists the entrypoint scripts it ran.
Success conditionYou can see the GET request you made. Add
-fto follow the log live, and--tail 20to see only the end. -
Stop, start and restart
Stopping sends SIGTERM and waits before forcing the issue, so the process can shut down cleanly. A stopped container still exists - its filesystem, configuration and name are all intact - which is why starting it again is instant and needs no arguments.
bash docker stop cg-webcg-webdocker ps -a --format "{{.Names}} {{.Status}}"cg-web Exited (0) Less than a second agodocker start cg-webcg-webdocker restart cg-webcg-webExpected resultEach command echoes the container name back. After stop, the status is
Exited (0)- exit code zero means a clean shutdown.Success condition
Exited (0)after stop, and the container appears indocker psagain after start. A non-zero exit code means the process died rather than shut down. -
Remove the container
Removing is permanent: the writable layer goes with it, so anything written inside the container and not stored in a volume is gone. Docker deliberately refuses to remove a running container unless you force it - that guard is doing its job, not getting in the way.
bash docker rm cg-webError response from daemon: cannot remove container "cg-web": container is running: stop the container before removing or force removedocker rm -f cg-webcg-webExpected resultThe first command fails with exit code 1.
-fstops and removes in one step and echoes the name.Success condition
docker ps -ano longer lists cg-web. Removing a container does not remove its image - the nginx:alpine image stays cached for next time.
Troubleshooting
curl returns HTTP 000 and exit code 7 immediately after starting the container
Why: The container reached
Upbefore the process inside finished binding to its port.Updescribes the container, not the readiness of the application in it.Fix:Wait a moment and retry, or poll until it answers. In scripts, never assume
docker runreturning means the service is ready - test the endpoint, or give the container a HEALTHCHECK and wait forhealthy.bash curl -s -o /dev/null -w "HTTP %{http_code}\n" --max-time 3 http://localhost:8080HTTP 000# exit code 7 - could not connectcurl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8080HTTP 200Conflict. The container name "/cg-web" is already in use
Why: Container names are unique per host, and a stopped container still holds its name. Re-running the same
docker run --namecommand finds the old container still present.Fix:Remove the old container first, or give the new one a different name. The error helpfully includes the ID of the container holding the name.
bash Example session docker run -d --name cg-web -p 8080:80 nginx:alpinedocker: Error response from daemon: Conflict. The container name "/cg-web" is already in use by container "e7cca8cbc07d...". You have to remove (or rename) that container to be able to reuse that name.docker rm -f cg-webcg-webBind for 0.0.0.0:8080 failed: port is already allocated
Why: Another process - often an earlier container you forgot about - already holds host port 8080.
Fix:Find what is using it and either stop it or publish on a different host port.
-p 8081:80changes only the host side; the container still listens on 80.bash docker ps --filter publish=8080 --format "{{.Names}} {{.Ports}}"ss -ltnp | grep :8080# then either stop the holder, or republish:docker run -d --name cg-web2 -p 8081:80 nginx:alpine