Managing Containers & Logs

Docker & DevOps · Lesson 7 of 26 7 min read Updated

Starting a container is the easy part. Most of your time with Docker goes into looking after containers that are already running: reading their logs when something goes wrong, opening a shell inside to check a file, seeing how much memory they use, and cleaning up old ones.

This lesson is your toolbox for those jobs. The examples use two containers from earlier lessons: chai-site (Nginx) and school-db (MySQL).

Filtering and formatting docker ps

On a busy machine, docker ps prints long lines. Two options make it readable. --filter shows only matching containers, and --format chooses the columns, using Go template names such as .Names, .Status, .Ports and .Image:

Terminal
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker ps -a --filter status=exited --format '{{.Names}}'
docker ps -q
Output
NAMES       STATUS       PORTS
chai-site   Up 3 hours   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
school-db   Up 3 hours   127.0.0.1:3307->3306/tcp, 33060/tcp

The word table at the start adds a header row, and \t separates columns. The second command prints only the names of stopped containers (nothing here, because both are running). The -q (quiet) option prints only container IDs, which is useful in scripts, for example docker stop $(docker ps -q) stops everything that is running.

Reading logs

A container's logs are everything its main process writes to standard output (stdout) and standard error (stderr). Docker captures both and stores them for you. This is why well-built images make their apps print logs to the terminal instead of writing log files inside the container.

Terminal
docker logs chai-site
docker logs -f chai-site
docker logs --tail 20 --since 10m -t school-db
  • With no options, you get the whole log so far.
  • -f (follow) keeps the command open and prints new lines as they arrive, like tail -f. Press Ctrl+C to stop following; the container keeps running.
  • --tail 20 shows only the last 20 lines, --since 10m only the last ten minutes, and -t adds a timestamp to every line.

After you open http://localhost:8080 once, the Nginx log shows your request:

Output
172.17.0.1 - - [30/Oct/2026:10:02:11 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"

The request came from 172.17.0.1, the address of Docker's default network gateway, because Docker forwards traffic from the published port. Status 200 means success, and 615 is the size of the welcome page in bytes.

Logs are kept until the container is removed. On a server they can grow very large, so you should turn on log rotation; Monitoring & Logging Basics shows how.

Running commands inside a container with docker exec

docker exec runs an extra command inside a container that is already running. Use it to look around, check a configuration file or test a connection:

Terminal
docker exec chai-site ls /usr/share/nginx/html
docker exec -it chai-site sh
docker exec -it school-db mysql -u kabir -p school
Output
50x.html
index.html

The second command opens a shell. Alpine-based images have sh but not bash. The third starts the MySQL client inside the database container and asks for Kabir's password.

Remember the difference: docker run creates a new container, docker exec works inside an existing one. When you exit an exec shell, the container keeps running, because the shell was not its main process.

Inspecting a container

docker inspect prints a container's full configuration and state as JSON: its image, command, environment variables, mounts, network addresses, restart count and more. --format picks single values:

Terminal
docker inspect --format '{{.State.Status}}, restarts: {{.RestartCount}}' school-db
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' chai-site
Output
running, restarts: 0
172.17.0.2

A high restart count tells you a container keeps crashing and coming back because of its restart policy. That is a sign to read its logs.

Copying files and watching resources

docker cp copies files between your computer and a container, in either direction:

Terminal
docker cp ./index.html chai-site:/usr/share/nginx/html/index.html
docker cp school-db:/etc/my.cnf ~/my.cnf
docker stats --no-stream
Output
Successfully copied 2.05kB to chai-site:/usr/share/nginx/html/index.html
Successfully copied 3.07kB to /home/aarav/my.cnf
CONTAINER ID   NAME        CPU %     MEM USAGE / LIMIT    MEM %     NET I/O           BLOCK I/O        PIDS
8c41d07e2b95   chai-site   0.00%     9.8MiB / 7.66GiB     0.12%     3.41kB / 2.05kB   0B / 8.19kB      9
d24a6f1c8e30   school-db   0.62%     412.3MiB / 7.66GiB   5.26%     1.83kB / 126B     38.9MB / 221MB   37

docker stats shows live CPU, memory, network and disk use for each container. Without --no-stream it refreshes every second, like the top command. MySQL using about 400 MB while Nginx uses under 10 MB is normal. docker top chai-site lists the processes inside one container.

Restarting, renaming and cleaning up

Terminal
docker restart chai-site
docker rename chai-site chai-site-old
docker container prune
docker system prune
  • docker restart is a stop followed by a start.
  • docker rename changes a container's name without recreating it.
  • docker container prune removes all stopped containers.
  • docker system prune removes stopped containers, unused networks, dangling images and the build cache. Add --volumes to remove unused volumes too, but only if you are sure no data you need lives there.

Common mistakes

  • Looking for log files inside the container. Most official images send logs to stdout and stderr. Use docker logs.
  • Using docker run when you meant docker exec. run makes a new container from the image; it does not enter the one that is already running.
  • "executable file not found" with docker exec ... bash. Small images often have only sh. Try sh instead.
  • Running docker system prune --volumes on a server without thinking. It can delete database volumes that belong to stopped containers.

Key points

  • docker ps --filter and --format turn long listings into exactly what you need; -q prints IDs for scripts.
  • docker logs shows a container's stdout and stderr; -f, --tail, --since and -t narrow it down.
  • docker exec -it NAME sh opens a shell in a running container without affecting its main process.
  • docker inspect --format reads state, restart counts, IP addresses and settings.
  • docker cp, docker stats and docker top copy files and show resource use; prune commands clean up.

Exercise

Start an Nginx container called exam-results on port 8090. Open it in your browser twice, then print only the last two log lines with timestamps. Next, use docker exec to print the first line of /etc/os-release inside it. Finally, print the container's status and restart count with one docker inspect command.

Show solution
Terminal
docker run -d --name exam-results -p 8090:80 nginx:alpine
docker logs --tail 2 -t exam-results
docker exec exam-results head -n 1 /etc/os-release
docker inspect --format '{{.State.Status}}, restarts: {{.RestartCount}}' exam-results
Output
NAME="Alpine Linux"
running, restarts: 0

--tail 2 -t shows the two newest log lines with Docker's timestamps. They may include the browser's automatic request for /favicon.ico, which Nginx answers with a 404. The nginx:alpine image is built on Alpine, which the first line of /etc/os-release confirms.

Quick Quiz

1. Where does docker logs get its text from?

2. What is the difference between docker run and docker exec?

3. Which command follows a container's log output live?

4. A container's restart count keeps going up. What should you check first?

Learn Docker & DevOps with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call