Exec vs Attach: Interacting with Containers
Once a container is running, it feels like a black box. How do you see what’s happening inside? How do you run a database migration or inspect a configuration file?
You have two primary tools: attach and exec. They sound similar, but they operate on fundamentally different principles.
1. The Difference: PID 1 vs New Process
graph TD
User((User))
subgraph Host [Host Machine]
DockerCLI[Docker CLI]
subgraph Container [Container Boundary (PID Namespace)]
PID1[PID 1: App]
PID2[PID 2: /bin/bash]
end
end
User -->|docker attach| DockerCLI
DockerCLI -.->|Stream Stdout/Stdin| PID1
User -->|docker exec| DockerCLI
DockerCLI -->|Spawn New Process| PID2
1. docker attach (The Main Line)
- Target: Connects your terminal’s standard input/output/error to the primary process (PID 1).
- Behavior: You are “becoming” the screen and keyboard for the running app.
- Danger: If you hit
Ctrl+C, you sendSIGINTto PID 1, killing the container. - Escape: Use
Ctrl+PthenCtrl+Qto detach without killing.
2. docker exec (The Sidecar)
- Target: Creates a new process inside the existing container’s namespaces.
- Behavior: It’s like SSH-ing into a server. You get a fresh shell (e.g.,
/bin/bash). - Safety: If you hit
Ctrl+C, you only kill your shell process (e.g., PID 25), not the main app (PID 1).
2. Interactive: Namespace Explorer
Visualize how attach and exec interact with the container’s process space.
3. Code Example: The “Attachable” App
To practice docker attach, you need an app that interacts with Standard Input (Stdin) and Standard Output (Stdout).
This Java app echoes whatever you type. Perfect for testing attach.
import java.util.Scanner;
public class EchoChamber {
public static void main(String[] args) {
System.out.println("Echo Chamber Started. Type something:");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println("You said: " + line);
if ("exit".equalsIgnoreCase(line)) {
break;
}
}
}
}
Try it:
- Run:
docker run -it --name echo my-java-app - Detach:
Ctrl+P,Ctrl+Q - Attach again:
docker attach echo
This Go app logs a counter every second but also listens for user input.
package main
import (
"bufio"
"fmt"
"os"
"time"
)
func main() {
// Background ticker
go func() {
i := 0
for {
time.Sleep(2 * time.Second)
fmt.Printf("[Log] Ticker %d\n", i)
i++
}
}()
fmt.Println("Interactive Shell ready. Type commands:")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
text := scanner.Text()
fmt.Printf("Received: %s\n", text)
}
}
Debug Tip: If you docker attach to this, you will see the “[Log] Ticker” messages interleaved with your typing. This is why docker logs is usually preferred for viewing output, and docker attach is reserved for interactive sessions.