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; attach puts you directly in the driver’s seat, holding the steering wheel.
  • Danger: If you hit Ctrl+C, you send SIGINT to PID 1, killing the container (turning off the engine).
  • Escape: Use Ctrl+P then Ctrl+Q to 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/bash shell).
  • 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.

Actions
Container Namespace
PID 1
PID 45
> Container running...

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:

  1. Run: docker run -it --name echo my-app
  2. Detach: Ctrl+P, Ctrl+Q
  3. 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.