CertGrid CertGrid
Hands-on Lab·Docker

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

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.

One Docker host is all this guide needs. Nothing here depends on a second machine, and the hardware above is modest on purpose - a 2 core, 4 GB VM runs everything in this path.
Server NameIP AddressOSRolesCPURAMHDD
DOCKER01192.168.0.21Ubuntu 26.04 LTSDocker Host2 Core4 GB50 GB

Before you start

  1. Run a container that proves the installation works

    hello-world is 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 conditionHello from Docker! appears. If you instead see a permission error on /var/run/docker.sock, your user is not in the docker group.

  2. Run something that keeps running

    hello-world exits, so there is nothing to manage. A web server is the opposite: it stays up until told otherwise. -d detaches so you get your prompt back, --name gives the container a stable handle instead of a random one, and -p 8080:80 publishes 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:alpinee7cca8cbc07d53df511ddca22b6a143f300148e6937875be0ccae52f429f580c

    Expected resultA 64-character container ID on the last line and your prompt back immediately.

    Success conditionYou get a container ID rather than an error. -d means no application output appears here - that is expected, and step 5 shows where it went.

  3. See what is running

    docker ps lists running containers only. The --format flag turns the very wide default table into just the columns worth reading. Note the PORTS column: it shows the host side first, so 0.0.0.0:8080->80/tcp means 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/tcp

    Expected resultOne row for cg-web with status starting Up and both an IPv4 and IPv6 port mapping.

    Success conditionStatus begins with Up. If the container is missing here, run docker ps -a - it may have started and exited, and the logs in step 5 will say why.

  4. Prove the service actually answers

    Up means 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 200

    Expected resultHTTP 200.

    Success conditionHTTP 200. If you get HTTP 000 with exit code 7, nothing is listening yet - see the first troubleshooting entry, this is a race not a failure.

  5. 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 -f to follow the log live, and --tail 20 to see only the end.

  6. 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-web

    Expected resultEach command echoes the container name back. After stop, the status is Exited (0) - exit code zero means a clean shutdown.

    Success conditionExited (0) after stop, and the container appears in docker ps again after start. A non-zero exit code means the process died rather than shut down.

  7. 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-web

    Expected resultThe first command fails with exit code 1. -f stops and removes in one step and echoes the name.

    Success conditiondocker ps -a no longer lists cg-web. Removing a container does not remove its image - the nginx:alpine image stays cached for next time.

Troubleshooting

Official sources