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. Imagine PID 1 as the driver of a car;
attachputs you directly in the driver’s seat, holding the steering wheel. - Danger: If you hit
Ctrl+C, you sendSIGINTto PID 1, killing the container (turning off the engine). - Escape: Use
Ctrl+PthenCtrl+Qto gently step out of the driver’s seat and detach without killing the container.
2. docker exec (The Sidecar)
- Target: Creates a new process inside the existing container’s namespaces.
- Behavior: It’s like hopping into the passenger seat of the same car with your own separate controls (e.g., SSH-ing into a server to get a fresh
/bin/bashshell). - Safety: If you hit
Ctrl+C, you only kill your local passenger 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). Here are two examples: a Java app that echoes your input, and a Go app that logs a ticker while listening to input.
Try it:
- Run:
docker run -it --name echo my-app - Detach:
Ctrl+P,Ctrl+Q - Attach again:
docker attach echo
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;
}
}
}
}
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.