To detach from a Docker container without stopping it, we press Ctrl+P and then Ctrl+Q. The Docker client disconnects our terminal, and the container keeps running in the background. To get back in, we run docker attach toolbox, which reconnects us to the same main process.
We detach when a container runs an interactive program that we started with docker run -it, such as a shell or a REPL, and we need our terminal back without ending the program. In most cases, docker exec -it toolbox sh is the better choice, because it opens a second shell that we can close with exit without stopping anything.
The following example starts a container with a shell, detaches from it, and connects to it again in two ways.
docker run -it --name toolbox alpine sh # 1. Start a container with an interactive shell
# 2. Press Ctrl+P, then Ctrl+Q: back on the host
docker ps --filter name=toolbox # STATUS: Up 3 seconds
docker attach toolbox # 3. Same shell again (press Enter to see the prompt)
docker exec -it toolbox sh # 4. A new shell; "exit" leaves the container running
Notice that docker ps still shows the container as Up after the detach keys. Only an exit typed in the main process stops the container.
| What we want | Keys or command | What happens to the container |
|---|---|---|
| Leave the container, keep it running | Ctrl+P, then Ctrl+Q | Keeps running |
| Go back to the main process | docker attach toolbox | Keeps running. Typing exit there stops it. |
| Open a working shell | docker exec -it toolbox sh | Keeps running. Typing exit closes only the new shell. |
Next, we cover these topics.
- The -it and -d options, and why Ctrl+C stops most containers.
- Custom detach keys, and what –sig-proxy=false does in Docker 29.
- The differences between docker attach, docker exec and docker logs -f.
- Detaching from docker compose, and from terminals where Ctrl+P never reaches Docker, such as VS Code and Docker Desktop.
1. How Docker Connects Our Terminal to a Container
Every container runs one main process, which has PID 1 (process ID 1) inside the container. For alpine sh, the main process is the shell, and for nginx:alpine, it is the NGINX master process. When the main process ends, the container stops. Every detach and attach rule follows from the main-process rule. The examples use Docker Engine 29.8.2 and Docker Compose 5.5.1.
The docker command we type is a client program that sends our request to the Docker daemon (dockerd), the background service that runs containers. Unless we ask for the background, the client connects our terminal to the main process, so our keystrokes go to the process’s standard input (stdin) and its output (stdout and stderr) appears in our terminal. “Attached” means the connection between our terminal and the main process exists, whereas “detached” means the container runs without it.
1.1. Foreground, Interactive and Detached Modes
The docker run command can start a container in three ways, and three flags decide which way we get.
- -i (interactive) keeps the container’s stdin open, so our keystrokes reach the main process.
- -t (tty) gives the container a pseudo-terminal (a fake terminal device), so a shell shows a prompt and treats Ctrl+C as a key.
- -d (detach) starts the container in the background and prints the container ID.
| Command | Terminal | Detach with Ctrl+P, Ctrl+Q | Ctrl+C |
|---|---|---|---|
| docker run –name web nginx:alpine | Shows the logs, no input | No | Stops the container |
| docker run -it –name toolbox alpine sh | Shell prompt | Yes | Goes to the shell, which survives |
| docker run -d –name web nginx:alpine | Prints the ID, prompt returns | Not attached | Not attached |
| docker run -dit –name toolbox alpine sh | Prints the ID, prompt returns | Yes, after docker attach | Goes to the shell |
Without any flags, the container runs in the foreground. The terminal shows the NGINX logs until we press a key that stops the container.
docker run --name web nginx:alpine
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/10/03 21:58:26 1#1: nginx/1.31.6
2026/10/03 21:58:26 1#1: start worker processes
For a container we want to keep, -d is the normal choice. For a long-running shell that we may attach to later, we use -dit. For example, a developer who runs a local NGINX for frontend work starts it with -d and keeps the terminal free for other commands.
docker run -d --name web -p 8080:80 nginx:alpine # da5dddf3412a... (prompt returns)
docker run -dit --name toolbox alpine sh # shell waits in the background
To check which of our containers are running, the docker ps command with a name filter is enough.
1.2. Detaching With Ctrl+P and Ctrl+Q
The default detach keys are Ctrl+P followed by Ctrl+Q, so we hold Ctrl, press P, and press Q. The sequence works only when the container was started with both -i and -t. The Docker client reads the two keys itself and never sends them to the container.
$ docker run -it --name toolbox alpine sh
/ # echo $$
1
/ # <- Ctrl+P, Ctrl+Q pressed here
$ docker ps --filter name=toolbox
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
44f1e1cb4a3a alpine "sh" 3 seconds ago Up 3 seconds toolbox
The echo $$ line prints the shell’s process ID, which is 1, so the shell is the main process. After we detach, docker ps still shows Up. The clients report the detach in different ways, and the difference matters in scripts.
| Client | Message after the keys | Exit code of the docker command |
|---|---|---|
| docker run -it | None | 0 |
| docker attach | read escape sequence | 1 |
| docker exec -it | read escape sequence | 1 |
When a container is started without -it, for example with docker run -d nginx:alpine, Ctrl+P passes through as a normal character and nothing happens. In our test, the terminal printed ^P and stayed attached. To leave a container without a terminal, we use –sig-proxy=false (section 3.2) or docker logs -f (section 4.3).
2. Why Ctrl+C Stops the Container
Old tutorials, and even one sentence in the Docker reference, say that Ctrl+C sends SIGKILL. The signal that reaches the container is SIGINT, signal 2, the “interrupt” signal. The container stops because the main process exits when SIGINT arrives, and NGINX writes the signal number in its log. For example, a developer who watches a Spring Boot app with docker run and presses Ctrl+C to get the prompt back stops the app as well.
^C2026/10/03 21:58:29 1#1: signal 2 (SIGINT) received, exiting
2026/10/03 21:58:29 21#21: exiting
2026/10/03 21:58:29 1#1: exit
NAMES STATUS
web Exited (0) Less than a second ago
The signal reaches the container in one of two ways, depending on -t.
- Without -t, the Docker client catches SIGINT and forwards it to PID 1. This forwarding is called signal proxying, and it is on by default (–sig-proxy=true).
- With -t, Ctrl+C is a character typed into the container’s terminal, which turns it into SIGINT for the program in the foreground. In a shell, the foreground program is the command we are running, not the shell itself, so the shell survives.
/ # sleep 100
^C
/ # echo still here
still here
2.1. Why Does Ctrl+C Sometimes Not Stop the Container?
Linux treats PID 1 in a special way. A program can install a signal handler (the code that runs when a signal arrives), and when PID 1 gets a signal without a handler, Linux ignores the signal instead of ending the process. NGINX installs a SIGINT handler, so it exits, whereas a command such as sleep 300 has no handler, so Ctrl+C does nothing and the terminal stays attached.
$ docker run --name napper alpine sleep 300
^C # ignored by PID 1, client still attached
$ docker run --init --name napper2 alpine sleep 300
^C # exit code 130
$ docker ps -a --format '{{.Names}} {{.Status}}'
napper Up 11 seconds
napper2 Exited (130) Less than a second ago
The –init option starts a small init program as PID 1, which forwards signals to our command, so the default signal behavior applies again. The same PID 1 rule explains why docker compose had to kill our alpine sh service with exit code 137 (section 6).
3. Changing How We Detach
Two options change what the detach keys and Ctrl+C do. Both are client options, so we pass them again on every command that attaches to a container.
3.1. Custom Detach Keys With –detach-keys
The –detach-keys option sets a different key sequence for one command. We write the sequence as a comma-separated list of keys, for example ctrl-x,x, where each key is a letter, or ctrl- followed by a letter or one of @ [ \ _ ^. The option works on docker run, docker start, docker attach and docker exec.
docker run -it --detach-keys="ctrl-x,x" --name toolbox alpine sh # Ctrl+X then X detaches
docker attach --detach-keys="ctrl-a" toolbox # Ctrl+A detaches
docker attach toolbox # back to Ctrl+P, Ctrl+Q
In the first command, Ctrl+P and Ctrl+Q went to the shell as normal keys, and Ctrl+X followed by X detached. Docker does not store the keys with the container, so the last docker attach used the default sequence again.
The Docker client reads its settings from ~/.docker/config.json, so to change the default keys for every command, we add detachKeys to config.json.
{
"detachKeys": "ctrl-e,e"
}
With detachKeys set, Ctrl+P reached the shell and busybox sh recalled the previous command, while Ctrl+E followed by E printed read escape sequence. A –detach-keys option on the command line still wins over the file. We pick a sequence that no program inside the container needs. For example, Ctrl+P means “previous command” in bash, which is why many developers change the default.
3.2. Leaving a Container Without -it Using –sig-proxy=false
The –sig-proxy=false option turns off signal forwarding, so Ctrl+C never reaches PID 1. It is meant for containers without a terminal, where Ctrl+P does not work, such as nginx:alpine started with -d.
docker run -d --name web nginx:alpine
docker attach --sig-proxy=false web
^C^C^C
got 3 SIGTERM/SIGINTs, forcefully exiting
NAMES STATUS
web Up 5 seconds
With Docker 29.8.2, one Ctrl+C did not end docker attach. The client kept streaming the NGINX logs for more than ten seconds, and a new request line appeared in that time too. The third Ctrl+C forced the client to exit, and the container kept running. We saw the same result with docker run –sig-proxy=false –name web nginx:alpine. Older tutorials show one Ctrl+C detaching, but we did not test older Docker releases.
With -t, –sig-proxy=false does not help, because Ctrl+C goes to the container’s terminal. In our test, a docker run -dit NGINX container still stopped.
4. docker attach vs docker exec vs docker logs -f
All three commands “connect” to a running container, but each one connects to something different, and the target decides whether leaving can stop the container.

| docker attach toolbox | docker exec -it toolbox sh | docker logs -f web | |
|---|---|---|---|
| Connects to | PID 1, the main process | A new process (here PID 7) | The log stream only |
| Input | Yes | Yes | No |
| exit or Ctrl+D | Stops the container | Closes the new shell only | Not possible |
| Ctrl+C | Goes to PID 1 (stops NGINX) | Goes to the new process | Ends docker logs. The container keeps running. |
| Several at once | All share one screen | Independent shells | Independent |
| Typical use | Interactive main process, such as a REPL | Debugging, running commands | Watching a server |
4.1. Attaching to a Running Container With docker attach
The docker attach command connects our terminal to the main process again. With an idle shell, the terminal stays empty until we press Enter, because the shell printed its prompt before we attached.
$ docker attach toolbox
<- Enter pressed
/ # echo $$
1
/ # read escape sequence <- Ctrl+P, Ctrl+Q
Typing exit (or pressing Ctrl+D) in an attached shell stops the container. The shell is PID 1, so ending the shell ends the container.
$ docker attach toolbox
exit
$ docker ps -a --filter name=toolbox
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
44f1e1cb4a3a alpine "sh" 12 seconds ago Exited (0) Less than a second ago toolbox
A stopped container starts again with docker start toolbox and keeps its filesystem. For a server such as NGINX, docker attach shows the live logs. When we sent curl http://localhost:8080/ from another terminal, the request line appeared in the attached terminal, and Ctrl+C stopped the server with SIGINT, as in section 2.
172.17.0.1 - - [03/Oct/2026:22:05:54 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.5.0" "-"
4.2. Opening a Shell With docker exec
The docker exec command starts an extra process in a running container. For debugging, docker exec -it <name> sh is the safe default. Typing exit closes the new shell, and the main process is not touched. For example, when a containerized Spring Boot app returns errors, we open a shell with docker exec and read the mounted config files without any risk to the app.
$ docker exec -it toolbox sh
/ # echo $$
7
/ # ps
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 sh
13 root 0:00 ps
/ # exit
$ docker ps --filter name=toolbox
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4743eadcbfb7 alpine "sh" 4 seconds ago Up 4 seconds toolbox
The ps output shows both shells, where PID 1 is the main shell and PID 7 is ours. Images such as nginx:alpine and alpine have sh but no bash (in our test, which bash found nothing in either image), whereas most Debian- and Ubuntu-based images have bash. A one-off command needs no -it.
docker exec web hostname # c86f1385ed25 (the short container ID)
We avoid Ctrl+P, Ctrl+Q in an exec session, because the keys detach the client but the exec shell keeps running in the container, and our ps still listed it afterwards. Docker has no command to re-attach to an exec session, so closing an exec shell with exit is cleaner. We use the same docker exec approach to connect to a database running in a Docker container with the database’s command-line client.
4.3. Watching Output With docker logs -f
To watch a server’s output without any risk of stopping the server, we follow the log stream with docker logs -f. Ctrl+C ends only the docker logs command, with exit code 130, and the command never connects to the container’s input or signals, so the container keeps running.
2026/10/03 21:59:21 1#1: start worker process 21
172.17.0.1 - - [03/Oct/2026:22:00:18 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.5.0" "-"
^C
$ docker ps --filter name=web --format '{{.Names}} {{.Status}}'
web Up About a minute
5. Detaching in VS Code and Docker Desktop Terminals
Terminals inside other applications often use Ctrl+P for their own shortcuts, so the key never reaches the Docker client. In the VS Code integrated terminal, Ctrl+P opens Quick Open, the file search. Two fixes work.
- Change the detach keys in ~/.docker/config.json (section 3.1) to a sequence the editor does not use, such as ctrl-e,e.
- Tell VS Code to send the Quick Open key to the terminal instead, with the terminal.integrated.commandsToSkipShell setting.
{
"terminal.integrated.commandsToSkipShell": [
"-workbench.action.quickOpen"
]
}
VS Code also has the terminal.integrated.sendKeybindingsToShell setting, which sends most shortcuts to the shell, but the editor shortcuts, such as Ctrl+F in the terminal, stop working.
In Docker Desktop, each container has an Exec tab that opens a shell the same way docker exec does, so exit, or closing the tab, leaves the container running and no detach keys are needed. When in doubt, we use docker exec and exit, which work in every terminal.
6. Detaching From docker compose
Docker Compose (the docker compose up command) gives us the same choices. Before we can attach to a service and detach with Ctrl+P, Ctrl+Q, the service needs stdin_open and tty, the Compose versions of -i and -t.
services:
web:
image: nginx:alpine
toolbox:
image: alpine
command: sh
stdin_open: true # docker run -i
tty: true # docker run -t
docker compose up -d # start both services in the background
docker compose attach toolbox # attach to the toolbox service
docker compose exec toolbox sh # or open a new shell
[+] up 3/3
Network demo_default Created
Container demo-web-1 Started
Container demo-toolbox-1 Started
$ docker compose attach toolbox
/ # hostname
5b2418b466df
/ # read escape sequence
The docker compose attach command accepts –detach-keys and –sig-proxy, like docker attach, and also –index, which picks the replica (one running copy of the service) to attach to. In an interactive terminal, docker compose up in the foreground shows the menu line w Enable Watch d Detach, and pressing d returned our prompt while both containers kept running. Pressing Ctrl+C in docker compose up stops every service instead.
Attaching to toolbox-1, web-1
Container demo-web-1 Stopping
Container demo-toolbox-1 Stopping
Container demo-web-1 Stopped
web-1 exited with code 0
Container demo-toolbox-1 Stopped
toolbox-1 exited with code 137
The toolbox service exited with code 137, which means Compose killed the container with SIGKILL. The sh shell, as PID 1, ignored the stop signal, so Compose waited for the 10-second timeout and then killed the container.
7. Docker Detach FAQs
7.1. How Do I Detach From a Docker Container Without Stopping It?
If the container was started with -it, press Ctrl+P, then Ctrl+Q, and in a docker exec shell, exit is enough. If the container has no terminal, use docker logs -f to watch it, or docker attach –sig-proxy=false with three presses of Ctrl+C (section 3.2).
7.2. Why Does docker attach Show Nothing?
Because the main process is waiting for input and printed its prompt before we attached, nothing new appears until we press Enter. For a server, docker attach shows only new output, so to see what the server printed earlier, we use docker logs.
7.3. Does exit Stop the Container?
In docker attach or docker run -it, yes, because exit ends PID 1 and the container shows Exited (0). In docker exec, no, because exit ends only the extra shell.
7.4. How Do I Get Back Into a Stopped Container?
We start it first, because a stopped container has no process to attach to. The call docker start toolbox runs the container’s command again, here a new sh, and docker attach toolbox connects to the new shell. To clean up containers we no longer need, see how to remove Docker containers.
8. Conclusion
To leave a container running, we detach with Ctrl+P, Ctrl+Q, which works only with -it, or we never attach to the main process at all. Instead, we open a shell with docker exec -it <name> sh and leave with exit. Ctrl+C sends SIGINT to PID 1, which stops most servers, and the –sig-proxy=false option blocks that signal.
Editors such as VS Code capture Ctrl+P, so we move the detach keys with –detach-keys or detachKeys in ~/.docker/config.json. For Compose, docker compose up -d and docker compose exec follow the same rules. To package our own application into a container, we can start with the Docker hello world example and then move on to the Spring Boot Docker guide.
9. References
- docker container attach (Docker CLI reference)
- docker container run (Docker CLI reference)
- docker container exec (Docker CLI reference)
- Docker CLI configuration files
- Running containers (Docker Engine docs)
- docker compose attach
- Compose services reference: stdin_open and tty
- VS Code terminal advanced settings
- signal(7) Linux manual page
Happy Learning !!