Foreground vs Detached Mode: When to Run Docker Containers in the Background
Foreground or -d? How attached output, Ctrl-C, Ctrl-P Ctrl-Q detach, docker logs and restart policies interact — and how to pick the right mode every time.
Published on • October 31, 2026
AI Assistant

Every docker run has exactly two personalities. In foreground mode (the default) the CLI stays attached to the container’s output streams and blocks your terminal until the container exits. With -d (--detach) the CLI returns immediately after printing a container ID, and the container quietly does its work in the background. The flag is one character; getting it wrong costs you either a blocked terminal or hours spent hunting for logs that were there all along.
What foreground mode gives you
Run a container with no -d and the Docker CLI binds itself to the container’s STDOUT and STDERR:
$ docker run --name worker my-app:latest
2026-10-31T08:12:01Z starting up
2026-10-31T08:12:01Z listening on :8080
^C
What you get:
- Live output with no extra commands. What the process prints is what you see — ideal for watching a migration, a build, or a crash loop as it happens.
- The exit code. When the container stops,
docker runreturns the container’s exit status to your shell, sodocker run ... && echo okworks as you’d expect in scripts. - Ctrl-C works. Pressing Ctrl-C sends
SIGINTto the container’s main process (the CLI proxies received signals to the container by default —--sig-proxydefaults totrue), and the container stops with it. - Graceful detaching. Press Ctrl-P then Ctrl-Q and the client disconnects without stopping the container. The container keeps running; you get your shell back. This is the standard escape hatch, and you can change the key sequence with
--detach-keysif it collides with your terminal.
A detail worth internalizing: attaching is not the same as logging. Output is captured by the configured logging driver whether or not anyone is attached. You can detach, close the terminal, go home — and the lines are still waiting in docker logs.
Foreground also composes with -i (keep STDIN open) and -t (allocate a pseudo-TTY), which is why interactive shells are almost always written -it:
$ docker run -it --name shell alpine sh
/ # ls
Without -t, some programs behave oddly (prompts without echo suppression, no job control); with -t but no -i, there is no input at all. Neither flag changes where output goes — only how it reaches your terminal.
What -d does
$ docker run -d --name web -p 80:80 nginx:alpine
4bed76d3ad428b889c56c1ecc2bf2ed95cb08256db22dc5ef5863e1d03252a19
-d prints the container ID and returns. Your terminal is free, and the container is now a background service of the Docker daemon. Two consequences from the docker run reference:
- A detached container exits when its main process exits. There is no terminal holding it alive. If your command daemonizes itself (
service nginx start,daemonize,nohup ... &) and returns, the container stops immediately — often leaving a half-started service that appears to “flash” indocker ps. Run the server in the foreground inside the container instead:nginx -g 'daemon off;',docker run -d my-image ./app-server. - I/O happens elsewhere. The container is no longer listening to the command line where
docker runwas run. To interact with it, use ports, shared volumes,docker exec, ordocker attach.
When each mode is right
| Foreground | Detached (-d) | |
|---|---|---|
| Typical use | Debugging, one-off jobs, dev iteration | Services, workers, anything long-running |
| Output | On your terminal instantly | Only via docker logs / docker attach |
| Terminal | Occupied until exit | Free immediately |
| Exit code | Returned to your shell | Hidden in docker inspect |
| Ctrl-C | Stops the container | Kills your client only (container keeps running) |
Pairs with --restart | Poorly | Naturally |
Use foreground when the command is the task: a migration, a test suite, a quick busybox probe, or the first five minutes of debugging a container that misbehaves. If it will finish, you want its exit code and its output in the same place.
Use -d for anything meant to outlive your shell session: web servers, databases, queue consumers, agents. Add --name, and pair it with a restart policy (--restart=unless-stopped) so the service survives daemon restarts.
A hybrid that covers most workflows: start detached, then follow the output:
$ docker run -d --name api my-api:latest
$ docker logs -f api
The restart-policy trap
Attaching and restart policies don’t mix well. As the docker run reference puts it: on container restart, attached clients are disconnected. A foreground docker run --restart=always ... returns to your shell when the process exits while the daemon restarts the container in the background — you are no longer watching anything, and you may not notice. (--restart combined with --rm is rejected outright as contradictory.)
Rule of thumb: restart policies belong to -d; foreground belongs to --rm or to jobs that should run exactly once.
The --attach flags: pick your streams
-d is not all-or-nothing. --attach (-a) selects which streams a docker run binds to, and you can combine it with -d to filter what lands in your terminal or script:
# print only errors to the console; normal output stays in the logs
$ docker run -a stderr ubuntu echo test
4bed76d3ad428b889c56c1ecc2bf2ed95cb08256db22dc5ef5863e1d03252a19
# pipe stdin into a background job and grab its ID
$ cat somefile | docker run -d -i -a stdin mybuilder dobuild
To rejoin a running detached container later:
$ docker attach web # Ctrl-P Ctrl-Q to leave again without stopping it
$ docker detach web # detach programmatically
Common mistakes
“I ran -d and now there are no logs.” There are logs — docker logs reads whatever the container’s process wrote to STDOUT/STDERR, regardless of mode. If docker logs shows nothing, the real problem is usually that the application writes to files inside the container instead of the standard streams (the official nginx image solves this by symlinking its log files to /dev/stdout and /dev/stderr). Check with docker inspect -f '{{.HostConfig.LogConfig.Type}}' <container> which driver is storing them.
“My terminal froze / I can’t Ctrl-C it.” A foreground container whose main process ignores signals will not react to Ctrl-C — particularly PID 1, which ignores signals whose default action would terminate it unless it installs handlers. Escape with Ctrl-P Ctrl-Q, then stop it properly with docker stop <name>.
“I closed the terminal and the container died.” Only if it was running in foreground and something sent it a signal. Closing an attached terminal can deliver SIGHUP; run services with -d so they don’t depend on your session.
“I Ctrl-C’d in the wrong terminal.” Ctrl-C only reaches containers attached to that terminal. From any other terminal, stop a stray foreground container with docker stop <name> or docker kill <name> — there is nothing to interrupt client-side.
“I docker exec’d in and killed my app.” docker exec processes are not PID 1 and share the container’s fate only indirectly; killing the exec’d shell does nothing to the main process. Stop the container itself.