Managing Containers & Logs
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:
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker ps -a --filter status=exited --format '{{.Names}}'
docker ps -qNAMES 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.
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, liketail -f. Press Ctrl+C to stop following; the container keeps running.--tail 20shows only the last 20 lines,--since 10monly the last ten minutes, and-tadds a timestamp to every line.
After you open http://localhost:8080 once, the Nginx log shows your request:
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:
docker exec chai-site ls /usr/share/nginx/html
docker exec -it chai-site sh
docker exec -it school-db mysql -u kabir -p school50x.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:
docker inspect --format '{{.State.Status}}, restarts: {{.RestartCount}}' school-db
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' chai-siterunning, 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:
docker cp ./index.html chai-site:/usr/share/nginx/html/index.html
docker cp school-db:/etc/my.cnf ~/my.cnf
docker stats --no-streamSuccessfully 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
docker restart chai-site
docker rename chai-site chai-site-old
docker container prune
docker system prunedocker restartis a stop followed by a start.docker renamechanges a container's name without recreating it.docker container pruneremoves all stopped containers.docker system pruneremoves stopped containers, unused networks, dangling images and the build cache. Add--volumesto 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 runwhen you meantdocker exec.runmakes 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 onlysh. Tryshinstead. - Running
docker system prune --volumeson a server without thinking. It can delete database volumes that belong to stopped containers.
Key points
docker ps --filterand--formatturn long listings into exactly what you need;-qprints IDs for scripts.docker logsshows a container's stdout and stderr;-f,--tail,--sinceand-tnarrow it down.docker exec -it NAME shopens a shell in a running container without affecting its main process.docker inspect --formatreads state, restart counts, IP addresses and settings.docker cp,docker statsanddocker topcopy 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
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-resultsNAME="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?
Docker captures the standard output and error streams of the main process.
2. What is the difference between docker run and docker exec?
Use exec to look inside a container that is already running.
3. Which command follows a container's log output live?
-f keeps printing new lines as they arrive until you press Ctrl+C.
4. A container's restart count keeps going up. What should you check first?
The restart policy brings it back, but the logs show why it keeps failing.
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
