| |

Docker 8 🐳 Container Exec, Logs, and Interactive Shells: docker exec, docker logs, docker attach

Once a container is running, the next question is how to observe and interact with it. A container is isolated by design, so you cannot simply open a terminal into it or read its output from the host. Docker provides three commands for this: docker logs retrieves the output a container has already produced, docker exec runs a new command inside a running container, and docker attach connects to the main process’s standard input and output. Each serves a different purpose, and confusing them leads to frustration—attaching to a container and pressing Ctrl+C kills the container, while docker exec gives you a shell that is independent of the main process.

The distinction between exec and attach is the source of most confusion. docker attach connects to PID 1, the container’s main process. Any input you type goes to that process, and signals you send (like Ctrl+C) go to it as well. docker exec starts a new process inside the container with its own stdin, stdout, and stderr. You can run a shell, a debugger, or a one-off command without affecting the main process. For interactive debugging, exec is almost always what you want; attach is for observing or interacting with a process that expects direct terminal input.

This chapter covers docker logs with follow and tail options, docker exec for one-off commands and interactive shells, docker attach and its risks, the difference between the three, and the patterns for debugging running containers.

Key point: docker logs reads the output a container has produced. docker exec runs a new command in a running container, including an interactive shell, without affecting the main process. docker attach connects to the main process’s stdin/stdout, and Ctrl+C sent through attach kills the container.


Why these commands exist

The observability problem. A container runs in isolation. Its output goes to a log stream that Docker captures, but the host does not see it unless you ask. docker logs retrieves that stream, with options to follow it live or show only the most recent lines.

The debugging problem. When a container misbehaves, you need to look inside it. Is the configuration file correct? Is the network reachable? Is the process running? docker exec lets you run commands inside the running container—cat, curl, ps, sh—to answer these questions without stopping the container or rebuilding the image.

The interaction problem. Some processes expect a terminal. A database CLI, a REPL, or an interactive installer needs stdin and stdout connected. docker exec -it provides an interactive terminal for a new process, and docker attach provides it for the main process.

The distinction problem. The difference between exec and attach is not obvious from the names. exec runs a new process; attach connects to the existing one. This distinction determines whether Ctrl+C kills the container (attach) or just the command you started (exec).

The persistence problem. Logs are stored by the logging driver. The default driver writes to JSON files on the host, and docker logs reads from there. If the container is removed, the logs are gone unless they were forwarded to a remote system or written to a volume.


a. docker logs: reading container output

docker logs retrieves the stdout and stderr of a container’s main process. This includes anything the process printed, as well as anything written to those streams by exec-ed processes if they inherit the streams (they do not by default).

docker logs web

The output is everything the container has produced since it started, in order. For a long-running container, this can be thousands of lines.

Follow mode (-f or --follow) streams new output as it is produced, like tail -f:

docker logs -f web

The command does not exit until the container stops or you interrupt it. This is the standard way to watch a container’s output live.

Tail (--tail N) shows only the last N lines:

docker logs --tail 100 web

Combined with -f, this shows the last 100 lines and then follows:

docker logs -f --tail 50 web

Timestamps (-t or --timestamps) prefixes each line with its timestamp:

docker logs -t web

The timestamp is in RFC 3339 format with nanosecond precision, which is useful when correlating logs across services.

Since and until (--since, --until) filter by time:

docker logs --since 10m web
docker logs --since 2026-01-01T00:00:00 web
docker logs --until 1h web

The time can be an absolute timestamp or a relative duration (10m, 1h, 30s). These flags are useful for narrowing a large log to a relevant window.

docker logs reads from the configured logging driver. With the default json-file driver, the logs are stored in /var/lib/docker/containers/<id>/<id>-json.log on the host. With other drivers (journald, syslog, fluentd), the logs may not be available through docker logs at all, depending on the driver’s capabilities.


b. docker exec: running commands in a running container

docker exec runs a new command inside a running container. The container must be running; exec fails on a stopped container.

docker exec web ls /usr/share/nginx/html

The command runs in the container’s namespace, with the container’s filesystem, network, and environment. It does not affect the main process.

Interactive shell (-it) is the most common use:

docker exec -it web /bin/bash

The -i keeps stdin open, and -t allocates a pseudo-TTY. You get a shell prompt inside the container, and you can run commands interactively. When you exit the shell (exit or Ctrl+D), the container keeps running; only the shell process ends.

If the image does not have bash, use sh:

docker exec -it web /bin/sh

Alpine-based images use ash or sh rather than bash.

Environment variables (-e) set variables for the exec’d command:

docker exec -e DEBUG=1 web env

Working directory (-w) sets the directory:

docker exec -w /app web ls

User (-u) runs the command as a specific user:

docker exec -u root web whoami

This is useful when the container runs as a non-root user but you need root for a debugging command.

Detached mode (-d) runs the command in the background:

docker exec -d web touch /tmp/marker

The command returns immediately, and the process runs inside the container.

docker exec is the standard tool for debugging running containers. Common uses include checking configuration files, testing network connectivity with curl or ping, inspecting processes with ps, and opening a shell to explore the filesystem.

A single exec command:

docker exec web cat /etc/nginx/nginx.conf

A pipeline:

docker exec web sh -c "ps aux | grep nginx"

When the command contains shell features (pipes, redirection, variables), wrap it in sh -c:

docker exec web sh -c 'echo $PATH'

Without the sh -c, the command is executed directly by the kernel, and shell features are not available.


c. docker attach: connecting to the main process

docker attach connects your terminal’s stdin, stdout, and stderr to the container’s main process (PID 1).

docker attach web

Any output the main process produces is shown in your terminal, and any input you type is sent to the process. This is useful for processes that expect interactive input, like a REPL or a shell running as PID 1.

The critical risk: attach connects you to PID 1, and signals you send are delivered to PID 1. Pressing Ctrl+C sends SIGINT to the main process, which usually terminates it. In most cases, this kills the container.

To detach from an attached container without killing it, use the escape sequence Ctrl+P followed by Ctrl+Q. This detaches the terminal without sending a signal to the process.

# Detach without stopping: Ctrl+P then Ctrl+Q

Because of the Ctrl+C risk, attach is less commonly used than exec. It is appropriate when the main process is designed for interactive use—a shell, a REPL, or a process that reads from stdin. For debugging a service, exec is almost always safer.


d. Comparing exec, attach, and logs

The three commands serve different purposes and have different effects.

CommandConnects toRuns a new processCtrl+C effect
docker logsOutput streamNoStops the log stream
docker execContainer namespaceYesStops the exec’d command
docker attachMain process (PID 1)NoSends SIGINT to PID 1

docker logs is read-only; it never affects the container. docker exec starts a new process, so Ctrl+C affects only that process. docker attach connects to the existing process, so Ctrl+C affects the container itself.

For debugging, the order of preference is: logs first to see what the container has reported, exec to run diagnostic commands, and attach only when direct interaction with the main process is needed.


e. Practical debugging patterns

Check if the container is running and healthy:

docker ps
docker inspect --format '{{.State.Health.Status}}' web

View recent logs:

docker logs --tail 50 web

Follow logs while reproducing an issue:

docker logs -f web

Open a shell to explore:

docker exec -it web /bin/sh

Check processes inside the container:

docker exec web ps aux

Test network connectivity from inside:

docker exec web curl -s http://localhost:80
docker exec web ping -c 3 google.com

Inspect environment variables:

docker exec web env

Check file contents:

docker exec web cat /etc/hosts
docker exec web cat /app/config.json

Copy a file out of the container:

docker cp web:/var/log/app.log ./app.log

Run a database CLI:

docker exec -it db psql -U postgres
docker exec -it db mysql -u root -p

Run a command as root when the container uses a non-root user:

docker exec -u root web whoami

Execute a one-off command without a shell:

docker exec web ls -la /app

For containers built from distroless images, there may be no shell at all. In that case, exec can still run any binary that exists in the image, but you cannot open an interactive shell. Debugging such containers typically involves adding a debug variant of the image or using ephemeral containers in Kubernetes.


Complete Example Session

# ============================================
# PART 1: RUN A CONTAINER
# ============================================
docker run -d --name web -p 8080:80 nginx
# ============================================
# PART 2: VIEW ALL LOGS
# ============================================
docker logs web
# ============================================
# PART 3: VIEW RECENT LOGS WITH TIMESTAMPS
# ============================================
docker logs --tail 20 -t web
# ============================================
# PART 4: FOLLOW LOGS LIVE
# ============================================
docker logs -f --tail 10 web
# ============================================
# PART 5: RUN A ONE-OFF COMMAND
# ============================================
docker exec web ls /usr/share/nginx/html
# ============================================
# PART 6: OPEN AN INTERACTIVE SHELL
# ============================================
docker exec -it web /bin/bash
# ============================================
# PART 7: CHECK PROCESSES INSIDE
# ============================================
docker exec web ps aux
# ============================================
# PART 8: TEST NETWORK FROM INSIDE
# ============================================
docker exec web curl -s http://localhost:80
# ============================================
# PART 9: RUN AS ROOT
# ============================================
docker exec -u root web whoami
# ============================================
# PART 10: DETACH FROM ATTACH
# ============================================
# docker attach web
# Ctrl+P then Ctrl+Q to detach without stopping

These ten parts cover viewing logs, filtering by time and count, following live, running one-off commands, opening a shell, checking processes, testing network, running as a different user, and detaching from attach.


Quick Reference

docker logs Flags

FlagPurpose
(none)All output
-fFollow live output
--tail NLast N lines
-tAdd timestamps
--sinceOutput since a time
--untilOutput until a time

docker exec Flags

FlagPurpose
-iKeep stdin open
-tAllocate a pseudo-TTY
-dDetached (background)
-e KEY=valSet environment variable
-u userRun as user
-w dirSet working directory

Comparison

Aspectlogsexecattach
Reads outputYesNoYes
Runs new processNoYesNo
Interactive inputNoYes with -itYes
Affects main processNoNoYes
Ctrl+C effectStop streamStop commandKill container
Container must be runningNoYesYes

Detach Keys

SequenceEffect
Ctrl+P Ctrl+QDetach without stopping
Ctrl+CSend SIGINT to main process
exit (in exec shell)End the shell, container continues

Best Practices

✅ Do This:

docker logs --tail 50 -f web                  # Recent logs, follow
docker exec -it web /bin/sh                   # Interactive shell
docker exec web ps aux                        # One-off diagnostic
docker exec -u root web whoami                # Root for debugging
docker exec web sh -c 'ps aux | grep nginx'   # Shell features with -c
# Ctrl+P Ctrl+Q to detach from attach

❌ Don’t Do This:

docker attach web                             # ❌ Ctrl+C kills container
docker exec web ps aux | grep nginx           # ❌ Pipe runs on host, not container
docker exec -it web                           # ❌ No command specified
docker logs -f web                            # ❌ Without --tail: dumps entire history first

Common Pitfalls

PitfallWhy It HappensFix
Ctrl+C kills containerUsed attach instead of execUse exec -it; detach with Ctrl+P Ctrl+Q
exec fails on stopped containerContainer not runningStart container first
Shell not foundDistroless or minimal imageUse a debug image or a shell that exists
Pipe runs on hostNot wrapped in sh -c`docker exec web sh -c ‘cmd
Logs emptyApp logs to a file, not stdoutConfigure app to log to stdout/stderr
Logs grow unboundedNo log rotationConfigure max-size and max-file
Cannot read logsNon-json-file driverUse the driver’s native tooling

Real-World Examples

1. Tail and Follow

docker logs -f --tail 100 web

2. Logs Since a Time

docker logs --since 30m web

3. Logs with Timestamps

docker logs -t web | head -20

4. Interactive Shell

docker exec -it web /bin/bash

5. One-Off Command

docker exec web ls -la /app

6. Check Environment

docker exec web env | sort

7. Test Connectivity

docker exec web curl -s -o /dev/null -w "%{http_code}" http://localhost

8. Database CLI

docker exec -it db psql -U postgres -d mydb

9. Run as Root

docker exec -u root web apt-get update

10. Copy Logs Out

docker cp web:/var/log/nginx/access.log ./access.log

Visual

The Three Commands

┌──────────────────────────────────────────────────────────────┐
│  HOST                          CONTAINER                     │
│                                                              │
│  docker logs ◀─────────────── stdout/stderr of PID 1         │
│                                                              │
│  docker exec ──▶ new process (shell, command)                │
│                  independent stdin/stdout                    │
│                  does not affect PID 1                       │
│                                                              │
│  docker attach ◀──────▶ stdin/stdout of PID 1                │
│                  input goes to PID 1                         │
│                  signals go to PID 1                         │
│                  Ctrl+C kills PID 1                          │
└──────────────────────────────────────────────────────────────┘

Exec vs Attach

┌──────────────────────────────────────────────────────────────┐
│  docker exec -it web /bin/sh                                 │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  PID 1: nginx (unchanged)                              │  │
│  │  PID 42: /bin/sh (your shell)                          │  │
│  │                                                        │  │
│  │  Ctrl+C → kills PID 42 only                            │  │
│  │  exit   → ends PID 42, nginx continues                 │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  docker attach web                                           │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  PID 1: nginx (your terminal)                          │  │
│  │                                                        │  │
│  │  Ctrl+C → sends SIGINT to nginx → container dies       │  │
│  │  Detach → Ctrl+P Ctrl+Q (no signal)                    │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

Log Retrieval

┌──────────────────────────────────────────────────────────────┐
│  docker logs web                                             │
│       │                                                      │
│       ▼                                                      │
│  Logging driver (default: json-file)                         │
│       │                                                      │
│       ▼                                                      │
│  /var/lib/docker/containers/<id>/<id>-json.log               │
│       │                                                      │
│       ▼                                                      │
│  Options:                                                    │
│  --tail 50     last 50 lines                                 │
│  -f            follow new output                             │
│  -t            add timestamps                                │
│  --since 10m   from 10 minutes ago                           │
└──────────────────────────────────────────────────────────────┘

Debugging Workflow

┌──────────────────────────────────────────────────────────────┐
│  CONTAINER MISBEHAVING                                       │
│       │                                                      │
│       ▼                                                      │
│  1. docker ps                                                │
│     └── Is it running? What is the status?                   │
│       │                                                      │
│       ▼                                                      │
│  2. docker logs --tail 50 web                                │
│     └── What did it report? Any errors?                      │
│       │                                                      │
│       ▼                                                      │
│  3. docker exec web ps aux                                   │
│     └── What processes are running?                          │
│       │                                                      │
│       ▼                                                      │
│  4. docker exec -it web /bin/sh                              │
│     └── Explore filesystem, test network, inspect config     │
│       │                                                      │
│       ▼                                                      │
│  5. docker inspect web                                       │
│     └── Configuration, mounts, network, exit code            │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
docker logsRead container output
-fFollow live output
--tail NShow last N lines
-tAdd timestamps
--since, --untilFilter by time
docker execRun new process in running container
-itInteractive shell
-uRun as user
-wSet working directory
-eSet environment variable
docker attachConnect to PID 1 stdin/stdout
Detach keysCtrl+P Ctrl+Q
Ctrl+C in attachKills container

Key takeaways:

  • docker logs reads the output a container has produced. It supports following live, showing a tail, adding timestamps, and filtering by time. It does not affect the container.
  • docker exec runs a new process inside a running container. It does not affect the main process, and Ctrl+C stops only the exec’d command. It is the standard tool for debugging.
  • docker exec -it opens an interactive shell. The shell is a new process; exiting it does not stop the container.
  • docker attach connects to the main process. Ctrl+C sends SIGINT to PID 1, which usually kills the container. Detach with Ctrl+P Ctrl+Q instead.
  • Wrap shell features in sh -c. Pipes, redirection, and variables are interpreted by a shell, so docker exec web sh -c 'ps aux | grep nginx' is required when the command uses them.
  • Logs come from the logging driver. The default is json-file, stored on the host. Other drivers may not support docker logs, and log rotation must be configured to prevent disk exhaustion.
  • For debugging, prefer logs then exec. Use attach only when direct interaction with the main process is required, and remember the Ctrl+C risk.

Remember: Once a container is running, the three commands for observing and interacting with it are logs, exec, and attach. They are not interchangeable. logs is read-only and shows what the container has produced. exec runs a new process and is the safe way to debug. attach connects to the main process and is dangerous because signals from your terminal reach PID 1. The rule of thumb is: read logs first, exec second, attach only when necessary. When using exec, remember that shell features require sh -c, and that running as root with -u root is often needed in containers that drop privileges. When using logs, remember that the output comes from the logging driver and that rotation must be configured to prevent the log files from filling the disk. These three commands are the window into a running container, and understanding their differences makes the difference between effective debugging and accidental downtime.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!