Docker Logs and Exec: Inspect a Running Container
When a containerised application misbehaves, two commands answer most questions: docker logs ("what did the app print?") and docker exec ("let me look inside"). In this lab you'll use both on the Nginx container from the Nginx lab: read its access and error logs, open a shell inside it, explore its files, change the web page while it runs, and see why those changes don't last.
Make sure the container is running (docker ps should show my-nginx). If not: docker run -d --name my-nginx -p 8080:80 nginx. All outputs below are from a real run on Docker Engine 29.
Part 1: docker logs
Where container logs come from
Docker captures everything the container's main process writes to standard output (stdout) and standard error (stderr). The official Nginx image links its access.log to stdout and its error.log to stderr, so every request appears in docker logs. Your own apps should do the same: log to the console, not to files inside the container.
To create some log entries, open http://localhost:8080 a few times, and once a page that doesn't exist, such as http://localhost:8080/missing-page.
Show the last lines: --tail
docker logs --tail 4 my-nginx
The first three lines are the access log (stdout): client IP, time, request, status code and size. The last is the error log (stderr), explaining the 404: the file doesn't exist in /usr/share/nginx/html. The client IP 172.17.0.1 is Docker's bridge gateway, because the request came in through the published port.
Add timestamps: -t
docker logs -t --tail 2 my-nginx
-t adds the exact time (UTC) at which Docker received each line. Useful when the application's own log lines have no timestamp.
Follow live: -f
docker logs -f --tail 1 my-nginx
The command keeps running and prints new lines as they arrive, like tail -f. While it was running, we opened http://localhost:8080/?from=follow, and the new line appeared immediately:
Press Ctrl+C to stop following. This only stops the logs command; the container keeps running.
Logs by time: --since and --until
docker logs --since 10m my-nginx # last 10 minutes
docker logs --since 2026-11-04T13:00:00 my-nginx
docker logs --since 1h --until 30m my-nginx
| Option | Use it to |
|---|---|
--tail N | show only the last N lines (the full log can be huge) |
-f | follow new lines live |
-t | add Docker's timestamp to each line |
--since / --until | limit by time: 10m, 2h or an exact date-time |
Logs also work on a stopped container. When a container exits right after starting, docker logs <name> is the first thing to run: the error message is usually there.
Part 2: docker exec
docker exec runs an extra command inside a running container, next to its main process. It doesn't restart or change the main process.
Run one command
docker exec my-nginx nginx -v
docker exec my-nginx head -3 /etc/os-release
The Nginx image is built on Debian 13. (The nginx:alpine variant used in the introduction is built on Alpine Linux instead; the tag decides the base.)
Open an interactive shell: -it
docker exec -it my-nginx sh
-i(interactive) keeps standard input open so you can type.-tallocates a terminal (TTY) so you get a prompt and proper output.shis the program to run. Many images also havebash; minimal ones may have onlysh, or no shell at all.
You're now inside the container. Here is the real session (the commands we typed, followed by their output):
whoami
hostname
pwd
cd /usr/share/nginx/html
ls -l
head -4 index.html
ps aux
- whoami: you are
rootinside this container. - hostname: the container's hostname is its short container ID.
- ls -l: the website is just two files.
index.htmlis the welcome page you saw on localhost:8080. - ps: not found: images contain only what the app needs. Even common tools like
ps,curlorviare often missing. That keeps images small and safer.
Without ps, you can still see the main process (PID 1) through /proc:
cat /proc/1/cmdline | tr "\0" " "
Type exit to leave the shell. The container keeps running, because the shell was only an extra process.
Inspect the Nginx configuration
docker exec my-nginx cat /etc/nginx/conf.d/default.conf
This explains everything you've seen: Nginx listens on port 80 (which is why we published -p 8080:80) and serves files from /usr/share/nginx/html.
Change the web page while the container runs
docker exec my-nginx sh -c "echo '<h1>Hello from MakeMyCodes inside Docker</h1>' > /usr/share/nginx/html/index.html"
Refresh http://localhost:8080:
No restart was needed: Nginx reads the file on every request.
Part 3: copy files with docker cp
docker cp copies files between your computer and a container (running or stopped). Copy Nginx's main configuration out:
docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf
And copy a new page in:
docker cp ./about.html my-nginx:/usr/share/nginx/html/about.html
Open http://localhost:8080/about.html:
Part 4: see what changed, and why it doesn't last
docker diff lists the changes in the container's writable layer compared with the image (A = added, C = changed, D = deleted):
docker diff my-nginx
Our two page changes are there, together with files Nginx itself created at start-up. Now remove the container and create a fresh one from the same image:
docker rm -f my-nginx
docker run -d --name my-nginx -p 8080:80 nginx
The original welcome page is back and about.html is gone. Changes made with exec or cp live only in that container's writable layer, and docker rm deleted it. That's why:
- exec is for debugging and inspecting, not for deploying changes.
- Application files belong in the image (a Dockerfile
COPY, as in the next labs). - Data that must survive belongs in a volume or a bind mount.
Interview questions
What is the difference between docker exec and docker run?
docker run creates and starts a new container from an image. docker exec runs an additional command inside an existing, running container.
What do -i and -t mean in docker exec -it?
-i keeps standard input open (interactive); -t allocates a pseudo-terminal. Together they give you a usable shell prompt.
Where does docker logs get its data?
From the stdout and stderr of the container's main process, captured by Docker's logging driver (json-file by default). Files the app writes inside the container don't appear in docker logs.
Are changes made with docker exec kept?
They stay while the container exists (even across stop and start) but are lost when it's removed. Put permanent changes in the image or a volume.
Summary
docker logsshows stdout/stderr; use--tail,-f,-tand--since.docker exec -it <name> shopens a shell inside a running container;exitleaves without stopping it.docker cpcopies files in and out;docker diffshows what changed.- Changes inside a container disappear with
docker rm: use images and volumes for anything permanent.
Next lab: Build an ASP.NET Core Docker image (dotnet-day1:1.0) and run it on port 8081.